在爬蟲入門系列(一):快速理解HTTP協(xié)議中介紹了 HTTP 協(xié)議蔬墩,Python 提供了很多模塊來基于 HTTP 協(xié)議的網(wǎng)絡(luò)編程译打,urllib、urllib2拇颅、urllib3奏司、httplib、httplib2樟插,都是和 HTTP 相關(guān)的模塊韵洋,看名字覺得很反人類竿刁,更糟糕的是這些模塊在 Python2 與 Python3 中有很大的差異,如果業(yè)務(wù)代碼要同時(shí)兼容 2 和 3搪缨,寫起來會(huì)讓人崩潰食拜。
幸運(yùn)地是,繁榮的 Python 社區(qū)給開發(fā)者帶來了一個(gè)非常驚艷的 HTTP 庫 requests副编,一個(gè)真正給人用的HTTP庫负甸。它是 GitHUb 關(guān)注數(shù)最多的 Python 項(xiàng)目之一,requests 的作者是 Kenneth Reitz 大神痹届。
requests 實(shí)現(xiàn)了 HTTP 協(xié)議中絕大部分功能呻待,它提供的功能包括 Keep-Alive、連接池队腐、Cookie持久化蚕捉、內(nèi)容自動(dòng)解壓、HTTP代理柴淘、SSL認(rèn)證迫淹、連接超時(shí)、Session等很多特性为严,最重要的是它同時(shí)兼容 python2 和 python3敛熬。
快速入門
requests 的安裝可以直接使用 pip 方法:pip install requests
>>> import requests
# GET 請(qǐng)求
>>> response = requests.get("https://foofish.net")
返回的時(shí) Response 對(duì)象,Response 對(duì)象是 對(duì) HTTP 協(xié)議中服務(wù)端返回給瀏覽器的響應(yīng)數(shù)據(jù)的封裝梗脾,響應(yīng)的中的主要元素包括:狀態(tài)碼、原因短語盹靴、響應(yīng)首部炸茧、響應(yīng)體等等,這些屬性都封裝在Response 對(duì)象中稿静。
# 狀態(tài)碼
>>> response.status_code
200
# 原因短語
>>> response.reason
'OK'
# 響應(yīng)首部
>>> for name,value in response.headers.items():
... print("%s:%s" % (name, value))
...
Content-Encoding:gzip
Server:nginx/1.10.2
Date:Thu, 06 Apr 2017 16:28:01 GMT
# 響應(yīng)內(nèi)容
>>> response.content
'<html><body>此處省略一萬字...</body></html>
requests 除了支持 GET 請(qǐng)求外梭冠,還支持 HTTP 規(guī)范中的其它所有方法,包括 POST改备、PUT控漠、DELTET、HEADT悬钳、OPTIONS方法盐捷。
>>> r = requests.post('http://httpbin.org/post', data = {'key':'value'})
>>> r = requests.put('http://httpbin.org/put', data = {'key':'value'})
>>> r = requests.delete('http://httpbin.org/delete')
>>> r = requests.head('http://httpbin.org/get')
>>> r = requests.options('http://httpbin.org/get')
構(gòu)建請(qǐng)求查詢參數(shù)
很多URL都帶有很長(zhǎng)一串參數(shù),我們稱這些參數(shù)為URL的查詢參數(shù)默勾,用"?"附加在URL鏈接后面碉渡,多個(gè)參數(shù)之間用"&"隔開,比如:http://fav.foofish.net/?p=4&s=20 母剥,現(xiàn)在你可以用字典來構(gòu)建查詢參數(shù):
>>> args = {"p": 4, "s": 20}
>>> response = requests.get("http://fav.foofish.net", params = args)
>>> response.url
'http://fav.foofish.net/?p=4&s=2'
構(gòu)建請(qǐng)求首部 Headers
requests 可以很簡(jiǎn)單地指定請(qǐng)求首部字段 Headers滞诺,比如有時(shí)要指定 User-Agent 偽裝成瀏覽器發(fā)送請(qǐng)求形导,以此來蒙騙服務(wù)器。直接傳遞一個(gè)字典對(duì)象給參數(shù) headers 即可习霹。
>>> r = requests.get(url, headers={'user-agent': 'Mozilla/5.0'})
構(gòu)建 POST 請(qǐng)求數(shù)據(jù)
requests 可以非常靈活地構(gòu)建 POST 請(qǐng)求需要的數(shù)據(jù)朵耕,如果服務(wù)器要求發(fā)送的數(shù)據(jù)是表單數(shù)據(jù),則可以指定關(guān)鍵字參數(shù) data淋叶,如果要求傳遞 json 格式字符串參數(shù)阎曹,則可以使用json關(guān)鍵字參數(shù),參數(shù)的值都可以字典的形式傳過去爸吮。
作為表單數(shù)據(jù)傳輸給服務(wù)器
>>> payload = {'key1': 'value1', 'key2': 'value2'}
>>> r = requests.post("http://httpbin.org/post", data=payload)
作為 json 格式的字符串格式傳輸給服務(wù)器
>>> import json
>>> url = 'http://httpbin.org/post'
>>> payload = {'some': 'data'}
>>> r = requests.post(url, json=payload)
Response中的響應(yīng)體
HTTP返回的響應(yīng)消息中很重要的一部分內(nèi)容是響應(yīng)體芬膝,響應(yīng)體在 requests 中處理非常靈活,與響應(yīng)體相關(guān)的屬性有:content形娇、text锰霜、json()。
content 是 byte 類型桐早,適合直接將內(nèi)容保存到文件系統(tǒng)或者傳輸?shù)骄W(wǎng)絡(luò)中
>>> r = requests.get("https://pic1.zhimg.com/v2-2e92ebadb4a967829dcd7d05908ccab0_b.jpg")
>>> type(r.content)
<class 'bytes'>
# 另存為 test.jpg
>>> with open("test.jpg", "wb") as f:
... f.write(r.content)
text 是 str 類型癣缅,比如一個(gè)普通的 HTML 頁面,需要對(duì)文本進(jìn)一步分析時(shí)哄酝,使用 text友存。
>>> r = requests.get("https://foofish.net/understand-http.html")
>>> type(r.text)
<class 'str'>
>>> re.compile('xxx').findall(r.text)
如果使用第三方開放平臺(tái)或者API接口爬取數(shù)據(jù)時(shí),返回的內(nèi)容是json格式的數(shù)據(jù)時(shí)陶衅,那么可以直接使用json()方法返回一個(gè)經(jīng)過json.loads()處理后的對(duì)象屡立。
>>> r = requests.get('https://www.v2ex.com/api/topics/hot.json')
>>> r.json()
[{'id': 352833, 'title': '在長(zhǎng)沙,父母同住...
代理設(shè)置
當(dāng)爬蟲頻繁地對(duì)服務(wù)器進(jìn)行抓取內(nèi)容時(shí)搀军,很容易被服務(wù)器屏蔽掉膨俐,因此要想繼續(xù)順利的進(jìn)行爬取數(shù)據(jù),使用代理是明智的選擇罩句。如果你想爬取墻外的數(shù)據(jù)焚刺,同樣設(shè)置代理可以解決問題,requests 完美支持代理门烂。
import requests
proxies = {
'http': 'http://10.10.1.10:3128',
'https': 'http://10.10.1.10:1080',
}
requests.get('http://example.org', proxies=proxies)
超時(shí)設(shè)置
requests 發(fā)送請(qǐng)求時(shí)乳愉,默認(rèn)請(qǐng)求下線程一直阻塞,直到有響應(yīng)返回才處理后面的邏輯屯远。如果遇到服務(wù)器沒有響應(yīng)的情況時(shí)蔓姚,問題就變得很嚴(yán)重了,它將導(dǎo)致整個(gè)應(yīng)用程序一直處于阻塞狀態(tài)而沒法處理其他請(qǐng)求慨丐。
>>> import requests
>>> r = requests.get("http://www.google.coma")
...一直阻塞中
正確的方式的是給每個(gè)請(qǐng)求顯示地指定一個(gè)超時(shí)時(shí)間赂乐。
>>> r = requests.get("http://www.google.coma", timeout=5)
5秒后報(bào)錯(cuò)
Traceback (most recent call last):
socket.timeout: timed out
Session
在爬蟲入門系列(一):快速理解HTTP協(xié)議中介紹過HTTP協(xié)議是一中無狀態(tài)的協(xié)議,為了維持客戶端與服務(wù)器之間的通信狀態(tài)咖气,使用 Cookie 技術(shù)使之保持雙方的通信狀態(tài)挨措。
有些網(wǎng)頁是需要登錄才能進(jìn)行爬蟲操作的挖滤,而登錄的原理就是瀏覽器首次通過用戶名密碼登錄之后,服務(wù)器給客戶端發(fā)送一個(gè)隨機(jī)的Cookie浅役,下次瀏覽器請(qǐng)求其它頁面時(shí)斩松,就把剛才的 cookie 隨著請(qǐng)求一起發(fā)送給服務(wù)器,這樣服務(wù)器就知道該用戶已經(jīng)是登錄用戶觉既。
import requests
# 構(gòu)建會(huì)話
session = requests.Session()
# 登錄url
session.post(login_url, data={username, password})
# 登錄后才能訪問的url
r = session.get(home_url)
session.close()
構(gòu)建一個(gè)session會(huì)話之后惧盹,客戶端第一次發(fā)起請(qǐng)求登錄賬戶,服務(wù)器自動(dòng)把cookie信息保存在session對(duì)象中瞪讼,發(fā)起第二次請(qǐng)求時(shí)requests 自動(dòng)把session中的cookie信息發(fā)送給服務(wù)器钧椰,使之保持通信狀態(tài)。
項(xiàng)目實(shí)戰(zhàn)
最后是一個(gè)實(shí)戰(zhàn)項(xiàng)目符欠,如何用 requests 實(shí)現(xiàn)知乎自動(dòng)登錄并給用戶發(fā)私信嫡霞,我會(huì)在下一篇文章中進(jìn)行講解。
延伸閱讀:
- Python實(shí)現(xiàn)知乎自動(dòng)登錄:https://foofish.net/python-auto-login-zhihu.html
- requests文檔:http://docs.python-requests.org/en/master/
- 如何閱讀 requests 源碼: https://www.slideshare.net/onceuponatimeforever/lets-read-code-pythonrequests-library?qid=9f3099df-4c9e-419a-ae62-b601f55b39f3&v=&b=&from_search=3