本章將告訴你該如何去對(duì)request模塊進(jìn)行二次封裝,暫時(shí)并不會(huì)告訴你HTTP協(xié)議及原理依啰、URL等相關(guān)乎串。當(dāng)然你會(huì)使用然后在來(lái)閱讀此文章一定會(huì)另有所獲。我已經(jīng)迫不及待要告訴你這個(gè)小秘密孔飒,以及想與你交流了灌闺。沒(méi)時(shí)間解釋了,快來(lái)一起和我一起探討相關(guān)的內(nèi)容吧
官方文檔對(duì)requests的定義為:Requests 唯一的一個(gè)非轉(zhuǎn)基因的 Python HTTP 庫(kù)坏瞄,人類可以安全享用桂对。
使用Python寫(xiě)做爬蟲(chóng)的小伙伴一定使用過(guò)requests這個(gè)模塊,初入爬蟲(chóng)的小伙伴也一定寫(xiě)過(guò)N個(gè)重復(fù)的requests鸠匀,這是你的疑問(wèn)蕉斜。當(dāng)然也一直伴隨著我,最近在想對(duì)requests如何進(jìn)行封裝一下缀棍,讓他支持支持通用的函數(shù)宅此。若需要使用,直接調(diào)用即可爬范。
那么問(wèn)題來(lái)了父腕,如果要寫(xiě)個(gè)供自己使用通用的請(qǐng)求函數(shù)將會(huì)有幾個(gè)問(wèn)題
- requests的請(qǐng)求方式(GET\POST\INPUT等等)
- 智能識(shí)別網(wǎng)站的編碼,避免出現(xiàn)亂碼
- 支持文本青瀑、二進(jìn)制(圖片璧亮、視頻等為二進(jìn)制內(nèi)容)
- 以及還需要傻瓜一點(diǎn)萧诫,那就是網(wǎng)站的Ua(Ua:User-Agent,基本上網(wǎng)站都會(huì)驗(yàn)證接受到請(qǐng)求的Ua枝嘶。來(lái)初步判斷是爬蟲(chóng)還是用戶)
那么咱們就針對(duì)以上問(wèn)題開(kāi)干吧
Requests的安裝
在確保python環(huán)境搭建完成后直接使用pip或者conda命令進(jìn)行安裝帘饶,安裝命令如下:
pip install requests
conda install requests
# 或者下載過(guò)慢點(diǎn)話,可以使用國(guó)內(nèi)的pip鏡像源群扶,例如:
pip install requests -i https://pypi.tuna.tsinghua.edu.cn/simple/
安裝完成后及刻,效果圖如下:
初探requests基本使用
HTTP 中最常見(jiàn)的請(qǐng)求之一就是 GET 請(qǐng)求,下面我們來(lái)詳細(xì)了解利用 requests 庫(kù)構(gòu)建 GET 請(qǐng)求的方法竞阐。
import requests
response = requests.get('http://httpbin.org/get')
# 響應(yīng)狀態(tài)碼
print("response.status_code:", response.status_code)
# 響應(yīng)頭
print("response.headers:", response.headers)
# 響應(yīng)請(qǐng)求頭
print("response.request.headers:", response.request.headers)
# 響應(yīng)二進(jìn)制內(nèi)容
print("response.content:", response.content)
# 響應(yīng)文本
print("response.text", response.text)
# 返回如下
response.status_code: 200
response.headers: {'Date': 'Thu, 12 Nov 2020 13:38:05 GMT', 'Content-Type': 'application/json', 'Content-Length': '306', 'Connection': 'keep-alive', 'Server': 'gunicorn/19.9.0', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Credentials': 'true'}
response.request.headers: {'User-Agent': 'python-requests/2.24.0', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive'}
response.content: b'{\n "args": {}, \n "headers": {\n "Accept": "*/*", \n "Accept-Encoding": "gzip, deflate", \n "Host": "httpbin.org", \n "User-Agent": "python-requests/2.24.0", \n "X-Amzn-Trace-Id": "Root=1-5fad3abd-7516d60b3e951824687a50d8"\n }, \n "origin": "116.162.2.166", \n "url": "http://httpbin.org/get"\n}\n'
{
"args": {},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Host": "httpbin.org",
"User-Agent": "python-requests/2.24.0",
"X-Amzn-Trace-Id": "Root=1-5fad3abd-7516d60b3e951824687a50d8"
},
"origin": "116.162.2.166",
"url": "http://httpbin.org/get"
}
requests基本使用已經(jīng)經(jīng)過(guò)簡(jiǎn)單的測(cè)試了缴饭,是否有一點(diǎn)點(diǎn)feel呢?接下來(lái)我們直接將它封裝為一個(gè)函數(shù)以供隨時(shí)調(diào)用
示例如下
import requests
urls = 'http://httpbin.org/get'
def downloader(url, headers=None):
response = requests.get(url, headers=headers)
return response
print("downloader.status_code:", downloader(url=urls).status_code)
print("downloader.headers:", downloader(url=urls).headers)
print("downloader.request.headers:", downloader(url=urls).request.headers)
print("downloader.content:", downloader(url=urls).content)
print("downloader.text", downloader(url=urls).text)
# 返回效果如上所示馁菜,此處省略
以上我們就把茴扁,請(qǐng)求方法封裝成了一個(gè)函數(shù)。將基本的url汪疮,headers以形參的方式暴露出來(lái)峭火,我們只需傳入需要請(qǐng)求的url即可發(fā)起請(qǐng)求,至此一個(gè)簡(jiǎn)單可復(fù)用的請(qǐng)求方法咱們就完成啦智嚷。
完~~~
以上照顧新手的就基本完成了卖丸,接下來(lái)我們搞點(diǎn)真家伙。
二次封裝
請(qǐng)求函數(shù)的封裝
由于請(qǐng)求方式并不一定(有可能是GET也有可能是POST)盏道,所以我們并不能智能
的確定它是什么方式發(fā)送請(qǐng)求的稍浆。
Requests中request方法以及幫我們實(shí)現(xiàn)了這個(gè)方法。我們將他的請(qǐng)求方式暴露出來(lái)猜嘱,寫(xiě)法如下:
urls = 'http://httpbin.org/get'
def downloader(url, method=None, headers=None):
_method = "GET" if not method else method
response = requests.request(url, method=_method, headers=headers)
return response
print("downloader.status_code:", downloader(url=urls).status_code)
print("downloader.headers:", downloader(url=urls).headers)
print("downloader.request.headers:", downloader(url=urls).request.headers)
print("downloader.content:", downloader(url=urls).content)
print("downloader.text", downloader(url=urls).text)
由于大部分都是GET方法衅枫,所以我們定義了一個(gè)默認(rèn)的請(qǐng)求方式。如果需要修改請(qǐng)求方式朗伶,只需在調(diào)用時(shí)傳入相對(duì)應(yīng)的方法即可弦撩。例如我們可以這樣
downloader(urls, method="POST")
文本編碼問(wèn)題
解決由于request的誤差判斷而造成解碼錯(cuò)誤,而得到亂碼论皆。
此誤差造成的原因是可能是響應(yīng)頭的Accept-Encoding
益楼,另一個(gè)是識(shí)別錯(cuò)誤
# 查看響應(yīng)編碼
response.encoding
此時(shí)我們需要借用Python中C語(yǔ)言編寫(xiě)的cchardet
這個(gè)包來(lái)識(shí)別響應(yīng)文本的編碼。安裝它
pip install cchardet -i https://pypi.tuna.tsinghua.edu.cn/simple/
# 如果pip直接安裝失敗的話点晴,直接用清華源的鏡像感凤。
# 實(shí)現(xiàn)智能版的解碼:如下
encoding = cchardet.detect(response.content)['encoding']
def downloader(url, method=None, headers=None):
_method = "GET" if not method else method
response = requests.request(url, method=_method, headers=headers)
encoding = cchardet.detect(response.content)['encoding']
return response.content.decode(encoding)
區(qū)分二進(jìn)制與文本的解析
在下載圖片、視頻等需獲取到其二進(jìn)制內(nèi)容粒督。而下載網(wǎng)頁(yè)文本需要進(jìn)行encode陪竿。
同理,我們只需要將一個(gè)標(biāo)志傳進(jìn)去屠橄,從而達(dá)到分辨的的效果萨惑。例如這樣
def downloader(url, method=None, headers=None, binary=False):
_method = "GET" if not method else method
response = requests.request(url, method=_method, headers=headers)
encoding = cchardet.detect(response.content)['encoding']
return response.content if binary else response.content.decode(encoding)
默認(rèn)Ua
在很多時(shí)候捐康,我們拿ua又是復(fù)制。又是加引號(hào)構(gòu)建key-value格式庸蔼。這樣有時(shí)候僅僅用requests做個(gè)測(cè)試。就搞的麻煩的很贮匕。而且請(qǐng)求過(guò)多了姐仅,直接就被封IP了。沒(méi)有自己的ip代理刻盐,沒(méi)有錢(qián)又時(shí)候還真有點(diǎn)感覺(jué)玩不起爬蟲(chóng)掏膏。
為了減少被封禁IP的概率什么的,我們添加個(gè)自己的Ua池敦锌。Ua池的原理很簡(jiǎn)單馒疹,內(nèi)部就是采用隨機(jī)的Ua,從而減少被發(fā)現(xiàn)的概率
.至于為什么可以達(dá)到這這樣的效果乙墙,在這里僅作簡(jiǎn)單介紹颖变。詳細(xì)可能要從計(jì)算機(jī)網(wǎng)絡(luò)原理說(shuō)起。
結(jié)論就是你一個(gè)公司里大多采用的都是同一個(gè)外網(wǎng)
ip去訪問(wèn)目標(biāo)網(wǎng)址听想。那么就意味著可能你們公司有N個(gè)人使用同一個(gè)ip去訪問(wèn)目標(biāo)網(wǎng)址腥刹。而封禁做區(qū)分的一般由ip訪問(wèn)頻率和瀏覽器的指紋和在一起的什么鬼東東。簡(jiǎn)單理解為Ua+ip訪問(wèn)頻率達(dá)到峰值汉买,你IP就對(duì)方關(guān)小黑屋了衔峰。
構(gòu)建自己的ua池,去添加默認(rèn)的請(qǐng)求頭蛙粘,
Ua有很多垫卤,這里就不放出來(lái)了,如果有興趣可以直接去源碼里面拿出牧。直接說(shuō)原理:構(gòu)造很多個(gè)Ua穴肘,然后隨機(jī)取用。從而降低這個(gè)同一訪問(wèn)頻率:同時(shí)也暴露端口方便你自己傳入header
from powerspider.tools.Ua import ua
import requests
def downloader(url, method=None, header=None, binary=False):
_headers = header if header else {'User-Agent': ua()}
_method = "GET" if not method else method
response = requests.request(url, method=_method, headers=_headers)
encoding = cchardet.detect(response.content)['encoding']
return response.content if binary else response.content.decode(encoding)
那么基本的文件都已經(jīng)解決了崔列,不過(guò)還不完美梢褐。異常處理,錯(cuò)誤重試赵讯,日志什么都沒(méi)盈咳。這怎么行呢”咭恚活既然干了鱼响,那就干的漂漂亮亮的。
來(lái)讓我們加入進(jìn)來(lái)這些東西
import cchardet
from retrying import retry
from powerspider import logger
from powerspider.tools.Ua import ua
from requests import request, RequestException
@retry(stop_max_attempt_number=3, retry_on_result=lambda x: x is None, wait_fixed=2000)
def downloader(url, method=None, header=None, timeout=None, binary=False, **kwargs):
logger.info(f'Scraping {url}')
_header = {'User-Agent': ua()}
_maxTimeout = timeout if timeout else 5
_headers = header if header else _header
_method = "GET" if not method else method
try:
response = request(method=_method, url=url, headers=_headers, **kwargs)
encoding = cchardet.detect(response.content)['encoding']
if response.status_code == 200:
return response.content if binary else response.content.decode(encoding)
elif 200 < response.status_code < 400:
logger.info(f"Redirect_URL: {response.url}")
logger.error('Get invalid status code %s while scraping %s', response.status_code, url)
except RequestException as e:
logger.error(f'Error occurred while scraping {url}, Msg: {e}', exc_info=True)
if __name__ == '__main__':
print(downloader("https://www.baidu.com/", "GET"))
至此组底,我們的對(duì)Requests二次封裝丈积,構(gòu)造通用的請(qǐng)求函數(shù)就已經(jīng)完成了筐骇。
源碼地址:https://github.com/PowerSpider/PowerSpider/tree/dev
公眾號(hào):積跬Coder
期待下次再見(jiàn)