Python 3.12中如何实现高性能异步抓取_使用aiohttp库配合asyncio提升并发
Python 3.12 下 aiohttp 并发易卡住,因默认事件循环策略与 TCPConnector 对 DNS 缓存、SSL 验证、连接复用更敏感,需显式配置 limit、limit_per_host、ttl_dns_cache、force_close 及分层 timeout,否则触发连接耗尽、握手失败或超时堆积。
aiohttp + asyncio 在 Python 3.12 中依然能稳定实现高并发抓取,但默认行为和部分底层细节有实际影响——比如连接复用策略、DNS缓存、SSL验证开销,以及 asyncio.run() 的默认事件循环策略变化。不调整就直接套用旧代码,容易在高并发下触发连接耗尽、超时堆积或 TLS 握手失败。
为什么 Python 3.12 下 aiohttp 并发容易卡住?
Python 3.12 默认使用 asyncio.WindowsProactorEventLoopPolicy(Windows)或 asyncio.DefaultEventLoopPolicy(Linux/macOS),但 aiohttp 的 TCPConnector 在高并发场景下对 DNS 缓存、连接复用、SSL 验证更敏感。常见现象包括:
-
ClientConnectorError: Cannot connect to host ... ssl=None—— SSL 握手被批量拒绝,尤其在目标站强制 HTTPS 且未显式关闭验证时 - 大量
TimeoutError: [WinError 10060]或ServerDisconnectedError—— 连接池未合理限流,触发服务端限速或防火墙拦截 - 请求吞吐量随并发数上升反而下降 —— DNS 解析阻塞或 TCP 连接复用失效,导致重复建连
根本原因不是库不兼容,而是旧配置在新版本中“放大了缺陷”。
必须显式配置的 TCPConnector 参数
不要依赖 aiohttp.ClientSession() 的默认连接器。以下参数直接影响并发稳定性:
-
limit=300:总并发连接上限,设太高(如 1000)易触发系统级EMFILE错误;设太低(如 10)又无法压满带宽 -
limit_per_host=30:单域名最大连接数,避免被识别为攻击;多数公开 API 要求 ≤20 -
ttl_dns_cache=300:DNS 缓存 5 分钟,防止高频重解析拖慢请求启动 -
force_close=False:保持连接复用(HTTP/1.1 keep-alive),否则每个请求都重建 TCP+TLS 开销翻倍 -
ssl=False(仅调试用):跳过证书验证可缓解部分握手失败,但生产环境应配trust_env=True或自定义ssl_context
示例:
connector = aiohttp.TCPConnector(
limit=200,
limit_per_host=25,
ttl_dns_cache=300,
force_close=False,
enable_cleanup_closed=True
)
并发控制别只靠 asyncio.Semaphore
asyncio.Semaphore 控制的是“协程并发数”,不是“连接并发数”。若你设了 Semaphore(50),但 TCPConnector 的 limit_per_host=10,那最多只有 10 个连接真正发出请求,其余 40 个协程在排队等空闲连接——看起来没卡,实则吞吐被 connector 卡死。
Clipfly
一站式AI视频生成和编辑平台,提供多种AI视频处理、AI图像处理工具。
下载
- 先按目标站点的反爬强度设
limit_per_host(通常 5~30) - 再设
Semaphore值 ≥limit_per_host,否则语义冗余 - 对多个不同域名批量抓取时,
limit_per_host比Semaphore更关键
错误写法:
sem = asyncio.Semaphore(100) # 看似并发高,但 connector 只放行 10 个连接
async with sem:
async with session.get(url) as resp:
...
Python 3.12 下 timeout 必须分层设置
3.12 对 socket 层超时更严格,ClientTimeout(total=30) 不再隐式覆盖底层读写超时。漏设会导致协程挂起数分钟才报错。
-
total:整个请求生命周期上限(含 DNS、connect、read) -
connect:DNS + TCP 握手时间,建议 ≤5s,避免 DNS 故障拖垮整批请求 -
sock_read:从 socket 读响应体的单次等待上限,设 10~15s 较稳妥 -
sock_connect:TCP 连接建立超时,与connect功能重叠,3.12 推荐统一用connect
推荐配置:
timeout = aiohttp.ClientTimeout(
total=25,
connect=4,
sock_read=12
)
最常被忽略的一点:Python 3.12 的 aiohttp 在处理大量 https 请求时,SSL 上下文初始化成本变高。如果没复用 ClientSession 或反复新建 connector,CPU 会卡在 TLS 握手准备阶段——这时候看日志全是超时,但实际是卡在本地,不是网络问题。