使用urllib的request
模塊,咱們能夠方便地實現請求的發送並獲得響應,本節就來看下它的具體用法。html
urllib.request
模塊提供了最基本的構造HTTP請求的方法,利用它能夠模擬瀏覽器的一個請求發起過程,同時它還帶有處理受權驗證(authenticaton)、重定向(redirection)、瀏覽器Cookies以及其餘內容。python
下面咱們來看一下它的強大之處。這裏以Python官網爲例,咱們來把這個網頁抓下來:nginx
import urllib.request
response = urllib.request.urlopen('https://www.python.org')
print(response.read().decode('utf-8'))
複製代碼
運行結果如圖3-1所示。json
圖3-1 運行結果瀏覽器
這裏咱們只用了兩行代碼,便完成了Python官網的抓取,輸出了網頁的源代碼。獲得源代碼以後呢?咱們想要的連接、圖片地址、文本信息不就均可以提取出來了嗎?bash
接下來,看看它返回的究竟是什麼。利用type()
方法輸出響應的類型:服務器
import urllib.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
等屬性。cookie
獲得這個對象以後,咱們把它賦值爲response
變量,而後就能夠調用這些方法和屬性,獲得返回結果的一系列信息了。網絡
例如,調用read()
方法能夠獲得返回的網頁內容,調用status
屬性能夠獲得返回結果的狀態碼,如200表明請求成功,404表明網頁未找到等。
下面再經過一個實例來看看:
import urllib.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', 'GNU Terry Pratchett'), ('Content-Length', '47397'), ('Accept-Ranges', 'bytes'), ('Date', 'Mon, 01 Aug 2016 09:57:31 GMT'), ('Via', '1.1 varnish'), ('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
參數是可選的。若是要添加該參數,而且若是它是字節流編碼格式的內容,即bytes
類型,則須要經過bytes()
方法轉化。另外,若是傳遞了這個參數,則它的請求方式就再也不是GET方式,而是POST方式。
下面用實例來看一下:
import urllib.parse
import urllib.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爲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
參數用於設置超時時間,單位爲秒,意思就是若是請求超出了設置的這個時間,尚未獲得響應,就會拋出異常。若是不指定該參數,就會使用全局默認時間。它支持HTTP、HTTPS、FTP請求。
下面用實例來看一下:
import urllib.request
response = urllib.request.urlopen('http://httpbin.org/get', timeout=1)
print(response.read())
複製代碼
運行結果以下:
During handling of the above exception, another exception occurred:
Traceback (most recent call last): File "/var/py/python/urllibtest.py", line 4, in <module> response = urllib.request.urlopen('http://httpbin.org/get', timeout=1)
...
urllib.error.URLError: <urlopen error timed out>
複製代碼
這裏咱們設置超時時間是1秒。程序1秒事後,服務器依然沒有響應,因而拋出了URLError
異常。該異常屬於urllib.error
模塊,錯誤緣由是超時。
所以,能夠經過設置這個超時時間來控制一個網頁若是長時間未響應,就跳過它的抓取。這能夠利用try except
語句來實現,相關代碼以下:
import socket
import urllib.request
import urllib.error
try:
response = urllib.request.urlopen('http://httpbin.org/get', timeout=0.1)
except urllib.error.URLError as e:
if isinstance(e.reason, socket.timeout):
print('TIME OUT')
複製代碼
這裏咱們請求了httpbin.org/get測試連接,設置超時時間是0.1秒,而後捕獲了URLError
異常,接着判斷異常是socket.timeout
類型(意思就是超時異常),從而得出它確實是由於超時而報錯,打印輸出了TIME OUT
。
運行結果以下:
TIME OUT
複製代碼
按照常理來講,0.1秒內基本不可能獲得服務器響應,所以輸出了TIME OUT
的提示。
經過設置timeout
這個參數來實現超時處理,有時仍是頗有用的。
除了data
參數和timeout
參數外,還有context
參數,它必須是ssl.SSLContext
類型,用來指定SSL設置。
此外,cafile
和capath
這兩個參數分別指定CA證書和它的路徑,這個在請求HTTPS連接時會有用。
cadefault
參數如今已經棄用了,其默認值爲False
。
前面講解了urlopen()
方法的用法,經過這個最基本的方法,咱們能夠完成簡單的請求和網頁抓取。若需更加詳細的信息,能夠參見官方文檔:docs.python.org/3/library/u…。
咱們知道利用urlopen()
方法能夠實現最基本請求的發起,但這幾個簡單的參數並不足以構建一個完整的請求。若是請求中須要加入Headers等信息,就能夠利用更強大的Request
類來構建。
首先,咱們用實例來感覺一下Request
的用法:
import urllib.request
request = urllib.request.Request('https://python.org')
response = urllib.request.urlopen(request)
print(response.read().decode('utf-8'))
複製代碼
能夠發現,咱們依然是用urlopen()
方法來發送這個請求,只不過此次該方法的參數再也不是URL,而是一個Request
類型的對象。經過構造這個數據結構,一方面咱們能夠將請求獨立成一個對象,另外一方面可更加豐富和靈活地配置參數。
下面咱們看一下Request
能夠經過怎樣的參數來構造,它的構造方法以下:
class urllib.request.Request(url, data=None, 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; Linux i686) Gecko/20071127 Firefox/2.0.0.11
origin_req_host
指的是請求方的host名稱或者IP地址。unverifiable
表示這個請求是不是沒法驗證的,默認是False
,意思就是說用戶沒有足夠權限來選擇接收這個請求的結果。例如,咱們請求一個HTML文檔中的圖片,可是咱們沒有自動抓取圖像的權限,這時unverifiable的值就是
True`。method
是一個字符串,用來指示請求使用的方法,好比GET、POST和PUT等。下面咱們傳入多個參數構建請求來看一下:
from urllib import request, parse
url = 'http://httpbin.org/post'
headers = {
'User-Agent': 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)',
'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": {},
"data": "",
"files": {},
"form": {
"name": "Germey"
},
"headers": {
"Accept-Encoding": "identity",
"Content-Length": "11",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "httpbin.org",
"User-Agent": "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)"
},
"json": null,
"origin": "219.224.169.11",
"url": "http://httpbin.org/post"
}
複製代碼
觀察結果能夠發現,咱們成功設置了data
、headers
和method
。
另外,headers
也能夠用add_header()
方法來添加:
req = request.Request(url=url, data=data, method='POST')
req.add_header('User-Agent', 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)')
複製代碼
如此一來,咱們就能夠更加方便地構造請求,實現請求的發送啦。
在上面的過程當中,咱們雖然能夠構造請求,可是對於一些更高級的操做(好比Cookies處理、代理設置等),咱們該怎麼辦呢?
接下來,就須要更強大的工具Handler登場了。簡而言之,咱們能夠把它理解爲各類處理器,有專門處理登陸驗證的,有處理Cookies的,有處理代理設置的。利用它們,咱們幾乎能夠作到HTTP請求中全部的事情。
首先,介紹一下urllib.request
模塊裏的BaseHandler
類,它是全部其餘Handler
的父類,它提供了最基本的方法,例如default_open()
、protocol_request()
等。
接下來,就有各類Handler
子類繼承這個BaseHandler
類,舉例以下。
HTTPDefaultErrorHandler
:用於處理HTTP響應錯誤,錯誤都會拋出HTTPError
類型的異常。HTTPRedirectHandler
:用於處理重定向。HTTPCookieProcessor
:用於處理Cookies。ProxyHandler
:用於設置代理,默認代理爲空。HTTPPasswordMgr
:用於管理密碼,它維護了用戶名和密碼的表。HTTPBasicAuthHandler
:用於管理認證,若是一個連接打開時須要認證,那麼能夠用它來解決認證問題。另外,還有其餘的Handler
類,這裏就不一一列舉了,詳情能夠參考官方文檔:docs.python.org/3/library/u…。
關於怎麼使用它們,如今先不用着急,後面會有實例演示。
另外一個比較重要的類就是OpenerDirector
,咱們能夠稱爲Opener
。咱們以前用過urlopen()
這個方法,實際上它就是urllib爲咱們提供的一個Opener
。
那麼,爲何要引入Opener
呢?由於須要實現更高級的功能。以前使用的Request
和urlopen()
至關於類庫爲你封裝好了極其經常使用的請求方法,利用它們能夠完成基本的請求,可是如今不同了,咱們須要實現更高級的功能,因此須要深刻一層進行配置,使用更底層的實例來完成操做,因此這裏就用到了Opener
。
Opener
可使用open()
方法,返回的類型和urlopen()
一模一樣。那麼,它和Handler
有什麼關係呢?簡而言之,就是利用Handler
來構建Opener
。
下面用幾個實例來看看它們的用法。
有些網站在打開時就會彈出提示框,直接提示你輸入用戶名和密碼,驗證成功後才能查看頁面,如圖3-2所示。
圖3-2 驗證頁面
那麼,若是要請求這樣的頁面,該怎麼辦呢?藉助HTTPBasicAuthHandler
就能夠完成,相關代碼以下:
from urllib.request import HTTPPasswordMgrWithDefaultRealm, HTTPBasicAuthHandler, build_opener
from urllib.error import URLError
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)
except URLError as e:
print(e.reason)
複製代碼
這裏首先實例化HTTPBasicAuthHandler
對象,其參數是HTTPPasswordMgrWithDefaultRealm
對象,它利用add_password()
添加進去用戶名和密碼,這樣就創建了一個處理驗證的Handler
。
接下來,利用這個Handler
並使用build_opener()
方法構建一個Opener
,這個Opener
在發送請求時就至關於已經驗證成功了。
接下來,利用Opener
的open()
方法打開連接,就能夠完成驗證了。這裏獲取到的結果就是驗證後的頁面源碼內容。
在作爬蟲的時候,免不了要使用代理,若是要添加代理,能夠這樣作:
from urllib.error import URLError
from urllib.request import ProxyHandler, 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'))
except URLError as e:
print(e.reason)
複製代碼
這裏咱們在本地搭建了一個代理,它運行在9743端口上。
這裏使用了ProxyHandler
,其參數是一個字典,鍵名是協議類型(好比HTTP或者HTTPS等),鍵值是代理連接,能夠添加多個代理。
而後,利用這個Handler及build_opener()
方法構造一個Opener
,以後發送請求便可。
Cookies的處理就須要相關的Handler
了。
咱們先用實例來看看怎樣將網站的Cookies獲取下來,相關代碼以下:
import http.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')
for item in cookie:
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文件,其內容以下:
# Netscape HTTP Cookie File
# http://curl.haxx.se/rfc/cookie_spec.html
# This is a generated file! Do not edit.
.baidu.com TRUE / FALSE 3622386254 BAIDUID 05AE39B5F56C1DEC474325CDA522D44F:FG=1
.baidu.com TRUE / FALSE 3622386254 BIDUPSID 05AE39B5F56C1DEC474325CDA522D44F
.baidu.com TRUE / FALSE H_PS_PSSID 19638_1453_17710_18240_21091_18560_17001_21191_21161
.baidu.com TRUE / FALSE 3622386254 PSTM 1474902606
www.baidu.com FALSE / FALSE BDSVRTM 0
www.baidu.com FALSE / FALSE BD_HOME 0
複製代碼
另外,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-14 18:25:19Z"; version=0
Set-Cookie3: BIDUPSID=0CE9C56F598E69DB375B7C294AE5C591; path="/"; domain=".baidu.com"; path_spec; domain_dot; expires="2084-10-14 18: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-14 18: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
模塊的基本用法,若是想實現更多的功能,能夠參考官方文檔的說明:docs.python.org/3/library/u…。
本資源首發於崔慶才的我的博客靜覓: Python3網絡爬蟲開發實戰教程 | 靜覓
如想了解更多爬蟲資訊,請關注個人我的微信公衆號:進擊的Coder
weixin.qq.com/r/5zsjOyvEZ… (二維碼自動識別)