python

关注公众号 jb51net

关闭
首页 > 脚本专栏 > python > Python多线程发送HTTP请求

Python中多线程发送HTTP请求的三种方案对比

作者:detayun

本文介绍了Python中多线程发送HTTP请求的三种主流方案对比,分别是threading+Queue,ThreadPoolExecutor和asyncio+aiohttp,文内还指出了常见陷阱,希望对大家有所帮助

为什么需要多线程?

单线程发送 100 个请求,每个 0.5 秒,总耗时 50 秒。多线程发送同样 100 个请求,总耗时可能压到 3~5 秒。

差距不是"快一点",是量级 difference

但多线程不是银弹。用错了,比单线程还慢,还容易把对方服务打挂。

三种主流方案对比

方案适用场景上手难度性能上限
threading + Queue简单批量任务⭐⭐中等
concurrent.futures.ThreadPoolExecutor大多数场景首选
asyncio + aiohttp高并发、IO密集⭐⭐⭐⭐最高

结论先给:80% 的场景用 ThreadPoolExecutor 就够了。

方案一:threading + Queue(手动控制)

适合需要精细控制线程数、任务队列的场景。

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

优点

方案三:asyncio + aiohttp(高性能)

适合 上千级别并发,或者你本身就在用异步框架(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")

优点:单线程实现高并发,资源占用极低。

代价:学习曲线陡,所有调用链必须是 async 的,混用会出问题。

常见坑

1. max_workers 不是越大越好

workers = 100  # ❌ 大概率更慢,还可能被封 IP
workers = 10   # ✅ 大部分场景够用

经验值:10~20 是 sweet spot。超过 50 基本没收益,还可能触发对方限流。

2. 忘了设 timeout

requests.get(url)  # ❌ 对方不响应,你的线程就永远挂着
requests.get(url, timeout=5)  # ✅ 5秒没响应就放弃

3. 混用 Session 和多线程

requests.Session() 不是线程安全的

session = requests.Session()  # ❌ 多线程共享同一个 session 会出问题

# 正确做法:每个线程自己创建 session
def fetch(url):
    with requests.Session() as s:  # ✅
        return s.get(url).text

或者用 ThreadPoolExecutor 配合 requests 本身就没问题,因为每个 submit 独立执行函数,函数内自己创建 session。

4. 对方有反爬

多线程 = 高频率访问 = 容易触发风控。

应对:

选型决策树

请求量 < 100?
  → 单线程 + requests 就够了,别过度设计

请求量 100~1000?
  → ThreadPoolExecutor(方案二)

请求量 > 1000 或已在用异步框架?
  → asyncio + aiohttp(方案三)

需要精细控制任务优先级/重试/失败队列?
  → threading + Queue(方案一)或 Celery

一句话总结

多线程发送请求的核心不是"开更多线程",而是控制并发数 + 复用连接 + 设置超时

ThreadPoolExecutor 解决了 80% 的问题,剩下 20% 才需要上 asyncio 或 Celery。

到此这篇关于Python中多线程发送HTTP请求的三种方案对比的文章就介绍到这了,更多相关Python多线程发送HTTP请求内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文