aiohttp本身不“自动高效”,必须配TCPConnector限流、ClientSession复用、asyncio.Semaphore控并发,否则并发一高就触发ClientOSError: [Errno 24] Too many open files或被目标站封IP。

直接说结论:aiohttp本身不“自动高效”,必须配TCPConnector限流、ClientSession复用、asyncio.Semaphore控并发,否则并发一高就触发ClientOSError: [Errno 24] Too many open files或被目标站封IP。
为什么aiohttp并发一多就报“Too many open files”
默认aiohttp.TCPConnector不限制连接数,每个session.get()都可能新建TCP连接;Linux系统默认单进程文件描述符上限是1024,500个并发请求+DNS/SSL等开销很容易打穿。这不是aiohttp的bug,是没显式配置连接池导致的资源失控。
-
limit控制全局最大连接数(建议设为50~200,别设0) -
limit_per_host更关键:防止单域名瞬间发几百请求被限流(例如设30) - 务必搭配
asyncio.Semaphore——limit管底层连接,Semaphore管上层协程并发数,二者缺一不可 - 漏掉
await session.close()会导致连接不释放,下次ClientSession()会继续开新连接
如何正确启动和管理ClientSession
ClientSession不是工具函数,是带状态的连接池管理器。它必须在同一个async with块里创建、使用、关闭,不能在循环里反复new,也不能跨async def函数传递后忘记关。
- 错误写法:
session = aiohttp.ClientSession()然后传进多个fetch()——没async with,没close(),连接泄漏 - 正确结构:外层
async with aiohttp.ClientSession(...) as session:,所有session.get()都在这个上下文内 - 如果要复用session做多次批量请求,每次都要重新构建
tasks列表,不要试图把session塞进全局变量 - 超时必须显式设
ClientTimeout(total=10),否则默认5分钟,某个挂掉的请求会让整个gather卡死
怎么处理HTTP状态码误判和反爬响应
aiohttp默认对403、429、503等状态码不抛异常,resp.status是403你也照样拿到响应体。很多反爬页面返回200但HTML里是验证码或跳转登录页,代码却当成“成功”继续解析。
- 每次
await response.text()前先检查if resp.status != 200:,按需raise或记录 - 对关键字段做二次校验:比如预期有
,结果text[:200]里是"请登录"或"verify",直接标为失败 - 别依赖
raise_for_status()——它只对4xx/5xx抛ClientResponseError,但有些反爬返回200+伪装内容 - 加
headers={'User-Agent': 'xxx'}和cookies(如需)避免被当爬虫拦截
批量请求时gather和as_completed怎么选
asyncio.gather(*tasks)按输入顺序返回结果,适合后续要严格对应URL列表的场景;asyncio.as_completed(tasks)按完成顺序返回,适合边抓边存、不关心顺序的流式处理。
- 用
gather时加return_exceptions=True,否则一个请求失败整个任务就中断 - 用
as_completed时记得await coro取结果,别直接打印coro对象 - 两者都不该嵌套在
for await里逐个等——那等于退化成串行,失去异步意义 - 真正压测时,优先观察目标站响应头里的
X-RateLimit-Remaining或Retry-After,比硬调并发数更可靠
最易忽略的一点:DNS解析在高频请求下会成为瓶颈,默认用系统getaddrinfo是同步阻塞的。高并发时得换AsyncResolver,否则session.get()还没发出去就卡在域名解析上。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











