Python爬蟲系列(三):requests高級耍法
昨天,我們更多的討論了request的基礎API,讓我們對它有了基礎的認知。學會上一課程,我們已經能寫點基本的爬蟲了。但是還不夠,因為,很多站點是需要登錄的,在站點的各個請求之間,是需要保持回話狀態的,有的站點還需要證書驗證,等等這一系列的問題,我們將在今天這一環節,加以討論。
1.會話對象
會話:session,就是你點進這個站點後,由瀏覽器與伺服器之間保持的一次連接。這次連接裡面,你跳轉頁面,或發起其他請求,伺服器要求某些數據驗證。伺服器不會叫你在每次跳轉時候進行驗證,而是用已驗證的結果進行跳轉。這樣就節省伺服器資源,底層的TCP連接也會被重用。
跨請求保持數據(客戶端存數據):
s = requests.Session()
s.get(http://httpbin.org/cookies/set/sessioncookie/123456789)r = s.get("http://httpbin.org/cookies")
print(r.text)回話提供請求默認數據(數據會被存到服務端):
s = requests.Session()
s.auth = (user, pass)s.headers.update({x-test: true})# x-test 和 x-test2 一起發送給urls.get(http://httpbin.org/headers, headers={x-test2: true})注意:即便使用了session,方法級別的參數,仍然不會再跨請求保持。
以下代碼,另個請求分別有自己的cookies
s = requests.Session()
r = s.get(http://httpbin.org/cookies)print(r.text)# {"cookies": {}}r = s.get(http://httpbin.org/cookies, cookies={from-my: browser})print(r.text)# {"cookies": {"from-my": "browser"}}會話上下文管理器:是指用with 塊限定會話對象的使用範圍
with requests.Session() as s:
s.get(http://httpbin.org/cookies/set/sessioncookie/123456789)
2.請求與響應對象
任何時候,我們往伺服器發消息,都會返回一個response的響應對象,同時,還能獲得我們自己創建的request對象
r = requests.get(http://en.wikipedia.org/wiki/Monty_Python)
print (r.headers)print (r.request.headers)在發送請求之前,需要對body獲header做一些額外處理,使用如下方法:
from requests import Request, Session
s = Session()req = Request(GET, url,data=data,
headers=header )#要想獲取有狀態的請求,使用:
prepped = s.prepare_request(req)
而不是使用:
prepped = req.prepare()# do something with prepped.body# do something with prepped.headersresp = s.send(prepped,stream=stream,
verify=verify, proxies=proxies, cert=cert, timeout=timeout )print(resp.status_code)3.SSL 證書驗證
requests.get(https://requestb.in)
requests.get(https://github.com, verify=True)requests.get(https://github.com, verify=/path/to/certfile)# 將驗證路徑保持在會話中
s = requests.Session()s.verify = /path/to/certfile# 忽略對SSL的驗證requests.get(https://kennethreitz.org, verify=False)4.客戶端證書
requests.get(https://kennethreitz.org, cert=(/path/client.cert, /path/client.key))
#證書在會話中
s = requests.Session()
s.cert = /path/client.cert注意:本地證書的私有 key 必須是解密狀態。目前,Requests 不支持使用加密的 key。
這裡補充一句:當登錄12306時喊你要安裝證書。當向有證書驗證要求的站點,就使用上面的代碼
http://5.CA 證書 科普:
Requests 默認附帶了一套它信任的根證書,來自於 Mozilla trust store。然而它們在每次 Requests 更新時才會更新。這意味著如果你固定使用某一版本的 Requests,你的證書有可能已經 太舊了。
從 Requests 2.4.0 版之後,如果系統中裝了 certifi 包,Requests 會試圖使用它裡邊的 證書。這樣用戶就可以在不修改代碼的情況下更新他們的可信任證書。
6.響應體內容工作流
import requests
# 默認情況下,發起請求會同時下載響應頭和響應體(就是響應內容)tarball_url = https://github.com/kennethreitz/requests/tarball/master# 如果將stream=True 則會推遲響應內容的下載r = requests.get(tarball_url, stream=True)# 這裡就是:滿足某種條件才去下載if int(r.headers[content-length]) < TOO_LONG:content = r.content
# 在請求中把 stream 設為 True,Requests 無法將連接釋放回連接池,# 除非消耗了所有的數據,或者調用了 Response.close。# 這樣會帶來連接效率低下的問題。如果在使用 stream=True 的同時還在部分讀取請求的 body(或者完全沒有讀取 body),# 那麼就應該使用 with 語句發送請求,這樣可以保證請求一定會被關閉with requests.get(http://httpbin.org/get, stream=True) as r: # 這裡處理響應 content = r.content7.保持活動狀態(持久連接)科普
同一會話內的持久連接是完全自動處理的,同一會話內你發出的任何請求都會自動復用恰當的連接。
注意:只有所有的響應體數據被讀取完畢連接才會被釋放回連接池;所以確保將 stream 設置為 False 或讀取 Response 對象的 content 屬性。
8.流式上傳
Requests支持流式上傳,這允許發送大的數據流或文件,而無需先把它們讀入內存。要使用流式上傳,需,為請求體,提供一個類文件對象即可:
with open(massive-body) as f:
requests.post(http://some.url/streamed, data=f)注意的問題:
最好使用二進位模式打開文件。這是因為 requests 有默認設置 header 中的 Content-Length,在這種情況下該值會被設為文件的位元組數。如果用文本模式打開文件,就可能碰到錯誤
9.塊編碼請求
對於出去和進來的請求,Requests 也支持分塊傳輸編碼。要發送一個塊編碼的請求,僅需為請求體提供一個生成器(或任意沒有具體長度的迭代器)
def gen():
yield hi yield thererequests.post(http://some.url/chunked, data=gen())注意:
對於分塊的編碼請求,最好使用 Response.iter_content() 對其數據進行迭代。在理想情況下,request 會設置 stream=True,這樣就可以通過調用 iter_content 並將分塊大小參數設為 none,從而進行分塊的迭代。如果要設置分塊的最大體積,可以把分塊大小參數設為任意整數。
說白了,就是段點續傳
妹的,報了一堆錯。先記錄在這,後面遇到了再研究
10.POST 多個分塊編碼的文件
上傳圖片
import requests
url = http://httpbin.org/postmultiple_files = [ (images, (foo.png, open(foo.png, rb), image/png)), (images, (bar.png, open(bar.png, rb), image/png))]r = requests.post(url, files=multiple_files)print (r.text)11.事件掛鉤
import requests
def print_url(r, *args, **kwargs): print(r.url)# Requests有一個鉤子系統,你可以用來操控部分請求過程,或信號事件處理。# 在產生響應之前調用print_url 就是server響應之前的回調hooks = dict(response=print_url)r = requests.get(http://httpbin.org, hooks=dict(response=print_url))print (r.text)也沒看有什麼用
12.自定義身份驗證
import requests
from requests.auth import AuthBaseclass PizzaAuth(AuthBase): def __init__(self, username): # setup any auth-related data here self.username = username def __call__(self, r): # modify and return the request r.headers[X-Pizza] = self.username return r#注意:auth參數必須是一個可調用對象 實現了 __call__ 方法requests.get(http://pizzabin.org/admin, auth=PizzaAuth(kenneth))作用就是在請求發出之前,有機會修改請求
13.流式請求
import requests
r = requests.get(http://httpbin.org/stream/20, stream=True)for line in r.iter_lines(): # filter out keep-alive new lines if line: decoded_line = line.decode(utf-8) print(json.loads(decoded_line))就是將請求參數設置stream=True
import requests
r = requests.get(http://httpbin.org/stream/20, stream=True)#當使用 decode_unicode=True 在 Response.iter_lines() 或 Response.iter_content() 中#時,需要提供一個編碼方式,以防伺服器沒有提供默認回退編碼,從而導致錯誤
if r.encoding is None:
r.encoding = utf-8#設置編碼方式for line in r.iter_lines(decode_unicode=True): if line: print(json.loads(line))#注意:iter_lines 不保證重進入時的安全性。多次調用該方法 會導致部分收到的數據丟失。#如果要在多處調用它,就應該使用生成的迭代器對象:
lines = r.iter_lines()
# 保存第一行以供後面使用,或者直接跳過first_line = next(lines)for line in lines: print(line)不曉得各位看懂什麼是流式請求沒。指的不是請求是流,而是請求返回的數據流。返回一點即取一點。而不是普通的是返回完成後在取內容
14.代理
import requests
proxies = { "http": "http://10.10.1.10:3128", "https": "http://10.10.1.10:1080",}requests.get("http://example.org", proxies=proxies)代理:就是你訪問一個網站,其實並不是你直接訪問的,而是你發請求給A機器,A機器取請求B機器。B返回給A,A再返回給你。代理就是中間人的意思。為什麼需要代理?因為:反爬蟲網站一般使用IP來識別一個機器。老是一個IP再不停訪問網站,該網站就會把這個IP拉入黑名單,不允許訪問。這時,就需要很多IP再擾亂反爬蟲工具的思維,避免封IP。
15.SOCKS
除了基本的 HTTP 代理,Request 還支持 SOCKS 協議的代理。這是一個可選功能,若要使用, 你需要安裝第三方庫。
在後面的項目實戰中,我們將會使用
16.HTTP動詞
GET、OPTIONS、HEAD、POST、PUT、PATCH、DELETE
GET POST最常用
PUT,DELETE在調用rest的介面時,也會用到
17.定製動詞
有些伺服器不接受GET POST等,要求用自定義的,就使用如下方法
r = requests.request(MKCOL, url, data=data)
r.status_code
18.響應頭鏈接欄位
許多 HTTP API 都有響應頭鏈接欄位的特性,它們使得 API 能夠更好地自我描述和自我顯露。
比如在使用分頁的情況下最有用
url = https://api.github.com/users/kennethreitz/repos?page=1&per_page=10
r = requests.head(url=url)print(r.headers[link])print(r.links["next"])19.阻塞和非阻塞
使用默認的傳輸適配器,Requests 不提供任何形式的非阻塞 IO。 Response.content 屬性會阻塞,直到整個響應下載完成。如果你需要更多精細控制,該庫的數據流功能(見 流式請求) 允許你每次接受少量的一部分響應,不過這些調用依然是阻塞式的。
如果你對於阻塞式 IO 有所顧慮,還有很多項目可以供你使用,它們結合了 Requests 和 Python 的某個非同步框架。典型的優秀例子是 grequests 和 requests-futures。
20.Header 排序
在某些特殊情況下你也許需要按照次序來提供 header,如果你向 headers 關鍵字參數傳入一個OrderedDict,就可以向提供一個帶排序的 header。然而,Requests 使用的默認 header 的次序會被優先選擇,這意味著如果你在 headers 關鍵字參數中覆蓋了默認 header,和關鍵字參數中別的 header 相比,它們也許看上去會是次序錯誤的。
如果這個對你來說是個問題,那麼用戶應該考慮在 Session 對象上面設置默認 header,只要將 Session 設為一個定製的 OrderedDict 即可。這樣就會讓它成為優選的次序。
以上內容,是request的高級耍法,也不算好高級。接下來的一周,我們將著重討論如何解析網頁。當然,這一部分內容不會包含JS生成的HTML。這個我們將會在項目實戰環節再討論。
最近培訓新人,搞得很晚才下班。精力不夠哇。若是喜歡我的系列文章,還請帥哥美女點個贊,足矣
推薦閱讀:
※大數據和雲計算
※「大數據殺熟」事件發酵,誰來為數據運用洗白
※入場比特幣堪比大冒險?他用一千多種交易數據給你定心丸
※大數據時代來了,你準備好了嗎?
※從頭學習大數據培訓課程 spark 基於內存的分散式計算框架(七)spark-hbase_bulkload、spark 程序集群運行