直接用aiohttp+asyncio实现代理验证会卡死,因未配置超时、连接池限制及dns缓存,导致dns解析阻塞、tcp连接耗尽、ssl握手堆积,拖慢甚至假死事件循环。

为什么直接用 aiohttp + asyncio 实现代理验证会卡死?
因为默认的 aiohttp.ClientSession() 没配超时和连接池限制,大量并发验证时 DNS 解析阻塞、TCP 连接耗尽、SSL 握手堆积,最终 asyncio 事件循环被拖慢甚至假死。常见现象是:前 20 个代理能跑完,后面全 hang 在 await session.get(...)。
- 必须显式设置
timeout=<code>aiohttp.ClientTimeout(total=5),不能只靠timeout=5参数(那是旧版写法) - 连接池要限流:
connector=aiohttp.TCPConnector(limit=100, limit_per_host=5, ttl_dns_cache=300)——limit_per_host防止单个代理 IP 被打爆,也避免触发目标站的频率拦截 - DNS 缓存开启(
ttl_dns_cache)能减少重复解析开销,实测提升 15%+ 吞吐量
如何让代理池自动剔除失效节点并维持最小可用数?
靠定时轮询 + 状态缓存,而不是每次请求都验证。核心是维护一个带 TTL 的内存字典:proxy_status = {"http://u:p@ip:port": {"ok": True, "last_check": 1717023456, "fail_count": 0}}。
- 验证失败时递增
fail_count,≥3 次就标记为不可用,并从可用列表中移除 - 每次取代理前检查
last_check是否超时(比如 60 秒),超时则异步触发单次验证,不阻塞主流程 - 后台任务每 30 秒扫描一次,对
fail_count > 0且距上次检查 > 300 秒的代理发起复检 - 不要用 Redis 做状态存储——高并发下 Lua 脚本争抢和网络延迟反而成为瓶颈;纯内存 + 定时同步到磁盘(如
json.dump)更稳
requests 和 aiohttp 在代理池里能混用吗?
不能在同一个 asyncio 事件循环里混用。一旦你在协程里调了 requests.get(),整个 loop 就会被阻塞,吞吐量断崖下跌。曾有项目因某处漏掉异步替换,QPS 从 1200 掉到 80。
- 所有代理验证、健康检查、上游请求必须统一走
aiohttp或httpx.AsyncClient(后者对 HTTP/2 和连接复用更友好) - 如果业务代码已重度依赖
requests,用asyncio.to_thread(requests.get, ...)包一层,但仅限低频管理接口(如配置更新),绝不能用于代理验证主路径 - 注意
aiohttp不支持 socks 代理,要用httpx或aiosocks—— 但 socks5 代理本身延迟高,建议优先过滤掉
为什么代理池返回的随机代理总是集中在少数几个 IP?
因为没做权重衰减。刚验证成功的代理被反复选取,而其他代理长期闲置导致过期,形成“马太效应”。真实场景中,200 个代理里常有 15 个扛下 70% 流量,很快被封。
- 用加权轮询替代纯随机:
random.choices(list(proxies), weights=[1/(status[p]['fail_count']+1) for p in proxies]) - 每次成功使用后,给该代理的
last_used时间戳 + 权重临时衰减(比如 30 秒内权重 ×0.5) - 对连续成功 ≥5 次的代理,主动降权(避免被盯上),哪怕它响应最快
- 记录每个代理的平均响应时间,但别直接按速度排序——快的容易被识别为爬虫,要加抖动:实际权重 =
1 / (rtt + random.uniform(0.1, 0.5))
代理池真正的难点不在并发,而在行为模拟。IP 失效只是表象,背后是服务端对请求模式的识别。越追求“高性能”,越要克制——留出空档、打乱节奏、容忍部分低效,系统才活得久。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











