參考:https://zhuanlan.zhihu.com/p/146016738
urllib庫的作用
爬蟲的第一個步驟是獲取網頁,urllib庫是用來實現這個功能:想服務器發送請求,得到服務器響應,獲取網頁的內容。
Python的強大在於提供了功能齊全的類庫,來幫助我們完成這個請求,通過調用urllib庫,我們不需要了解請求的數據結構,HTTP,TCP,IP層網絡傳輸同學,以及服務器應答原理等。
我們只需要關心以下三點,然后通過幾行調用urllib庫的代碼,就能夠獲得我們想要的網頁內容。
- 請求的URL是什么
- 傳遞的參數是什么
- 如何設置可選的請求頭
urllib庫的構成
在python2中,曾經有urllib和urllib2兩個庫來實現請求的發送,但目前通用的python3版本中,兩個庫的功能已經合並成一個庫,統一為urllib,它是python內置函數,不需要額外安裝即可使用。
urllib的四個模塊
【1】requset:HTTP請求模塊,可以用來模擬發送請求,只需要傳入URL及額外參數,就可以模擬瀏覽器訪問網頁的過程。
【2】error:異常處理模塊,檢測請求是否報錯,捕捉異常錯誤,進行重試或其他操作,保證程序不會終止。
【3】parse:工具模塊,提供許多URL處理方法,如拆分、解析、合並等。
【4】robotparser:識別網站的robots.txt文件,判斷哪些網站可以爬,哪些網站不可以爬,使用頻率較少。
發送請求
urlopen是request模塊中的方法,用於抓取網絡。
官方文檔:https://docs.python.org/3/library/urllib.request.html
我們以代碼示例,我們抓取百度的網頁
# 調用urllib庫中的request模塊 import urllib.request # 發送請求獲取百度網頁的響應 response = urllib.request.urlopen("http://www.baidu.com") # 打印響應內容 # read()是把響應對象內容全部讀取出來,讀取出來為bytes碼 # decode('utf-8')把bytes碼解碼 print(response.read().decode('utf-8'))
返回的結果比較多,隨便截取其中一部分,可以看出是百度的網頁HTML源代碼。
我們只用幾行代碼,就完成了百度的抓取,並打印了網頁的源代碼,接下來,我們看一看我們獲得的響應內容response到底是什么?利用type()方法來輸出響應的類型。
print(type(response)) # <class 'http.client.HTTPResponse'>
它是一個HTTPResponse類型的對象
包含方法:read()、readinto()、getheader()、getheaders()、fileno()等
包含屬性:msg、version、status、reason、debuglevel、closed等屬性。
通過調用以上的方法和屬性,就能返回我們所需的信息。
比如一開始我們打印獲取網頁內容時,就用到了read()方法。
調用status屬性則可以得到返回結果的狀態碼,200代表強求成功,404代表網頁未找到等。
再看一個代碼示例加深理解:
# 打印響應狀態碼 print(response.status,"\n") # 200 # 打印響應頭信息 print(response.getheaders(),"\n") # [('Bdpagetype', '1'), ('Bdqid', '0x90ae03e0000443e3'), ('Cache-Control', 'private'), ('Content-Type', 'text/html;charset=utf-8'), ('Date', 'Tue, 24 Aug 2021 01:18:50 GMT'), ('Expires', 'Tue, 24 Aug 2021 01:18:50 GMT'), ('P3p', 'CP=" OTI DSP COR IVA OUR IND COM "'), ('P3p', 'CP=" OTI DSP COR IVA OUR IND COM "'), ('Server', 'BWS/1.1'), ('Set-Cookie', 'BAIDUID=162948399648CA18CD24B2476E039F6B:FG=1; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com'), ('Set-Cookie', 'BIDUPSID=162948399648CA18CD24B2476E039F6B; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com'), ('Set-Cookie', 'PSTM=1629767930; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com'), ('Set-Cookie', 'BAIDUID=162948399648CA18CA2A9672599961C8:FG=1; max-age=31536000; expires=Wed, 24-Aug-22 01:18:50 GMT; domain=.baidu.com; path=/; version=1; comment=bd'), ('Set-Cookie', 'BDSVRTM=17; path=/'), ('Set-Cookie', 'BD_HOME=1; path=/'), ('Set-Cookie', 'H_PS_PSSID=34437_34441_31253_34004_34092_26350_34390; path=/; domain=.baidu.com'), ('Traceid', '1629767930035569306610425274448017114083'), ('Vary', 'Accept-Encoding'), ('Vary', 'Accept-Encoding'), ('X-Frame-Options', 'sameorigin'), ('X-Ua-Compatible', 'IE=Edge,chrome=1'), ('Connection', 'close'), ('Transfer-Encoding', 'chunked')] # 打印響應頭的Server值 print(response.getheader('Server'),"\n") # BWS/1.1
在打印的三行代碼中
第一行輸出了響應的狀態碼(200代表正常)
第二行輸出了響應的頭信息
第三行通過調用getheader()方法並傳遞參數Server,獲取了第二行響應頭信息中的Server對應的值BWS/1.1。
參數
利用基本的urlopen()方法可以完成最基本的簡單網頁GET方法抓取。
如果想達成更復雜一些的任務,需要給鏈接傳遞一些參數,該如何實現。
urlopen()的函數原型如下:
urllib.request.urlopen(url, data=None, [timeout, ]*, cafile=None, capath=None, cadefault=False, context=None)
除了第一個參數傳遞URL之外,我們還可以傳遞其他參數,比如data(附加數據),timeout(超時時間)等。
data參數
data用來指明往服務器請求中的額外參數信息,data默認是None,此時以GET方式發送請求;當用戶給出data參數的時候,改為POST方式發送請求。
data參數是可選的,如果需要添加該參數,需要使用bytes()方法將參數轉化為二進制數據。
還是通過代碼理解
首先了解模塊urllib.parse.urlencode的用法
urllib.parse.urlencode用於把一個字典轉換成對應的str
舉例說明
# urllib.parse.urlencode把字典轉換成字符串str # 如果字典包含多個元素則之間使用&分隔 import urllib.parse print(urllib.parse.urlencode({"word":"hello"})) # word=hello print(urllib.parse.urlencode({"word":"hello","word1":"hello1"})) # word=hello&word1=hello1
下面代碼演示POST方法
import urllib.parse import urllib.request # urllib.parse.urlencode({'word':'hello'})把字典轉換為字符串 "word=hello" # bytes("word=hello",encoding='utf-8')把字符串轉換成二進制b"word=hello" data = bytes(urllib.parse.urlencode({'word':'hello'}),encoding='utf-8') # 以下方法和上面轉換的結果一致 # data = urllib.parse.urlencode({'word1':'hello'}).encode('utf-8') response = urllib.request.urlopen("http://httpbin.org/post",data=data) print(response.read().decode('utf-8'))
首頁這里請求的站點是http://httpbin.org,它是一個HTTP請求測試網站,記住它后面舉例會經常用到它。
這次我們要使用post方式請求,而不是get,因為post是需要攜帶表單信息的(類似登陸的用戶名和密碼)所以我們要在urlopen函數中傳遞data參數。
前面講了data參數必須是bytes型。
bytes()這個方法第一個參數需要str(字符串類型),這里就需要用到urllib庫的parse模塊里的urlencode()方法來將參數字典轉化為字符串。第二個參數是 指定編碼格式,這里指定為utf-8。
運行結果如下:
{ "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.6", "X-Amzn-Trace-Id": "Root=1-6124521e-0844c17c161a90e06ad543c0" }, "json": null, "origin": "116.25.236.109", "url": "http://httpbin.org/post" }
該網頁通過POST傳遞了表單數據,在form內輸出一個字典格式,如果傳遞字典有多個元素則form下也對應多個元素。
可以看到,我們傳遞的參數data中的字典鍵值對"word":"hello"出現在了form字段中,這表明了表單提交的方式,以POST方式傳輸數據。
timeout參數
timeout參數用於設置超時時間,單位為秒,意思是如果請求時間超過了設置的時間,還沒有得到響應,就會拋出異常,在實際爬蟲中,我們要對許多URL發起請求,中途肯定會出現爬取異常的URL,短時間無法獲得響應,我們需要識別出這種異常,就需要用到timeout參數。
如果不指定timeout參數,會使用全局默認時間。它支持HTTP、HTTPS、FTP請求。
通過代碼示例理解:
# 調用urllib庫中的request模塊 import urllib.request response = urllib.request.urlopen("http://httpbin.org/get",timeout=0.01) print(response.read().decode('utf-8'))
這里我們把timeout參數設成0.01秒,網頁反應時間是沒這么快的,所以一定會報錯,我們來看一下報錯的信息:
urllib.error.URLError: <urlopen error timed out>
顯示異常屬於urllib.error模塊,錯誤原因是超時。
在實際爬蟲中,我們可以用過設置這個超時時間來控制一個網頁在長時間未響應時,就跳過它的抓取。這可以利用try except 語句來實現。
代碼示例:
import socket import urllib.request try: response = urllib.request.urlopen("http://httpbin.org/get",timeout=0.01) print(response.read().decode('utf-8')) except urllib.error.URLError as e : if isinstance(e.reason,socket.timeout): print("time out !")
在代碼中加入try except 語句后,如果響應超時,便會跳過抓取,我們捕獲了URLError異常后,接着判斷異常是socket.timeout類型(意思就是超時異常),於是打印出了time out !
輸出結果如下:
time out !
其他參數(少用)
context參數:它必須是ssl.SSLContext類型,用來指定SSL設置,實現SSL加密傳輸。
cafile、capath:分別指定CA證書和它的路徑,用於實現可信任的CA證書的HTTP請求。
cadefault:已經棄用,默認False。
2,Request
上文已經講解了如何利用urlopen()方法實現最基本的請求發起。但這幾個簡單的參數並不足以構建一個完整的請求(過於簡單的請求會被瀏覽器識別為爬蟲而拒絕響應)。如果請求中需要加入Headers等信息,就需要用到Requset類來構建。
同樣通過實例展示Request用法:
# 展示Request import urllib.request request = urllib.request.Request('http://httpbin.org/get') response = urllib.request.urlopen(request) print(response.read().decode('utf-8'))
我們依然是用urlopen()方法來發送請求,只是這次的參數不在是url,而是一個Request類型對象。通過構造這個數據類型,我們可以將請求獨立為一個對象,並且更靈活的配置參數。
Reque的函數原型如下:
class urllib.request.Request(url,data = None,headers ={ },origin_req_host =None,unverifiable = False,method = None)
下面來看它的具體參數:
url:用於請求的URL,這是畢傳參數,其他都是可選參數
data:必須是bytes類型,如果它是字典,可以先用urllib.parse模塊里的urlencode方法編碼(用於POST請求)
headers:是一個字典,它就是請求頭,我們可以在構造請求時,通過headers參數直接構造,也可以通過調用請求示例的add_header()方法添加
origin_req_host:指的是請求方的host名稱或IP地址。
unverifiable:表示這個請求是否無法驗證,默認為False,意思是說用戶沒有足夠權限來選擇接收這個請求的結果。比如我們請求一個HTML文檔中的圖片,但是我們沒有自助抓取圖象的權限,這是unverifiable的值就是True。
method:是一個字符串,用來指示請求使用方法,如GET、POST、PUT等。
# Request請求多個參數 import urllib.request,urllib.parse url = "http://httpbin.org/post" headers = { "User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3314.0 Safari/537.36 SE 2.X MetaSr 1.0", "Host":"httpbin.org" } dict = {'name':'Germey'} data = bytes(urllib.parse.urlencode(dict),encoding='utf-8') req = urllib.request.Request(url=url,data=data,headers=headers,method='POST') response = urllib.request.urlopen(req) print(response.read().decode('utf-8'))
在這個示例中,我們通過4個參數構造了一個請求,我們加入了url、data、headers、method,其中最重要的是把頭信息(包含User-Agent和Host)寫入了 Request,讓請求變得更完整。
運行結果如下:
{ "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/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3314.0 Safari/537.36 SE 2.X MetaSr 1.0", "X-Amzn-Trace-Id": "Root=1-61246460-1f4d152f11557e9f6670f33a" }, "json": null, "origin": "116.25.236.109", "url": "http://httpbin.org/post" }
可以看到,我們成功通過新建的參數,設置了data、headers 和 method。
另外之前提到的headers也可以用add_header()方法添加,示例代碼如下:
req = urllib.request.Request(url=url,data=data,method='POST') req.add_header('User-Agent',"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3314.0 Safari/537.36 SE 2.X MetaSr 1.0")
傳遞參數格式為key value格式