1. 程式人生 > 程式設計 >Python3爬蟲傳送請求的知識點例項

Python3爬蟲傳送請求的知識點例項

使用urllib的request模組,我們可以方便地實現請求的傳送並得到響應,本節就來看下它的具體用法。

1. urlopen()

urllib.request模組提供了最基本的構造HTTP請求的方法,利用它可以模擬瀏覽器的一個請求發起過程,同時它還帶有處理授權驗證(authenticaton)、重定向(redirection)、瀏覽器Cookies以及其他內容。

下面我們來看一下它的強大之處。這裡以Python官網為例,我們來把這個網頁抓下來:

importurllib.request
response=urllib.request.urlopen('https://www.python.org')
print(response.read().decode('utf-8'))

執行結果如圖3-1所示。

61f07d7e2fda534346eb6f900f0b60b.png

這裡我們只用了兩行程式碼,便完成了Python官網的抓取,輸出了網頁的原始碼。得到原始碼之後呢?我們想要的連結、圖片地址、文字資訊不就都可以提取出來了嗎?

接下來,看看它返回的到底是什麼。利用type()方法輸出響應的型別:

importurllib.request
response=urllib.request.urlopen('https://www.python.org')
print(type(response))

輸出結果如下:

<class'http.client.HTTPResponse'>

可以發現,它是一個HTTPResposne型別的物件。它主要包含read()、readinto()、getheader(name)、getheaders()、fileno()等方法,以及msg、version、status、reason、debuglevel、closed等屬性。

得到這個物件之後,我們把它賦值為response變數,然後就可以呼叫這些方法和屬性,得到返回結果的一系列資訊了。

例如,呼叫read()方法可以得到返回的網頁內容,呼叫status屬性可以得到返回結果的狀態碼,如200代表請求成功,404代表網頁未找到等。

下面再通過一個例項來看看:

importurllib.request
response=urllib.request.urlopen('https://www.python.org')
print(response.status)
print(response.getheaders())
print(response.getheader('Server'))

執行結果如下:

200
[('Server','nginx'),('Content-Type','text/html;charset=utf-8'),('X-Frame-Options','SAMEORIGIN'),('X-Clacks-Overhead','GNUTerryPratchett'),('Content-Length','47397'),('Accept-Ranges','bytes'),('Date','Mon,01Aug201609:57:31GMT'),('Via','1.1varnish'),('Age','2473'),('Connection','close'),('X-Served-By','cache-lcy1125-LCY'),('X-Cache','HIT'),('X-Cache-Hits','23'),('Vary','Cookie'),('Strict-Transport-Security','max-age=63072000;includeSubDomains')]
nginx

可見,前兩個輸出分別輸出了響應的狀態碼和響應的頭資訊,最後一個輸出通過呼叫getheader()方法並傳遞一個引數Server獲取了響應頭中的Server值,結果是nginx,意思是伺服器是用Nginx搭建的。

利用最基本的urlopen()方法,可以完成最基本的簡單網頁的GET請求抓取。

如果想給連結傳遞一些引數,該怎麼實現呢?首先看一下urlopen()函式的API:

urllib.request.urlopen(url,data=None,[timeout,]*,cafile=None,capath=None,cadefault=False,context=None)

可以發現,除了第一個引數可以傳遞URL之外,我們還可以傳遞其他內容,比如data(附加資料)、timeout(超時時間)等。

下面我們詳細說明下這幾個引數的用法。

data引數

data引數是可選的。如果要新增該引數,並且如果它是位元組流編碼格式的內容,即bytes型別,則需要通過bytes()方法轉化。另外,如果傳遞了這個引數,則它的請求方式就不再是GET方式,而是POST方式。

下面用例項來看一下:

importurllib.parse
importurllib.request
data=bytes(urllib.parse.urlencode({'word':'hello'}),encoding='utf8')
response=urllib.request.urlopen('http://httpbin.org/post',data=data)
print(response.read())

這裡我們傳遞了一個引數word,值是hello。它需要被轉碼成bytes(位元組流)型別。其中轉位元組流採用了bytes()方法,該方法的第一個引數需要是str(字串)型別,需要用urllib.parse模組裡的urlencode()方法來將引數字典轉化為字串;第二個引數指定編碼格式,這裡指定為utf8。

這裡請求的站點是httpbin.org,它可以提供HTTP請求測試。本次我們請求的URL為http://httpbin.org/post,這個連結可以用來測試POST請求,它可以輸出請求的一些資訊,其中包含我們傳遞的data引數。

執行結果如下:

{
"args":{},"data":"","files":{},"form":{
"word":"hello"
},"headers":{
"Accept-Encoding":"identity","Content-Length":"10","Content-Type":"application/x-www-form-urlencoded","Host":"httpbin.org","User-Agent":"Python-urllib/3.5"
},"json":null,"origin":"123.124.23.253","url":"http://httpbin.org/post"
}

我們傳遞的引數出現在了form欄位中,這表明是模擬了表單提交的方式,以POST方式傳輸資料。

timeout引數

timeout引數用於設定超時時間,單位為秒,意思就是如果請求超出了設定的這個時間,還沒有得到響應,就會丟擲異常。如果不指定該引數,就會使用全域性預設時間。它支援HTTP、HTTPS、FTP請求。

下面用例項來看一下:

importurllib.request
response=urllib.request.urlopen('http://httpbin.org/get',timeout=1)
print(response.read())

執行結果如下:

Duringhandlingoftheaboveexception,anotherexceptionoccurred:
Traceback(mostrecentcalllast):File"/var/py/python/urllibtest.py",line4,in<module>response=urllib.
request.urlopen('http://httpbin.org/get',timeout=1)
...
urllib.error.URLError:<urlopenerrortimedout>

這裡我們設定超時時間是1秒。程式1秒過後,伺服器依然沒有響應,於是丟擲了URLError異常。該異常屬於urllib.error模組,錯誤原因是超時。

因此,可以通過設定這個超時時間來控制一個網頁如果長時間未響應,就跳過它的抓取。這可以利用try except語句來實現,相關程式碼如下:

importsocket
importurllib.request
importurllib.error
try:
response=urllib.request.urlopen('http://httpbin.org/get',timeout=0.1)
excepturllib.error.URLErrorase:
ifisinstance(e.reason,socket.timeout):
print('TIMEOUT')

這裡我們請求了http://httpbin.org/get測試連結,設定超時時間是0.1秒,然後捕獲了URLError異常,接著判斷異常是socket.timeout型別(意思就是超時異常),從而得出它確實是因為超時而報錯,列印輸出了TIME OUT。

執行結果如下:

TIMEOUT

按照常理來說,0.1秒內基本不可能得到伺服器響應,因此輸出了TIME OUT的提示。

通過設定timeout這個引數來實現超時處理,有時還是很有用的。

其他引數

除了data引數和timeout引數外,還有context引數,它必須是ssl.SSLContext型別,用來指定SSL設定。

此外,cafile和capath這兩個引數分別指定CA證書和它的路徑,這個在請求HTTPS連結時會有用。

cadefault引數現在已經棄用了,其預設值為False。

前面講解了urlopen()方法的用法,通過這個最基本的方法,我們可以完成簡單的請求和網頁抓取。若需更加詳細的資訊,可以參見官方文件:https://docs.python.org/3/library/urllib.request.html。

2. Request

我們知道利用urlopen()方法可以實現最基本請求的發起,但這幾個簡單的引數並不足以構建一個完整的請求。如果請求中需要加入Headers等資訊,就可以利用更強大的Request類來構建。

首先,我們用例項來感受一下Request的用法:

importurllib.request
request=urllib.request.Request('https://python.org')
response=urllib.request.urlopen(request)
print(response.read().decode('utf-8'))

可以發現,我們依然是用urlopen()方法來發送這個請求,只不過這次該方法的引數不再是URL,而是一個Request型別的物件。通過構造這個資料結構,一方面我們可以將請求獨立成一個物件,另一方面可更加豐富和靈活地配置引數。

下面我們看一下Request可以通過怎樣的引數來構造,它的構造方法如下:

classurllib.request.Request(url,headers={},origin_req_host=None,unverifiable=False,method=None)

第一個引數url用於請求URL,這是必傳引數,其他都是可選引數。

第二個引數data如果要傳,必須傳bytes(位元組流)型別的。如果它是字典,可以先用urllib.parse模組裡的urlencode()編碼。

第三個引數headers是一個字典,它就是請求頭,我們可以在構造請求時通過headers引數直接構造,也可以通過呼叫請求例項的add_header()方法新增。

新增請求頭最常用的用法就是通過修改User-Agent來偽裝瀏覽器,預設的User-Agent是Python-urllib,我們可以通過修改它來偽裝瀏覽器。比如要偽裝火狐瀏覽器,你可以把它設定為:

Mozilla/5.0(X11;U;Linuxi686)Gecko/20071127Firefox/2.0.0.11

第四個引數origin_req_host指的是請求方的host名稱或者IP地址。

第五個引數unverifiable表示這個請求是否是無法驗證的,預設是False,意思就是說使用者沒有足夠許可權來選擇接收這個請求的結果。例如,我們請求一個HTML文件中的圖片,但是我們沒有自動抓取影象的許可權,這時unverifiable的值就是True`。

第六個引數method是一個字串,用來指示請求使用的方法,比如GET、POST和PUT等。

下面我們傳入多個引數構建請求來看一下:

fromurllibimportrequest,parse
url='http://httpbin.org/post'
headers={
'User-Agent':'Mozilla/4.0(compatible;MSIE5.5;WindowsNT)','Host':'httpbin.org'
}
dict={
'name':'Germey'
}
data=bytes(parse.urlencode(dict),encoding='utf8')
req=request.Request(url=url,data=data,headers=headers,method='POST')
response=request.urlopen(req)
print(response.read().decode('utf-8'))

這裡我們通過4個引數構造了一個請求,其中url即請求URL,headers中指定了User-Agent和Host,引數data用urlencode()和bytes()方法轉成位元組流。另外,指定了請求方式為POST。

執行結果如下:

{
"args":{},"form":{
"name":"Germey"
},"Content-Length":"11","User-Agent":"Mozilla/4.0(compatible;MSIE5.5;WindowsNT)"
},"origin":"219.224.169.11","url":"http://httpbin.org/post"
}

觀察結果可以發現,我們成功設定了data、headers和method。

另外,headers也可以用add_header()方法來新增:

req=request.Request(url=url,method='POST')
req.add_header('User-Agent','Mozilla/4.0(compatible;MSIE5.5;WindowsNT)')

如此一來,我們就可以更加方便地構造請求,實現請求的傳送啦。

3. 高階用法

在上面的過程中,我們雖然可以構造請求,但是對於一些更高階的操作(比如Cookies處理、代理設定等),我們該怎麼辦呢?

接下來,就需要更強大的工具Handler登場了。簡而言之,我們可以把它理解為各種處理器,有專門處理登入驗證的,有處理Cookies的,有處理代理設定的。利用它們,我們幾乎可以做到HTTP請求中所有的事情。

首先,介紹一下urllib.request模組裡的BaseHandler類,它是所有其他Handler的父類,它提供了最基本的方法,例如default_open()、protocol_request()等。

接下來,就有各種Handler子類繼承這個BaseHandler類,舉例如下。

HTTPDefaultErrorHandler:用於處理HTTP響應錯誤,錯誤都會丟擲HTTPError型別的異常。

HTTPRedirectHandler:用於處理重定向。

HTTPCookieProcessor:用於處理Cookies。

ProxyHandler:用於設定代理,預設代理為空。

HTTPPasswordMgr:用於管理密碼,它維護了使用者名稱和密碼的表。

HTTPBasicAuthHandler:用於管理認證,如果一個連結開啟時需要認證,那麼可以用它來解決認證問題。

另外,還有其他的Handler類,這裡就不一一列舉了,詳情可以參考官方文件:https://docs.python.org/3/library/urllib.request.html#urllib.request.BaseHandler。

關於怎麼使用它們,現在先不用著急,後面會有例項演示。

另一個比較重要的類就是OpenerDirector,我們可以稱為Opener。我們之前用過urlopen()這個方法,實際上它就是urllib為我們提供的一個Opener。

那麼,為什麼要引入Opener呢?因為需要實現更高階的功能。之前使用的Request和urlopen()相當於類庫為你封裝好了極其常用的請求方法,利用它們可以完成基本的請求,但是現在不一樣了,我們需要實現更高階的功能,所以需要深入一層進行配置,使用更底層的例項來完成操作,所以這裡就用到了Opener。

Opener可以使用open()方法,返回的型別和urlopen()如出一轍。那麼,它和Handler有什麼關係呢?簡而言之,就是利用Handler來構建Opener。

下面用幾個例項來看看它們的用法。

驗證

有些網站在開啟時就會彈出提示框,直接提示你輸入使用者名稱和密碼,驗證成功後才能檢視頁面,如圖3-2所示。

e19acff10035925c1b34ff4cacb05a7.png

圖3-2 驗證頁面

那麼,如果要請求這樣的頁面,該怎麼辦呢?藉助HTTPBasicAuthHandler就可以完成,相關程式碼如下:

fromurllib.requestimportHTTPPasswordMgrWithDefaultRealm,HTTPBasicAuthHandler,build_opener
fromurllib.errorimportURLError
username='username'
password='password'
url='http://localhost:5000/'
p=HTTPPasswordMgrWithDefaultRealm()
p.add_password(None,url,username,password)
auth_handler=HTTPBasicAuthHandler(p)
opener=build_opener(auth_handler)
try:
result=opener.open(url)
html=result.read().decode('utf-8')
print(html)
exceptURLErrorase:
print(e.reason)

這裡首先例項化HTTPBasicAuthHandler物件,其引數是HTTPPasswordMgrWithDefaultRealm物件,它利用add_password()新增進去使用者名稱和密碼,這樣就建立了一個處理驗證的Handler。

接下來,利用這個Handler並使用build_opener()方法構建一個Opener,這個Opener在傳送請求時就相當於已經驗證成功了。

接下來,利用Opener的open()方法開啟連結,就可以完成驗證了。這裡獲取到的結果就是驗證後的頁面原始碼內容。

代理

在做爬蟲的時候,免不了要使用代理,如果要新增代理,可以這樣做:

fromurllib.errorimportURLError
fromurllib.requestimportProxyHandler,build_opener
proxy_handler=ProxyHandler({
'http':'http://127.0.0.1:9743','https':'https://127.0.0.1:9743'
})
opener=build_opener(proxy_handler)
try:
response=opener.open('https://www.baidu.com')
print(response.read().decode('utf-8'))
exceptURLErrorase:
print(e.reason)

這裡我們在本地搭建了一個代理,它執行在9743埠上。

這裡使用了ProxyHandler,其引數是一個字典,鍵名是協議型別(比如HTTP或者HTTPS等),鍵值是代理連結,可以新增多個代理。

然後,利用這個Handler及build_opener()方法構造一個Opener,之後傳送請求即可。

Cookies

Cookies的處理就需要相關的Handler了。

我們先用例項來看看怎樣將網站的Cookies獲取下來,相關程式碼如下:

importhttp.cookiejar,urllib.request
cookie=http.cookiejar.CookieJar()
handler=urllib.request.HTTPCookieProcessor(cookie)
opener=urllib.request.build_opener(handler)
response=opener.open('http://www.baidu.com')
foritemincookie:
print(item.name+"="+item.value)

首先,我們必須宣告一個CookieJar物件。接下來,就需要利用HTTPCookieProcessor來構建一個Handler,最後利用build_opener()方法構建出Opener,執行open()函式即可。

執行結果如下:

BAIDUID=2E65A683F8A8BA3DF521469DF8EFF1E1:FG=1
BIDUPSID=2E65A683F8A8BA3DF521469DF8EFF1E1
H_PS_PSSID=20987_1421_18282_17949_21122_17001_21227_21189_21161_20927
PSTM=1474900615
BDSVRTM=0
BD_HOME=0

可以看到,這裡輸出了每條Cookie的名稱和值。

不過既然能輸出,那可不可以輸出成檔案格式呢?我們知道Cookies實際上也是以文字形式儲存的。

答案當然是肯定的,這裡通過下面的例項來看看:

filename='cookies.txt'
cookie=http.cookiejar.MozillaCookieJar(filename)
handler=urllib.request.HTTPCookieProcessor(cookie)
opener=urllib.request.build_opener(handler)
response=opener.open('http://www.baidu.com')
cookie.save(ignore_discard=True,ignore_expires=True)

這時CookieJar就需要換成MozillaCookieJar,它在生成檔案時會用到,是CookieJar的子類,可以用來處理Cookies和檔案相關的事件,比如讀取和儲存Cookies,可以將Cookies儲存成Mozilla型瀏覽器的Cookies格式。

執行之後,可以發現生成了一個cookies.txt檔案,其內容如下:

#NetscapeHTTPCookieFile
#http://curl.haxx.se/rfc/cookie_spec.html
#Thisisageneratedfile!Donotedit.
.baidu.comTRUE/FALSE3622386254BAIDUID05AE39B5F56C1DEC474325CDA5
22D44F:FG=1
.baidu.comTRUE/FALSE3622386254BIDUPSID05AE39B5F56C1DEC474325CDA522D44F
.baidu.comTRUE/FALSEH_PS_PSSID19638_1453_17710_18240_21091_18560
_17001_21191_21161
.baidu.comTRUE/FALSE3622386254PSTM1474902606
www.baidu.comFALSE/FALSEBDSVRTM0
www.baidu.comFALSE/FALSEBD_HOME0

另外,LWPCookieJar同樣可以讀取和儲存Cookies,但是儲存的格式和MozillaCookieJar不一樣,它會儲存成libwww-perl(LWP)格式的Cookies檔案。

要儲存成LWP格式的Cookies檔案,可以在宣告時就改為:

cookie=http.cookiejar.LWPCookieJar(filename)

此時生成的內容如下:

#LWP-Cookies-2.0
Set-Cookie3:BAIDUID="0CE9C56F598E69DB375B7C294AE5C591:FG=1";path="/";domain=".baidu.com";path_spec;domain_dot;
expires="2084-10-1418:25:19Z";version=0
Set-Cookie3:BIDUPSID=0CE9C56F598E69DB375B7C294AE5C591;path="/";domain=".baidu.com";path_spec;domain_dot;
expires="2084-10-1418:25:19Z";version=0
Set-Cookie3:H_PS_PSSID=20048_1448_18240_17944_21089_21192_21161_20929;path="/";domain=".baidu.com";path_spec;
domain_dot;discard;version=0
Set-Cookie3:PSTM=1474902671;path="/";domain=".baidu.com";path_spec;domain_dot;expires="2084-10-1418:25:19Z";
version=0
Set-Cookie3:BDSVRTM=0;path="/";domain="www.baidu.com";path_spec;discard;version=0
Set-Cookie3:BD_HOME=0;path="/";domain="www.baidu.com";path_spec;discard;version=0

由此看來,生成的格式還是有比較大差異的。

那麼,生成了Cookies檔案後,怎樣從檔案中讀取並利用呢?

下面我們以LWPCookieJar格式為例來看一下:

cookie=http.cookiejar.LWPCookieJar()
cookie.load('cookies.txt',ignore_discard=True,ignore_expires=True)
handler=urllib.request.HTTPCookieProcessor(cookie)
opener=urllib.request.build_opener(handler)
response=opener.open('http://www.baidu.com')
print(response.read().decode('utf-8'))

可以看到,這裡呼叫load()方法來讀取本地的Cookies檔案,獲取到了Cookies的內容。不過前提是我們首先生成了LWPCookieJar格式的Cookies,並儲存成檔案,然後讀取Cookies之後使用同樣的方法構建Handler和Opener即可完成操作。

執行結果正常的話,會輸出百度網頁的原始碼。

通過上面的方法,我們可以實現絕大多數請求功能的設定了。

這便是urllib庫中request模組的基本用法,如果想實現更多的功能,可以參考官方文件的說明:https://docs.python.org/3/library/urllib.request.html#basehandler-objects。

到此這篇關於Python3爬蟲傳送請求的知識點例項的文章就介紹到這了,更多相關Python3爬蟲傳送請求內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!