前排提醒变勇!
本文并非是“零起點(diǎn)”的憨攒。閱讀下列文檔之前,請保證你具有以下的知識:
- 了解requests和grequests模塊待笑,并能夠使用其建立HTTP請求;
- 了解回調(diào)函數(shù)的含義抓谴。
背景
進(jìn)行爬蟲操作時暮蹂,程序員除了想要獲取請求的結(jié)果之外,有時還想在請求過程中獲取相關(guān)的信息(比如說在某個時刻已請求數(shù)據(jù)的長度)齐邦,以完成某些任務(wù)(比如說下載進(jìn)度條)椎侠。我們知道Python內(nèi)置模塊urllib下的方法urllib.request.urlretrieve
可以添加回調(diào)函數(shù)參數(shù)callback,但是在另外一個常用模塊requests中措拇,我們難以找到直接的回調(diào)函數(shù)參數(shù)我纪,在國內(nèi)互聯(lián)網(wǎng)中也難以找到相關(guān)信息,但這不代表這個模塊完不成這個任務(wù)丐吓。在此我查閱了一些資料和問答頻道浅悉,并寫成此文,以供參考券犁。
研究
以下我們以添加GET請求的進(jìn)度條的方式來展示改進(jìn)方法术健。
使用的目標(biāo)URL:http://wppkg.baidupcs.com/issue/netdisk/yunguanjia/BaiduNetdisk_6.8.9.1.exe(百度網(wǎng)盤Windows端安裝包,大小約30MB粘衬,下載需時約10s)荞估。
requests沒有直接的callback參數(shù)(這一點(diǎn)待定),但是其具有一個參數(shù)“流”(stream)允許我們以流的形式進(jìn)行請求稚新。以流形式請求時勘伺,我們可以“一塊”(chunk)“一塊”地獲取請求的內(nèi)容,請求對象Response在此時不會再全部內(nèi)容完全被獲取時才能返回褂删;并且Response支持生成器iter_content
的方式來返回請求的內(nèi)容飞醉。由以上二點(diǎn),我們可以構(gòu)造一個新的請求函數(shù)屯阀,來做出回調(diào)函數(shù)的效果:
注:本方法源自Stack Overflow某條回答缅帘,源地址遺失轴术。
def get_callback(url, data=None, chunk_size=1024, callback=None):
r = requests.get(url, data=data, stream=True) # Response請求,由于添加了stream钦无,請求速度不會慢
length = r.headers.get('content-length') # 請求的總長度逗栽,便于回調(diào)
if length == None: # 有的請求不帶上面的頭參數(shù),故要特判
return r.content # 直接返回
if not callback: # 如果回調(diào)函數(shù)沒有給定
callback = lambda now, tot: None # 一個無用的函數(shù)铃诬,占位用祭陷,其中callback需要接受兩個參數(shù),分別對應(yīng)已獲取長度和總長度趣席,后同
dl = 0 # 目前已獲取的長度
length = int(length) # headers里取得的值都是字符串兵志,要化成整數(shù)
res = b'' # 目前已獲取的請求內(nèi)容
for chunk in r.iter_content(chunk_size=chunk_size): # 每次取出一塊,注意chunk_size在requests中默認(rèn)為1宣肚,這里調(diào)大為1024想罕,節(jié)約for循環(huán)的成本
dl += len(chunk)
res = b''.join([res, chunk]) # 這里沒有直接加,速度更快
callback(dl, length) # 執(zhí)行回調(diào)
return res # 最終給出請求的內(nèi)容
測試結(jié)果如下:
In [63]: res = get_callback(url, None, 1048576, callback)
1048576 35984960
2097152 35984960
3145728 35984960
4194304 35984960
5242880 35984960
6291456 35984960
7340032 35984960
8388608 35984960
9437184 35984960
10485760 35984960
11534336 35984960
12582912 35984960
13631488 35984960
14680064 35984960
15728640 35984960
16777216 35984960
17825792 35984960
18874368 35984960
19922944 35984960
20971520 35984960
22020096 35984960
23068672 35984960
24117248 35984960
25165824 35984960
26214400 35984960
27262976 35984960
28311552 35984960
29360128 35984960
30408704 35984960
31457280 35984960
32505856 35984960
33554432 35984960
34603008 35984960
35651584 35984960
35984960 35984960
In [64]: len(res)
Out[64]: 35984960
如果我們需要時刻對某個變量賦值霉涨,而不是print按价,我們可以在callback中添加global語句,使回調(diào)函數(shù)可以修改外部全局變量笙瑟。
但如果是第三方進(jìn)度條模塊(比如說progressbar)楼镐,則可能需要對源代碼改進(jìn)較多,原因是進(jìn)度條的實(shí)現(xiàn)需要將iter_content
視作某個函數(shù)的自變量往枷,并且進(jìn)度條模塊通常對生成器不友好(原因是生成器迭代次數(shù)無法確定框产,也就無法判斷最大循環(huán)次數(shù)。但是我們先前已經(jīng)得到了chunk_size
和length
兩個變量错洁,而整個過程不包括其他的循環(huán)秉宿,故我們可以計算得出總共的循環(huán)次數(shù):
max_value = int(math.ceil(length / chunk_size))
以此我們得出回調(diào)的改進(jìn)版:
def get_callback(url, data=None, chunk_size=1024, callback=None):
r = requests.get(url, data=data, stream=True)
length = r.headers.get('content-length')
if length == None:
return r.content
if not callback:
callback = lambda now, tot: None
dl = 0
length = int(length)
res = b''
max_value = int(math.ceil(length / chunk_size))
bar = progressbar.ProgressBar(max_value=max_value) # 設(shè)定進(jìn)度條的最大循環(huán)次數(shù)
for chunk in bar(r.iter_content(chunk_size=chunk_size)):
dl += len(chunk)
res = b''.join([res, chunk])
callback(dl, length)
return res
由于進(jìn)度條并未占用callback參數(shù),我們依然可以另添一個回調(diào)函數(shù)完成其他任務(wù)屯碴。
全文完描睦。