Python中多線程發(fā)送HTTP請求的三種方案對比
為什么需要多線程?
單線程發(fā)送 100 個請求,每個 0.5 秒,總耗時 50 秒。多線程發(fā)送同樣 100 個請求,總耗時可能壓到 3~5 秒。
差距不是"快一點",是量級 difference。
但多線程不是銀彈。用錯了,比單線程還慢,還容易把對方服務打掛。
三種主流方案對比
| 方案 | 適用場景 | 上手難度 | 性能上限 |
|---|---|---|---|
threading + Queue | 簡單批量任務 | ?? | 中等 |
concurrent.futures.ThreadPoolExecutor | 大多數(shù)場景首選 | ? | 高 |
asyncio + aiohttp | 高并發(fā)、IO密集 | ???? | 最高 |
結論先給:80% 的場景用 ThreadPoolExecutor 就夠了。
方案一:threading + Queue(手動控制)
適合需要精細控制線程數(shù)、任務隊列的場景。
import threading
import queue
import requests
import time
urls = [f"http://httpbin.org/delay/1?id={i}" for i in range(20)]
result_queue = queue.Queue()
def worker(q):
while not q.empty():
url = q.get()
try:
resp = requests.get(url, timeout=5)
result_queue.put((url, resp.status_code))
except Exception as e:
result_queue.put((url, str(e)))
finally:
q.task_done()
# 啟動 5 個線程
threads = []
for _ in range(5):
t = threading.Thread(target=worker, args=(queue.Queue(),))
t.start()
threads.append(t)
# 填入任務
task_queue = queue.Queue()
for url in urls:
task_queue.put(url)
# 重新分配任務給 worker(簡化寫法,實際應把 task_queue 傳進去)
for t in threads:
t.join()
while not result_queue.empty():
print(result_queue.get())
問題:代碼啰嗦,手動管理線程生命周期,容易寫錯。
方案二:ThreadPoolExecutor(推薦)
Python 3.2+ 內置,幾行代碼搞定。
import requests
from concurrent.futures import ThreadPoolExecutor, as_completed
import time
urls = [f"http://httpbin.org/delay/1?id={i}" for i in range(20)]
def fetch(url):
try:
resp = requests.get(url, timeout=5)
return url, resp.status_code
except Exception as e:
return url, str(e)
t1 = time.time()
with ThreadPoolExecutor(max_workers=5) as executor:
futures = {executor.submit(fetch, url): url for url in urls}
for future in as_completed(futures):
url, result = future.result()
print(f"{url} -> {result}")
print(f"耗時:{time.time() - t1:.2f}s")
優(yōu)點:
- 自動管理線程池,不用手動
start/join as_completed按完成順序返回結果,不用等全部完成max_workers控制并發(fā)數(shù),防止把對方打掛
方案三:asyncio + aiohttp(高性能)
適合 上千級別并發(fā),或者你本身就在用異步框架(FastAPI、Sanic 等)。
import asyncio
import aiohttp
import time
urls = [f"http://httpbin.org/delay/1?id={i}" for i in range(20)]
async def fetch(session, url):
try:
async with session.get(url, timeout=5) as resp:
return url, resp.status
except Exception as e:
return url, str(e)
async def main():
async with aiohttp.ClientSession() as session:
tasks = [fetch(session, url) for url in urls]
results = await asyncio.gather(*tasks)
for url, result in results:
print(f"{url} -> {result}")
t1 = time.time()
asyncio.run(main())
print(f"耗時:{time.time() - t1:.2f}s")
優(yōu)點:單線程實現(xiàn)高并發(fā),資源占用極低。
代價:學習曲線陡,所有調用鏈必須是 async 的,混用會出問題。
常見坑
1. max_workers 不是越大越好
workers = 100 # ? 大概率更慢,還可能被封 IP workers = 10 # ? 大部分場景夠用
經驗值:10~20 是 sweet spot。超過 50 基本沒收益,還可能觸發(fā)對方限流。
2. 忘了設 timeout
requests.get(url) # ? 對方不響應,你的線程就永遠掛著 requests.get(url, timeout=5) # ? 5秒沒響應就放棄
3. 混用 Session 和多線程
requests.Session() 不是線程安全的。
session = requests.Session() # ? 多線程共享同一個 session 會出問題
# 正確做法:每個線程自己創(chuàng)建 session
def fetch(url):
with requests.Session() as s: # ?
return s.get(url).text
或者用 ThreadPoolExecutor 配合 requests 本身就沒問題,因為每個 submit 獨立執(zhí)行函數(shù),函數(shù)內自己創(chuàng)建 session。
4. 對方有反爬
多線程 = 高頻率訪問 = 容易觸發(fā)風控。
應對:
- 加隨機延遲:
time.sleep(random.uniform(0.5, 2)) - 換 User-Agent 池
- 用代理 IP 池
選型決策樹
請求量 < 100?
→ 單線程 + requests 就夠了,別過度設計
請求量 100~1000?
→ ThreadPoolExecutor(方案二)
請求量 > 1000 或已在用異步框架?
→ asyncio + aiohttp(方案三)
需要精細控制任務優(yōu)先級/重試/失敗隊列?
→ threading + Queue(方案一)或 Celery
一句話總結
多線程發(fā)送請求的核心不是"開更多線程",而是控制并發(fā)數(shù) + 復用連接 + 設置超時。
ThreadPoolExecutor 解決了 80% 的問題,剩下 20% 才需要上 asyncio 或 Celery。
到此這篇關于Python中多線程發(fā)送HTTP請求的三種方案對比的文章就介紹到這了,更多相關Python多線程發(fā)送HTTP請求內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Python tensorflow實現(xiàn)mnist手寫數(shù)字識別示例【非卷積與卷積實現(xiàn)】
這篇文章主要介紹了Python tensorflow實現(xiàn)mnist手寫數(shù)字識別,結合實例形式分析了基于tensorflow模塊使用非卷積與卷積算法實現(xiàn)手寫數(shù)字識別的具體操作技巧,需要的朋友可以參考下2019-12-12
opencv python 圖像輪廓/檢測輪廓/繪制輪廓的方法
這篇文章主要介紹了opencv python 圖像輪廓/檢測輪廓/繪制輪廓的方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-07-07
Django Sitemap 站點地圖的實現(xiàn)方法
這篇文章主要介紹了Django Sitemap 站點地圖的實現(xiàn)方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-04-04
4種方法教你利用Python發(fā)現(xiàn)數(shù)據的規(guī)律
發(fā)現(xiàn)數(shù)據的規(guī)律是數(shù)據分析和數(shù)據科學中非常重要的一個步驟。這篇文章主要給大家整理了4個可以發(fā)現(xiàn)數(shù)據規(guī)律的方法,希望對大家有所幫助2023-03-03
Python開發(fā)之迭代器&生成器的實戰(zhàn)案例分享
在 Python 中,迭代器和生成器都是用來遍歷數(shù)據集合的工具,可以按需逐個生成或返回數(shù)據,從而避免一次性加載整個數(shù)據集合所帶來的性能問題和內存消耗問題。本文主要和大家分享幾個貼近實際運維開發(fā)工作中的場景案例,希望對大家有所幫助2023-04-04
Django數(shù)據庫(SQlite)基本入門使用教程
django有默認自帶的數(shù)據庫,當然也可以用其他的數(shù)據庫,下面這篇文章主要給大家介紹了關于Django數(shù)據庫(SQlite)基本入門使用教程的相關資料,需要的朋友可以參考下2022-07-07

