
asyncio并发请求的核心逻辑
Python中用asyncio发并发HTTP请求,关键不是“同时发100个”,而是让IO等待时不阻塞主线程——比如发请求后不等响应,立刻切去处理下一个请求或已返回的数据。真正起效的前提是:所有IO操作(如HTTP请求)都得是异步的,不能混用requests这类同步库。
用aiohttp替代requests发异步请求
aiohttp是asyncio生态中最常用的异步HTTP客户端。它本身基于async/await设计,能自然融入事件循环。安装后直接用session.get()发起请求,配合async with管理连接生命周期,避免资源泄漏。
- 创建
aiohttp.ClientSession()一次,复用给多个请求,别每次新建 - 每个请求用
await session.get(url),不是session.get(url).text() - 响应体用
await resp.text()或await resp.json(),加await - 加
timeout参数防卡死,例如timeout=aiohttp.ClientTimeout(total=10)
控制并发数防止目标服务器压力过大
无限制并发(如asyncio.gather(*tasks)扔几百个任务)可能触发对方限流、封IP,或耗尽本地文件描述符。推荐用asyncio.Semaphore做并发限流:
- 初始化一个信号量:
sem = asyncio.Semaphore(10),表示最多10个并发 - 每个请求前
async with sem:,自动排队获取许可 - 结合
asyncio.create_task()启动任务,比gather更灵活,便于错误隔离和重试
异常处理与重试不能省略
网络请求失败很常见:超时、连接拒绝、状态码非2xx、JSON解析失败……这些在异步里不会自动中断整个程序,但若不捕获,错误会被静默吞掉或导致后续逻辑出错。
立即学习“Python免费学习笔记(深入)”;
- 每个
await session.get()外层套try/except aiohttp.ClientError - 检查
resp.status,非2xx时主动raise Exception(f"HTTP {resp.status}") - 重试建议用
tenacity库(支持async),或手写带指数退避的async def fetch_with_retry() - 记录失败URL和原因,方便事后排查,别只打印然后跳过
完整最小可运行示例
以下代码实现10个并发请求,每秒最多5个,带基础错误处理:
import asyncio
import aiohttp
<p>async def fetch(session, url, sem):
async with sem:
try:
async with session.get(url, timeout=5) as resp:
if resp.status == 200:
return await resp.text()
else:
raise Exception(f"HTTP {resp.status}")
except Exception as e:
return f"ERROR: {url} → {e}"</p><p>async def main():
urls = ["<a href="https://www.php.cn/link/5f69e19efaba426d62faeab93c308f5c">https://www.php.cn/link/5f69e19efaba426d62faeab93c308f5c</a>"] <em> 20
sem = asyncio.Semaphore(5)
async with aiohttp.ClientSession() as session:
tasks = [fetch(session, url, sem) for url in urls]
results = await asyncio.gather(</em>tasks, return_exceptions=True)
print(f"完成 {len(results)} 个请求")</p><p>asyncio.run(main())</p>










