不能直接用asyncio.gather无限制并发,因为会触发连接数爆炸、服务器拒绝或“Too many open files”错误;需用asyncio.Semaphore控制同时请求数,并复用ClientSession、合理配置timeout与异常处理。

为什么不能直接用 asyncio.gather 无限制并发?
直接把几百个 fetch_url 任务丢给 asyncio.gather,看似简单,实际容易触发连接数爆炸、目标服务器拒绝或本地端口耗尽。aiohttp 默认的 TCPConnector 有连接池限制(通常 100),但没做并发请求数控制——它只管复用连接,不管同时发多少请求出去。
真实踩坑现象:OSError: [Errno 24] Too many open files 或大量 ClientConnectorError,不是代码写错了,是操作系统层面的 socket 资源被撑爆。
- 必须用
asyncio.Semaphore控制**同时发起的请求数**(比如设为 50) -
TCPConnector(limit=0)可放开连接复用上限,但不等于允许无限并发 - 信号量要放在
session.get()外层,否则起不到限流作用
aiohttp.ClientSession 必须复用,且要配对 async with
每次新建 ClientSession 都会重建 TCP 连接池和 cookie jar,开销远超请求本身。错误写法是每个请求都 ClientSession() —— 这等于把异步退化成“伪并发”,性能反而不如 requests。
正确姿势:整个抓取任务共用一个 session,用 async with session.get(...) 自动管理连接生命周期。
立即学习“Python免费学习笔记(深入)”;
- session 创建开销大,但复用后单次请求的 TCP 握手、TLS 协商可复用
- 务必用
async with,否则连接不释放,Too many open files早晚会来 - 不要在循环里写
async with aiohttp.ClientSession() as session:,那是反模式
超时和异常必须显式处理,不能靠 try/except 包一层就完事
aiohttp 的 timeout 不只是网络超时,还分 connect、sock_read、total。默认 ClientTimeout(total=5) 很危险:遇到慢响应或 DNS 卡顿,整个任务队列可能被拖住。
常见错误是只捕获 Exception,结果 asyncio.TimeoutError、aiohttp.ClientConnectorError、ClientResponseError 全被吞掉,日志里只剩空结果。
- 用
aiohttp.ClientTimeout(total=10, connect=3, sock_read=7)分段设限 - 单独 catch
aiohttp.ClientConnectorError(DNS 或连接失败)、asyncio.TimeoutError(响应太慢)、aiohttp.ClientResponseError(4xx/5xx) - 对 5xx 错误建议重试,但要用
asyncio.sleep加退避,别用time.sleep
并发数不是越高越好,得看目标服务和本地资源
测试过 100 并发在 httpbin 上跑得飞快,换到真实网站可能触发风控、IP 封禁,或因对方 CDN 限速导致实际吞吐不升反降。本地机器也有限制:macOS 默认 ulimit -n 是 256,Linux 一般 1024,超出就会报错。
没有通用最优值,只能实测调整。观察两个指标比调数字更重要:平均响应时间是否陡增、失败率是否超过 5%。
- 从 10 开始试,逐步加到 50、100,每档跑 100 个 URL 看耗时和错误率
- 加
logging.info打点,记录每个请求的 start/end 时间,定位瓶颈是网络还是解析 - 如果目标站有反爬,加随机 delay(
await asyncio.sleep(random.uniform(0.1, 0.5)))比硬扛并发更有效



















