Python3使用urllib訪問網頁


介紹

改教程翻譯自python官網的一篇文檔

urllib.request是一個用於訪問URL(統一資源定位符)的Python模塊。它以urlopen函數的形式提供了一個非常簡單的接口,可以訪問使用多種不同協議的URL。它也提供了一個稍微復雜一些的接口,用來處理常用的情況——如基本的認證,cookies,代理等等。這些服務由叫做handlers和openers的對象提供。

urllib.request支持訪問多種“URL模式”(模式由URL中“:”前面的字符串確定——比如“ftp”就是“ftp://python.org/”的URL模式),使用的是它們對應的網絡協議(如FTP,HTTP)。這個教程集中於最常用的的類型,HTTP。

對於直截了當的情況,urlopen很容易使用。但是,一旦你在打開HTTP URL的時候遇到錯誤或者一些不平常的情況,你就需要對超文本轉換協議的一些理解。關於HTTP最全面最權威的參考是RFC 2616。這是一個技術文檔,不太容易閱讀。這篇文章的目標就是說明如何使用urllib,包含足夠的HTTP協議細節幫助你理解。這篇文章不是要代替urllib.request的文檔,而是對它的補充。

訪問URL

使用urllib.request最簡單的方式如下:

import urllib.request
with urllib.request.urlopen('http://python.org/') as response:
    html = response.read()

如果你想利用url下載一個資源並把它存儲在一個臨時文件中,你可以利用urlretrieve()函數:

import urllib.request
local_filename,headers = urllib.request.urlretrieve('http://pythpn.org/')
html = open(local_filename)

下圖是演示效果:urlretrieve

uellib的很多用法就是這么簡單(注意除了‘http:’類型的url,我們也可以使用以‘ftp:’、‘file:’等開頭的url)。然而,這個教程的目的是解釋HTTP中一些更復雜的情形。

HTTP是基於請求和響應的——客戶端發起請求,服務端返回響應。urllib.request用Request類來表示你做出的HTTP請求。它的最簡形式就是只指定你需要訪問的url。Request對象調用urlopen方法會為這個請求返回一個響應對象。這個響應是一個類文件對象,意味着你可以對其調用.read()方法:

import urllib.request

req = urllib.request.Request('http://www.voidspace.org.uk')
with urllib.request.urlopen(req) as response:
   the_page = response.read()

注意urllib.request使用相同的接口來處理所有類型的url。比如說,你也可以這樣發起一個FTP請求:

req = urllib.request.urlopen('ftp://example.com/')

在HTTP的情況下,Request對象還允許你做兩件事:第一,你可以傳遞要發送到服務端的數據;第二,你可以發送關於數據或請求自身的額外信息(元數據)給服務器——這個信息會作為HTTP的“請求頭”進行發送。讓我們分別看一下。

數據

有時你想使用HTTP發送數據到一個url(通常這個url會指向一個CGI(通用網關接口,Common Gateway Interface)腳本或其它網絡應用),這通常使用一個POST請求來完成。這也就是當你提交一份填寫好的HTML表單的時候,你的瀏覽器所做的事情。不是所有的POST請求都來自表單:你可以使用POST方式把任意的數據發送到你自己的應用。在常見的HTML表單情況中,數據需要用標准方式進行編碼,然后作為data參數傳遞給Request對象。編碼是使用一個urllib.parse庫中的函數完成的。

import urllib.parse
import urllib.request

url = 'http://www.someserver.com/cgi-bin/register.cgi'
values = {'name' : 'Michael Foord',
          'location' : 'Northampton',
          'language' : 'Python' }

data = urllib.parse.urlencode(values)
data = data.encode('ascii') # data should be bytes
req = urllib.request.Request(url, data)
with urllib.request.urlopen(req) as response:
   the_page = response.read()

如果不傳遞data參數,那urllib就會使用GET請求方式。GET方式和POST方式的其中一個區別在於POST請求經常有副作用:它們會以某種方式改變系統的狀態(比如,在網上下訂單,會有一英擔的午餐肉罐頭送到你家門口)。盡管HTTP標准明確說POST方式總是會造成副作用,而GET方式從來不會,但是並沒有保證措施。數據也可以用GET方式傳遞,只要把它編碼在url中。

做法如下:

>>> import urllib.request
>>> import urllib.parse
>>> data = {}
>>> data['name'] = 'Somebody Here'
>>> data['location'] = 'Northampton'
>>> data['language'] = 'Python'
>>> url_values = urllib.parse.urlencode(data)
>>> print(url_values)  # The order may differ from below.  
name=Somebody+Here&language=Python&location=Northampton
>>> url = 'http://www.example.com/example.cgi'
>>> full_url = url + '?' + url_values
>>> data = urllib.request.urlopen(full_url)

注意full_url 是通過在url后面加一個?,然后再加上編碼后的數據進行創建的。

頭信息

我們在這里討論一個特殊的HTTP頭信息,來說明如何在你的HTTP請求中添加頭信息。

一些網站不喜歡被程序訪問,或者會給不同的瀏覽器發送不同的版本。默認情況下,urllib會把自身標記為Python-urllib/x.y(其中x和y表示Python的版本號,如Python-urllib/2.5),這可能會迷惑網站,或者干脆不起作用。瀏覽器標識自己的方式就是通過User-agent頭信息。當你創建一個Request對象時,你可以傳遞一個頭信息的字典。下面的例子發起的是跟上面一樣的請求,但是把自己標識為一個IE瀏覽器的版本。

import urllib.parse
import urllib.request

url = 'http://www.someserver.com/cgi-bin/register.cgi'
user_agent = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64)'
values = {'name':'Michael Foord',
		  'location':'Northampton',
		  'language':'Python' }
headers = {'User-Agent':user_agent}

data = urllib.parse.urlencode(values)
data = data.encode('ascii')
req = urllib.request.Request(url, data, headers)
with urllib.request.urlopen(req) as response:
	the_page = response.read()

這個響應也有兩個有用的方法。看[info和geturl](#info 和 geturl) 這一節。

處理異常

urlopen在不能處理某個響應的時候會拋出URLError(雖然一般使用Python API時,ValueError、TypeError等內建異常也可能被拋出)。

HTTPErrorURLError的子類,在遇到HTTP URL的特殊情況時被拋出。

異常類出自urllib.error模塊。

URLError

一般來說,URLError被拋出是因為沒有網絡連接(沒有到指定服務器的路徑),或者是指定服務器不存在。在這種情況下,拋出的異常將會包含一個‘reason’屬性,這是包含一個錯誤碼和一段錯誤信息的元組。例如

>>> req = urllib.request.Request('http://www.pretend_server.org')
>>> try: urllib.request.urlopen(req)
... except urllib.error.URLError as e:
...     print(e.reason)      
...
(4, 'getaddrinfo failed')

HTTPError

每一個來自服務器的HTTP響應都包含一個數字的“狀態碼”。有時狀態碼表明服務器不能執行請求。默認的處理程序會為你處理其中的部分響應(比如,如果響應是“重定向”,要求客戶端從一個不同的URL中獲取資料,那么urllib將會為你處理這個)。對於那些不能處理的響應,urlopen將會拋出一個HTTPError。典型的錯誤包括‘404’(頁面未找到),‘403’(請求禁止),和‘401’(請求認證)。

查看RFC 2616的第10節,作為對所有HTTP錯誤碼的參考。

拋出的HTTPError實例有一個整型的‘code’屬性,對應於服務器發送的錯誤。

錯誤碼

因為默認的處理程序會處理重定向問題(范圍在300的錯誤碼),而且范圍100-299之間的狀態碼表示成功,所以你通常只會看到范圍在400-599之間的錯誤碼。

http.server.BaseHTTPRequestHandler.responses 是一個關於響應碼的字典,展示了RFC 2616使用的所有狀態碼。為了方便,把字典展示如下:

# Table mapping response codes to messages; entries have the
# form {code: (shortmessage, longmessage)}.
responses = {
    100: ('Continue', 'Request received, please continue'),
    101: ('Switching Protocols',
          'Switching to new protocol; obey Upgrade header'),

    200: ('OK', 'Request fulfilled, document follows'),
    201: ('Created', 'Document created, URL follows'),
    202: ('Accepted',
          'Request accepted, processing continues off-line'),
    203: ('Non-Authoritative Information', 'Request fulfilled from cache'),
    204: ('No Content', 'Request fulfilled, nothing follows'),
    205: ('Reset Content', 'Clear input form for further input.'),
    206: ('Partial Content', 'Partial content follows.'),

    300: ('Multiple Choices',
          'Object has several resources -- see URI list'),
    301: ('Moved Permanently', 'Object moved permanently -- see URI list'),
    302: ('Found', 'Object moved temporarily -- see URI list'),
    303: ('See Other', 'Object moved -- see Method and URL list'),
    304: ('Not Modified',
          'Document has not changed since given time'),
    305: ('Use Proxy',
          'You must use proxy specified in Location to access this '
          'resource.'),
    307: ('Temporary Redirect',
          'Object moved temporarily -- see URI list'),

    400: ('Bad Request',
          'Bad request syntax or unsupported method'),
    401: ('Unauthorized',
          'No permission -- see authorization schemes'),
    402: ('Payment Required',
          'No payment -- see charging schemes'),
    403: ('Forbidden',
          'Request forbidden -- authorization will not help'),
    404: ('Not Found', 'Nothing matches the given URI'),
    405: ('Method Not Allowed',
          'Specified method is invalid for this server.'),
    406: ('Not Acceptable', 'URI not available in preferred format.'),
    407: ('Proxy Authentication Required', 'You must authenticate with '
          'this proxy before proceeding.'),
    408: ('Request Timeout', 'Request timed out; try again later.'),
    409: ('Conflict', 'Request conflict.'),
    410: ('Gone',
          'URI no longer exists and has been permanently removed.'),
    411: ('Length Required', 'Client must specify Content-Length.'),
    412: ('Precondition Failed', 'Precondition in headers is false.'),
    413: ('Request Entity Too Large', 'Entity is too large.'),
    414: ('Request-URI Too Long', 'URI is too long.'),
    415: ('Unsupported Media Type', 'Entity body in unsupported format.'),
    416: ('Requested Range Not Satisfiable',
          'Cannot satisfy request range.'),
    417: ('Expectation Failed',
          'Expect condition could not be satisfied.'),

    500: ('Internal Server Error', 'Server got itself in trouble'),
    501: ('Not Implemented',
          'Server does not support this operation'),
    502: ('Bad Gateway', 'Invalid responses from another server/proxy.'),
    503: ('Service Unavailable',
          'The server cannot process the request due to a high load'),
    504: ('Gateway Timeout',
          'The gateway server did not receive a timely response'),
    505: ('HTTP Version Not Supported', 'Cannot fulfill request.'),
    }

當錯誤被拋出時,服務器的響應就是返回一個HTTP錯誤碼和一個錯誤頁面。你可以使用HTTPError實例作為返回頁面的響應。這意味着除了‘code’屬性外,也可以使用由urllib.response模塊返回的read、geturl和info方法。演示如下:

req = urllib.request.Request('http://www.python.org/fish.html')
try:
    urllib.request.urlopen(req)
except urllib.error.HTTPError as e:
    print (e.code)
    print (e.info())
    print (e.geturl())
    print (e.read())

上面程序的效果如下,httperror_result

簡單起見,上圖中后面的頁面內容沒有全部截圖。

包裝一下

如果你希望程序對HTTPErrorURLError有所准備,有兩種基本方法可以使用,我更喜歡第二個。

第一種

from urllib.request import Request, urlopen
from urllib.error import URLError, HTTPError
req = Request(someurl)
try:
    response = urlopen(req)
except HTTPError as e:
    print('The server couldn\'t fulfill the request.')
    print('Error code: ', e.code)
except URLError as e:
    print('We failed to reach a server.')
    print('Reason: ', e.reason)
else:
    # everything is fine

注意except HTTPError必須放在第一個,否則except URLError也將捕獲一個HTTPError。

第二種

from urllib.request import Request, urlopen
from urllib.error import URLError
req = Request(someurl)
try:
    response = urlopen(req)
except URLError as e:
    if hasattr(e, 'reason'):
        print('We failed to reach a server.')
        print('Reason: ', e.reason)
    elif hasattr(e, 'code'):
        print('The server couldn\'t fulfill the request.')
        print('Error code: ', e.code)
else:
    # everything is fine

info和geturl

urlopen(或HTTPError實例)返回的響應有兩個實用的方法info()geturl(),是在模塊urllib.response中定義的。

geturl—這個方法返回的是所獲取頁面的真正URL路徑。這個是有用的,因為urlopen(或者使用的opener對象)可能經過了重定向。因此所得頁面的URL可能不是請求的URL。

info—這個方法返回一個類似字典的對象,描述所得到的頁面,尤其是服務器發回的頭信息。它實際上是一個http.client.HTTPMessage實例。

典型的頭信息包括‘Content-length’、‘Content-type’等等。你可以查看關於HTTP 頭信息的快速指南,這里有關於HTTP頭信息的含義和使用的簡短說明。

Openers和Handlers

當你訪問URL時,你使用的就是一個opener(這是urllib.reuest.OpenerDirector的一個實例)。一般來說我們使用的是默認的opener—通過urlopen—但是你可以創建自定義的opener。opener會使用handler。所有的“重活”都是由handler完成的。每一個handler知道如何去處理某種類型的URL(http,ftp等等),或者是如何處理訪問URL的某一方面,如HTTP重定向或HTTP cookies。

如果你想要用特定的handler來訪問URL,你可以創建一個opener。比如,得到一個處理cookies的opener或者是一個不處理重定向的opener。

要創建一個opener,可以實例化一個OpenerDirector,然后重復地調用.add_handler(some_handler_instance)

或者,你可以使用build_opener方法,這是一個很方便的函數,可以通過一次函數調用創建opener對象。build_opener默認添加了一些handler,但是提供了一個簡單的方法添加或者覆寫默認的handler。

你可能需要其它類型的handler,比如可以處理代理,認證,以及其它常見但是特殊的情形。

install_opener可以用來把一個opener對象設定為(全局)默認opener。這意味着調用urlopen的時候會使用你安裝的opener。

opener對象有一個open方法,可以直接調用來獲取URL資源,方式與urlopen相同:除非為了方便,否則不需要調用install_opener

基本認證

為了說明創建和安裝一個handler,我們使用HTTPBasicAuthHandler為例。關於這個話題的更多細節—包括關於基本認證如何工作的說明—請參看基本認證教程

當需要認證(或授權)時,服務器會發送一個頭信息(還有一個401錯誤碼)請求認證。它指定了認證模式和一個“realm(領域)”。這個頭信息的形式看起來是:WWW-Authenticate:SCHEME realm="REALM"

例如,WWW-Authenticate: Basic realm="cPanel Users"

如何客戶端應該重新發起請求,並把對應realm的用戶名與密碼加入請求的頭信息中。這就是“基礎認證”。為了簡化這個過程,我們創建一個HTTPBasicAuthHandler實例,再有一個opener來使用這個handler。

HTTPBasicAuthHandler使用一個叫做密碼管理器的對象處理url和用戶名密碼域的映射。如果你知道領域是什么(根據服務器發送的認證頭信息得知),那么你可以使用HTTPPasswordMgr。人們往往不在乎領域是什么。在那種情況下,最方便的就是使用HTTPPasswordMgrWithDefaultRealm。它允許你為一個url指定一個默認的用戶名和密碼。如果你沒有為某個領域提供可選的組合,anemia就會使用這個。我們通過把None作為add_password方法的realm參數來表示它。

頂層url就是需要認證的第一個url,比你傳遞給add_password()方法的url更“深層”的url也將匹配。

# create a password manager
password_mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()

# Add the username and password.
# If we knew the realm, we could use it instead of None.
top_level_url = "http://example.com/foo/"
password_mgr.add_password(None, top_level_url, username, password)

handler = urllib.request.HTTPBasicAuthHandler(password_mgr)

# create "opener" (OpenerDirector instance)
opener = urllib.request.build_opener(handler)

# use the opener to fetch a URL
opener.open(a_url)

# Install the opener.
# Now all calls to urllib.request.urlopen use our opener.
urllib.request.install_opener(opener)

注意:在上面的例子中,我們 build_opener時只使用了我們的HTTPBasicAuthHandler。默認情況下,opener包含處理常見情形的handler— ProxyHandler ( 如果設置了代理,比如http_proxy變量 ), UnknownHandlerHTTPHandler,HTTPDefaultErrorHandlerHTTPRedirectHandlerFTPHandlerFileHandlerDataHandlerHTTPErrorProcessor.

事實上,頂層url要么是一個完整的url(包括“http:”模式和主機名以及可選的端口號),例如“http://example.com/” ,或者是一個“授權機構”(即主機名,可以再加一個端口號)如“example.com” 或“example.com:8080” 。如果使用“授權機構”的話,一定不能包含“userinfo”元素—比如“joe:password@example.com”就是不正確的。

代理

urllib會自動地檢測並使用你的代理設置。這是通過ProxyHandler完成的,它是在檢測到一個代理設置時的正常處理程序鏈的一部分。通常這是個好事,但是有些時候它可能沒有用。還有一個方法就是設置我們自己的ProxyHandler,不定義代理。做法與設置一個Basic Authentication的步驟相同:

>>> proxy_support = urllib.request.ProxyHandler({})
>>> opener = urllib.request.build_opener(proxy_support)
>>> urllib.request.install_opener(opener)

Note:現在urllib.request不支持通過代理訪問一個https網址。不過,可以通過這篇教程實現。

如果設置一個變量REQUEST_METHOD,那么HTTP_PROXY就會被忽略,具體可以查看getproxies()文檔

套接字層

Python對於獲取網絡數據的支持是有層次的。urllib使用的是http.client庫,而他又轉而使用socket庫。

在Python 2.3中你可以設定套接字的超時等待時長。這在必須獲取網頁的應用中是很有用的。默認情況下,套接字模塊沒有超時設置,並且可能會一直等待。目前,套接字超時不在http.client或者urllib.request層可見了。但是,你可以為所有的套接字設定一個全局的等待時長:

import socket
import urllib.request

# timeout in seconds
timeout = 10
socket.setdefaulttimeout(timeout)

# this call to urllib.request.urlopen now uses the default timeout
# we have set in the socket module
req = urllib.request.Request('http://www.voidspace.org.uk')
response = urllib.request.urlopen(req)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM