aiohttp的Connector必须显式配置,否则每次新建ClientSession会创建独立连接池,导致DNS重复解析、TCP握手未复用、TIME_WAIT堆积;应复用session或connector,设limit_per_host防风控,缩短ttl_dns_cache避免DNS缓存失效,并确保loop生命周期内正确关闭session。

为什么 aiohttp 的 Connector 必须显式配置?
Python 3.11 默认的 aiohttp.TCPConnector 连接池大小是 100,但这是「并发连接数上限」,不是「默认复用行为」。不显式传入 connector,每次新建 ClientSession 都会创建独立连接池,导致 DNS 解析重复、TCP 握手未复用、TIME_WAIT 连接堆积——尤其在短生命周期 session(如函数内临时创建)时非常明显。
实操建议:
- 始终复用
ClientSession实例,或至少复用同一个connector实例 - 设置
limit匹配目标站点承受能力(通常 20–50 更稳妥,不是越大越好) - 启用
keepalive_timeout=30(默认 15s),避免频繁重连 - 加
force_close=False(默认值,但显式写出更安全)
connector = aiohttp.TCPConnector(
limit=30,
limit_per_host=5,
keepalive_timeout=30,
force_close=False
)
session = aiohttp.ClientSession(connector=connector)
limit_per_host 比 limit 更关键?
只设 limit=100 但没设 limit_per_host,所有请求可能压向同一域名(比如爬取分页时全是 api.example.com/page=1、page=2…),触发服务端限流或被封 IP。而 limit_per_host 控制单域名并发连接数,才是防触发风控的核心参数。
常见错误现象:
立即学习“Python免费学习笔记(深入)”;
- 请求突然大量超时,但
status=429或空响应增多 - 抓取速度先快后慢,且
netstat -an | grep :443 | wc -l显示大量TIME_WAIT - 同一 host 的请求排队等待,
connector._conns中该 host 的空闲连接为 0
推荐值:对中小型 API,设 limit_per_host=3–5;若目标明确支持高并发(如自建测试接口),可放宽到 8–10,但务必配合随机 delay。
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
如何避免 DNS 缓存失效导致的连接池浪费?
aiohttp 默认每 10 分钟刷新一次 DNS 缓存(use_dns_cache=True),但若爬虫运行中目标域名解析结果变更(如负载均衡切节点),旧连接仍会复用已过期的 IP,造成部分请求失败或延迟飙升——此时连接池里一堆“健康但不可达”的连接。
解决方式:
- 设
ttl_dns_cache=60(单位秒),缩短 DNS 缓存周期 - 搭配
family=socket.AF_INET强制 IPv4,避开某些 DNS 返回 IPv6 导致握手慢的问题 - 若使用代理,确保代理本身 DNS 缓存可控(如
httpx的trust_env=False类似逻辑)
connector = aiohttp.TCPConnector(
...,
use_dns_cache=True,
ttl_dns_cache=60,
family=socket.AF_INET
)
Python 3.11 下 asyncio.run() 与连接池生命周期冲突?
在每个任务里写 asyncio.run(main()),会导致每次调用都新建 event loop,而 connector 内部的连接清理依赖 loop 关闭时的 __aexit__。结果就是连接不释放,TIME_WAIT 持续累积,几分钟后系统报 OSError: [Errno 24] Too many open files。
必须改为:
- 整个爬虫共用一个 event loop(主函数用
asyncio.run()包一层即可) -
ClientSession和connector在 loop 启动后创建,结束前await session.close() - 绝对不要在子协程里调
asyncio.run()
容易被忽略的是:即使用了 async with session:,若 session 创建在 asyncio.run() 外部作用域,loop 关闭时它可能已被 GC 掉,close 未执行——所以显式 await session.close() 更可靠。

















