Python异步代理池实战:从requests阻塞到httpx.AsyncClient,爬虫效率翻倍的踩坑记录

发布时间:2026/7/5 1:37:25

Python异步代理池实战:从requests阻塞到httpx.AsyncClient,爬虫效率翻倍的踩坑记录 一、起因代理验证拖垮了整个采集系统先交代一下背景。我在一家电商公司做数据采集核心系统是竞品价格监控——每天爬天猫、京东、拼多多的商品价格日采集量在几十万到百万级。刚开始做的时候代理管理这块是比较粗糙的——抓了一批免费代理存Redis里爬虫取的时候随机拿一个挂了就换。看起来能跑但实际上问题很大。最头疼的是代理验证。当时的逻辑是这样的用requests库写了个定时任务每隔几分钟遍历一遍Redis里的代理挨个发请求验证是否存活。问题出在哪呢requests是同步阻塞的。假设池子里有500个代理每个代理验证超时设5秒光一轮验证就得跑2500秒——不现实。实际把超时压到1秒但就算这样500个代理串行验证也要差不多一分钟。更糟的是这一分钟里爬虫还在取代理可能取到的就是还没验证到的失效代理。后来代理池慢慢从免费切换到付费稳定了一些但体量从几百涨到上千之后验证延迟的问题又回来了。二、第一次尝试用aiohttp做异步验证第一个想到的方案是用aiohttp。异步并发验证一次发几十个请求出去比串行快太多了。import aiohttp import asyncio async def validate_proxy(session, proxy): try: async with session.get( http://httpbin.org/ip, proxyproxy, timeoutaiohttp.ClientTimeout(total3) ) as resp: return proxy if resp.status 200 else None except: return None async def batch_validate(proxies): async with aiohttp.ClientSession() as session: tasks [validate_proxy(session, p) for p in proxies] results await asyncio.gather(*tasks, return_exceptionsTrue) return [r for r in results if r is not None]改成异步之后验证1000个代理大概十几秒就搞定了效率提升很明显。但这个方案用了一段时间后发现了一个尴尬的问题aiohttp不支持SOCKS5代理。我们有一部分代理是SOCKS5协议的aiohttp原生不支持得用aiohttp-socks这个第三方库多了一层依赖不说偶尔还会有兼容性问题。三、最终方案httpx.AsyncClient后来全面切到了httpx。httpx的API和requests几乎一模一样但支持异步而且原生支持SOCKS5代理。核心改动就是把requests换成httpx.AsyncClientimport httpx import asyncio async def validate_proxy(client, proxy_url): try: resp await client.get( http://httpbin.org/ip, proxyproxy_url, timeout3.0 ) if resp.status_code 200: # 记录响应延迟 elapsed resp.elapsed.total_seconds() return {proxy: proxy_url, latency: elapsed} except Exception: pass return None async def validate_all(proxy_list, concurrency30): sem asyncio.Semaphore(concurrency) async def bounded_validate(p): async with sem: async with httpx.AsyncClient() as client: return await validate_proxy(client, p) tasks [bounded_validate(p) for p in proxy_list] results await asyncio.gather(*tasks) return [r for r in results if r is not None]四、一个容易忽略的坑事件循环里的同步代码这里有个细节特别容易踩——不要以为写了async函数就万事大吉了。我在做代理网关的时候路由层用了FastAPI的async但是底层调代理验证还傻乎乎用requests同步库。代码看起来没问题压测的时候QPS一直上不去。排查了半天才反应过来requests.get()把整个事件循环都堵死了async等于白写。结论用了async就全线用异步库。千万别混用同步库。五、代理延迟过滤快和准的平衡异步验证跑起来之后发现另一个问题验证通过不代表好用。有些代理能连通但响应时间动不动就5秒以上。爬虫那边对时效有要求——价格数据晚几分钟就失去参考价值了。所以只验证存活不够还得过滤掉慢的。加了一个延迟阈值过滤def filter_by_latency(results, max_latency3.0): 只保留响应时间在阈值以内的代理 fast [r for r in results if r[latency] max_latency] slow len(results) - len(fast) print(f过滤前: {len(results)} 个, 过滤后: {len(fast)} 个 (剔除 {slow} 个高延迟)) return fast阈值设了3秒。设完之后可用代理数量大概少了三四成但实际采集成功率反而上去了——因为爬虫不再在慢代理上浪费时间。这算是我踩过的一个认知偏差代理数量重要但代理质量更重要。六、验证端点的选择验证代理能不能用需要发一个请求确认。很多人用httpbin.org我也用了很长一段时间。后来发现有些代理莫名其妙连不上httpbin排查下来是代理服务商那边可能屏蔽了某些常用测试站点。后来自己在服务器上搭了个最简单的验证端点就返回一个200和一个JSON彻底避免了这个问题。# 最简单的验证端点 (FastAPI) from fastapi import FastAPI app FastAPI() app.get(/ping) async def ping(): return {status: ok}七、实际效果切换前后的对比基于我们系统的实际数据做了约数处理指标requests串行httpx异步1000个代理验证耗时约60-90秒约10-15秒代理有效率约60%约60%延迟过滤后约85%可用采集任务平均响应约800-1200ms约200-400ms每日有效采集量约40-50万约70-80万这里要说明一下代理有效率本身不会因为换异步库就提高——代理还是那些代理能不能用是由代理本身决定的。异步验证的价值在于更快地发现失效代理、更快地剔除慢代理从而提升整体的采集效率。八、总结回头看整个过程有几个体会1. 异步不是银弹但代理管理这个场景确实适合异步。代理验证是典型的IO密集型任务几千个代理挨个发请求的场景异步的价值特别明显。2. httpx是当下做代理管理的一个好选择。API和requests一致学习成本低原生异步支持SOCKS5代理原生可用HTTP/2支持更好。3. 关注延迟不只是存活。能连通的代理不等于好代理。加一个延迟过滤虽然代理池看起来瘦了但爬虫整体效率反而是提升的。4. 验证端点自己搭一个。别依赖第三方服务稳定性不可控。以上是我在代理池管理上的一些经验。如果你也在做类似的系统欢迎评论区交流。

相关新闻