天下武功,唯快不破!如何用Python發(fā)送 10 萬個(gè) http 請(qǐng)求?
Python 并發(fā)編程有很多方法,多線程的標(biāo)準(zhǔn)庫 threading,concurrency,協(xié)程 asyncio,當(dāng)然還有 grequests 這種異步庫,每一個(gè)都可以實(shí)現(xiàn)上述需求,下面一一用代碼實(shí)現(xiàn)一下,本文的代碼可以直接運(yùn)行,給你以后的并發(fā)編程作為參考:
隊(duì)列+多線程
定義一個(gè)大小為 400 的隊(duì)列,然后開啟 200 個(gè)線程,每個(gè)線程都是不斷的從隊(duì)列中獲取 url 并訪問。
主線程讀取文件中的 url 放入隊(duì)列中,然后等待隊(duì)列中所有的元素都被接收和處理完畢。代碼如下:
from?threading?import?Thread
import?sys
from?queue?import?Queue
import?requests
concurrent?=?200
def?doWork():
????while?True:
????????url?=?q.get()
????????status,?url?=?getStatus(url)
????????doSomethingWithResult(status,?url)
????????q.task_done()
def?getStatus(ourl):
????try:
????????res?=?requests.get(ourl)
????????return?res.status_code,?ourl
????except:
????????return?"error",?ourl
def?doSomethingWithResult(status,?url):
????print(status,?url)
q?=?Queue(concurrent?*?2)
for?i?in?range(concurrent):
????t?=?Thread(target=doWork)
????t.daemon?=?True
????t.start()
try:
????for?url?in?open("urllist.txt"):
????????q.put(url.strip())
????q.join()
except?KeyboardInterrupt:
????sys.exit(1)
運(yùn)行結(jié)果如下:
有沒有 get 到新技能?
線程池
如果你使用線程池,推薦使用更高級(jí)的 concurrent.futures 庫:
import?concurrent.futures
import?requests
out?=?[]
CONNECTIONS?=?100
TIMEOUT?=?5
urls?=?[]
with?open("urllist.txt")?as?reader:
????for?url?in?reader:
????????urls.append(url.strip())
def?load_url(url,?timeout):
????ans?=?requests.get(url,?timeout=timeout)
????return?ans.status_code
with?concurrent.futures.ThreadPoolExecutor(max_workers=CONNECTIONS)?as?executor:
????future_to_url?=?(executor.submit(load_url,?url,?TIMEOUT)?for?url?in?urls)
????for?future?in?concurrent.futures.as_completed(future_to_url):
????????try:
????????????data?=?future.result()
????????except?Exception?as?exc:
????????????data?=?str(type(exc))
????????finally:
????????????out.append(data)
????????????print(data)
協(xié)程 + aiohttp
協(xié)程也是并發(fā)非常常用的工具了:
import?asyncio
from?aiohttp?import?ClientSession,?ClientConnectorError
async?def?fetch_html(url:?str,?session:?ClientSession,?**kwargs)?->?tuple:
????try:
????????resp?=?await?session.request(method="GET",?url=url,?**kwargs)
????except?ClientConnectorError:
????????return?(url,?404)
????return?(url,?resp.status)
async?def?make_requests(urls:?set,?**kwargs)?->?None:
????async?with?ClientSession()?as?session:
????????tasks?=?[]
????????for?url?in?urls:
????????????tasks.append(
????????????????fetch_html(url=url,?session=session,?**kwargs)
????????????)
????????results?=?await?asyncio.gather(*tasks)
????for?result?in?results:
????????print(f'{result[1]}?-?{str(result[0])}')
if?__name__?==?"__main__":
????import?sys
????assert?sys.version_info?>=?(3,?7),?"Script?requires?Python?3.7+."
????with?open("urllist.txt")?as?infile:
????????urls?=?set(map(str.strip,?infile))
????asyncio.run(make_requests(urls=urls))
grequests[1]
這是個(gè)第三方庫,目前有 3.8K 個(gè)星,就是 Requests + Gevent[2],讓異步 http 請(qǐng)求變得更加簡(jiǎn)單。Gevent 的本質(zhì)還是協(xié)程。
使用前:
pip?install?grequests
使用起來那是相當(dāng)?shù)暮?jiǎn)單:
import?grequests
urls?=?[]
with?open("urllist.txt")?as?reader:
????for?url?in?reader:
????????urls.append(url.strip())
rs?=?(grequests.get(u)?for?u?in?urls)
for?result?in?grequests.map(rs):
????print(result.status_code,?result.url)
注意 grequests.map(rs) 是并發(fā)執(zhí)行的。運(yùn)行結(jié)果如下:
也可以加入異常處理:
>>>?def?exception_handler(request,?exception):
...????print("Request?failed")
>>>?reqs?=?[
...????grequests.get('http://httpbin.org/delay/1',?timeout=0.001),
...????grequests.get('http://fakedomain/'),
...????grequests.get('http://httpbin.org/status/500')]
>>>?grequests.map(reqs,?exception_handler=exception_handler)
Request?failed
Request?failed
[None,?None,?500]>]
最后的話
今天分享了并發(fā) http 請(qǐng)求的幾種實(shí)現(xiàn)方式,有人說異步(協(xié)程)性能比多線程好,其實(shí)要分場(chǎng)景看的,沒有一種方法適用所有的場(chǎng)景,筆者就曾做過一個(gè)實(shí)驗(yàn),也是請(qǐng)求 url,當(dāng)并發(fā)數(shù)量超過 500 時(shí),協(xié)程明顯變慢。
參考資料
[1]grequests: https://github.com/spyoungtech/grequests
[2]Gevent: http://www.gevent.org/
推薦閱讀:
入門:?最全的零基礎(chǔ)學(xué)Python的問題? |?零基礎(chǔ)學(xué)了8個(gè)月的Python??|?實(shí)戰(zhàn)項(xiàng)目?|學(xué)Python就是這條捷徑
量化:?定投基金到底能賺多少錢?? |?我用Python對(duì)去年800只基金的數(shù)據(jù)分析??
干貨:爬取豆瓣短評(píng),電影《后來的我們》?|?38年NBA最佳球員分析?|? ?從萬眾期待到口碑撲街!唐探3令人失望? |?笑看新倚天屠龍記?|?燈謎答題王?|用Python做個(gè)海量小姐姐素描圖?|碟中諜這么火,我用機(jī)器學(xué)習(xí)做個(gè)迷你推薦系統(tǒng)電影
趣味:彈球游戲? |?九宮格? |?漂亮的花?|?兩百行Python《天天酷跑》游戲!
AI:?會(huì)做詩的機(jī)器人?|?給圖片上色?|?預(yù)測(cè)收入?|?碟中諜這么火,我用機(jī)器學(xué)習(xí)做個(gè)迷你推薦系統(tǒng)電影
小工具:?Pdf轉(zhuǎn)Word,輕松搞定表格和水印!?|?一鍵把html網(wǎng)頁保存為pdf!|??再見PDF提取收費(fèi)!?|?用90行代碼打造最強(qiáng)PDF轉(zhuǎn)換器,word、PPT、excel、markdown、html一鍵轉(zhuǎn)換?|?制作一款釘釘?shù)蛢r(jià)機(jī)票提示器!?|60行代碼做了一個(gè)語音壁紙切換器天天看小姐姐!|
年度爆款文案
2).學(xué)Python真香!我用100行代碼做了個(gè)網(wǎng)站,幫人PS旅行圖片,賺個(gè)雞腿吃
9).發(fā)現(xiàn)一個(gè)舔狗福利!這個(gè)Python爬蟲神器太爽了,自動(dòng)下載妹子圖片
點(diǎn)閱讀原文,領(lǐng)AI全套資料!


