Python爬蟲系列(三):requests高級耍法

昨天,我們更多的討論了request的基礎(chǔ)API,讓我們對它有了基礎(chǔ)的認知。學會上一課程,我們已經(jīng)能寫點基本的爬蟲了。但是還不夠,因為,很多站點是需要登錄的,在站點的各個請求之間,是需要保持回話狀態(tài)的,有的站點還需要證書驗證,等等這一系列的問題,我們將在今天這一環(huán)節(jié),加以討論。

1.會話對象

會話:session,就是你點進這個站點后,由瀏覽器與服務(wù)器之間保持的一次連接。這次連接里面,你跳轉(zhuǎn)頁面,或發(fā)起其他請求,服務(wù)器要求某些數(shù)據(jù)驗證。服務(wù)器不會叫你在每次跳轉(zhuǎn)時候進行驗證,而是用已驗證的結(jié)果進行跳轉(zhuǎn)。這樣就節(jié)省服務(wù)器資源,底層的TCP連接也會被重用。

跨請求保持數(shù)據(jù)(客戶端存數(shù)據(jù)):

s = requests.Session()

s.get('http://httpbin.org/cookies/set/sessioncookie/123456789')

r = s.get("http://httpbin.org/cookies")

print(r.text)

回話提供請求默認數(shù)據(jù)(數(shù)據(jù)會被存到服務(wù)端):

s = requests.Session()

s.auth = ('user', 'pass')

s.headers.update({'x-test': 'true'})

# 'x-test' 和 'x-test2' 一起發(fā)送給url

s.get('http://httpbin.org/headers', headers={'x-test2': 'true'})

注意:即便使用了session,方法級別的參數(shù),仍然不會再跨請求保持。

以下代碼,另個請求分別有自己的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.請求與響應對象

任何時候,我們往服務(wù)器發(fā)消息,都會返回一個response的響應對象,同時,還能獲得我們自己創(chuàng)建的request對象

r = requests.get('http://en.wikipedia.org/wiki/Monty_Python')

print (r.headers)

print (r.request.headers)

在發(fā)送請求之前,需要對body獲header做一些額外處理,使用如下方法:

from requests import Request, Session

s = Session()

req = Request('GET', url,

data=data,

headers=header

)

#要想獲取有狀態(tài)的請求,使用:

prepped = s.prepare_request(req)

而不是使用:

prepped = req.prepare()

# do something with prepped.body

# do something with prepped.headers

resp = 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 必須是解密狀態(tài)。目前,Requests 不支持使用加密的 key。

這里補充一句:當?shù)卿?2306時喊你要安裝證書。當向有證書驗證要求的站點,就使用上面的代碼

http://5.CA證書 科普:

Requests 默認附帶了一套它信任的根證書,來自于Mozilla trust store。然而它們在每次 Requests 更新時才會更新。這意味著如果你固定使用某一版本的 Requests,你的證書有可能已經(jīng) 太舊了。

從 Requests 2.4.0 版之后,如果系統(tǒng)中裝了certifi包,Requests 會試圖使用它里邊的 證書。這樣用戶就可以在不修改代碼的情況下更新他們的可信任證書。

6.響應體內(nèi)容工作流

import requests

# 默認情況下,發(fā)起請求會同時下載響應頭和響應體(就是響應內(nèi)容)

tarball_url = 'https://github.com/kennethreitz/requests/tarball/master'

# 如果將stream=True 則會推遲響應內(nèi)容的下載

r = requests.get(tarball_url, stream=True)

# 這里就是:滿足某種條件才去下載

if int(r.headers['content-length']) < TOO_LONG:

content = r.content

# 在請求中把 stream 設(shè)為 True,Requests 無法將連接釋放回連接池,

# 除非消耗了所有的數(shù)據(jù),或者調(diào)用了 Response.close。

# 這樣會帶來連接效率低下的問題。如果在使用 stream=True 的同時還在部分讀取請求的 body(或者完全沒有讀取 body),

# 那么就應該使用 with 語句發(fā)送請求,這樣可以保證請求一定會被關(guān)閉

with requests.get('http://httpbin.org/get', stream=True) as r:

# 這里處理響應

content = r.content

7.保持活動狀態(tài)(持久連接)科普

同一會話內(nèi)的持久連接是完全自動處理的,同一會話內(nèi)你發(fā)出的任何請求都會自動復用恰當?shù)倪B接。

注意:只有所有的響應體數(shù)據(jù)被讀取完畢連接才會被釋放回連接池;所以確保將 stream 設(shè)置為 False 或讀取 Response 對象的 content 屬性。

8.流式上傳

Requests支持流式上傳,這允許發(fā)送大的數(shù)據(jù)流或文件,而無需先把它們讀入內(nèi)存。要使用流式上傳,需,為請求體,提供一個類文件對象即可:

with open('massive-body') as f:

requests.post('http://some.url/streamed', data=f)

注意的問題:

最好使用二進制模式打開文件。這是因為 requests 有默認設(shè)置 header 中的 Content-Length,在這種情況下該值會被設(shè)為文件的字節(jié)數(shù)。如果用文本模式打開文件,就可能碰到錯誤

9.塊編碼請求

對于出去和進來的請求,Requests 也支持分塊傳輸編碼。要發(fā)送一個塊編碼的請求,僅需為請求體提供一個生成器(或任意沒有具體長度的迭代器)

def gen():

yield 'hi'

yield 'there'

requests.post('http://some.url/chunked', data=gen())

注意:

對于分塊的編碼請求,最好使用 Response.iter_content() 對其數(shù)據(jù)進行迭代。在理想情況下,request 會設(shè)置 stream=True,這樣就可以通過調(diào)用 iter_content 并將分塊大小參數(shù)設(shè)為 none,從而進行分塊的迭代。如果要設(shè)置分塊的最大體積,可以把分塊大小參數(shù)設(shè)為任意整數(shù)。

說白了,就是段點續(xù)傳

妹的,報了一堆錯。先記錄在這,后面遇到了再研究

10.POST 多個分塊編碼的文件

上傳圖片

import requests

url = 'http://httpbin.org/post'

multiple_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有一個鉤子系統(tǒng),你可以用來操控部分請求過程,或信號事件處理。

# 在產(chǎn)生響應之前調(diào)用print_url 就是server響應之前的回調(diào)

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 AuthBase

class 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參數(shù)必須是一個可調(diào)用對象 實現(xiàn)了 __call__ 方法

requests.get('http://pizzabin.org/admin', auth=PizzaAuth('kenneth'))

作用就是在請求發(fā)出之前,有機會修改請求

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))

就是將請求參數(shù)設(shè)置stream=True

import requests

r = requests.get('http://httpbin.org/stream/20', stream=True)

#當使用 decode_unicode=True 在Response.iter_lines()Response.iter_content()中#時,需要提供一個編碼方式,以防服務(wù)器沒有提供默認回退編碼,從而導致錯誤

if r.encoding is None:

r.encoding = 'utf-8'#設(shè)置編碼方式

for line in r.iter_lines(decode_unicode=True):

if line:

print(json.loads(line))

#注意:iter_lines不保證重進入時的安全性。多次調(diào)用該方法 會導致部分收到的數(shù)據(jù)丟失。#如果要在多處調(diào)用它,就應該使用生成的迭代器對象:

lines = r.iter_lines()

# 保存第一行以供后面使用,或者直接跳過

first_line = next(lines)

forlineinlines:

print(line)

不曉得各位看懂什么是流式請求沒。指的不是請求是流,而是請求返回的數(shù)據(jù)流。返回一點即取一點。而不是普通的是返回完成后在取內(nèi)容

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)

代理:就是你訪問一個網(wǎng)站,其實并不是你直接訪問的,而是你發(fā)請求給A機器,A機器取請求B機器。B返回給A,A再返回給你。代理就是中間人的意思。為什么需要代理?因為:反爬蟲網(wǎng)站一般使用IP來識別一個機器。老是一個IP再不停訪問網(wǎng)站,該網(wǎng)站就會把這個IP拉入黑名單,不允許訪問。這時,就需要很多IP再擾亂反爬蟲工具的思維,避免封IP。

15.SOCKS

除了基本的 HTTP 代理,Request 還支持 SOCKS 協(xié)議的代理。這是一個可選功能,若要使用, 你需要安裝第三方庫。

在后面的項目實戰(zhàn)中,我們將會使用

16.HTTP動詞

GET、OPTIONS、HEAD、POST、PUT、PATCH、DELETE

GET POST最常用

PUT,DELETE在調(diào)用rest的接口時,也會用到

17.定制動詞

有些服務(wù)器不接受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屬性會阻塞,直到整個響應下載完成。如果你需要更多精細控制,該庫的數(shù)據(jù)流功能(見流式請求) 允許你每次接受少量的一部分響應,不過這些調(diào)用依然是阻塞式的。

如果你對于阻塞式 IO 有所顧慮,還有很多項目可以供你使用,它們結(jié)合了 Requests 和 Python 的某個異步框架。典型的優(yōu)秀例子是grequestsrequests-futures。

20.Header 排序

在某些特殊情況下你也許需要按照次序來提供 header,如果你向 headers 關(guān)鍵字參數(shù)傳入一個OrderedDict,就可以向提供一個帶排序的 header。然而,Requests 使用的默認 header 的次序會被優(yōu)先選擇,這意味著如果你在 headers 關(guān)鍵字參數(shù)中覆蓋了默認 header,和關(guān)鍵字參數(shù)中別的 header 相比,它們也許看上去會是次序錯誤的。

如果這個對你來說是個問題,那么用戶應該考慮在Session對象上面設(shè)置默認 header,只要將Session設(shè)為一個定制的 OrderedDict 即可。這樣就會讓它成為優(yōu)選的次序。

以上內(nèi)容,是request的高級耍法,也不算好高級。接下來的一周,我們將著重討論如何解析網(wǎng)頁。當然,這一部分內(nèi)容不會包含JS生成的HTML。這個我們將會在項目實戰(zhàn)環(huán)節(jié)再討論。

最近培訓新人,搞得很晚才下班。精力不夠哇。若是喜歡我的系列文章,還請帥哥美女點個贊,足矣

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容