aiohttp异步爬虫代理配置:高并发场景下的IP轮换策略
aiohttp 能通过异步 I/O 同时处理大量网络任务,但并发提高后,代理出口很容易成为新的瓶颈:一个代理被多个协程同时占满、连接池排队时间过长、网络异常与 HTTP 状态码混在一起,都会让失败率快速上升。
本文实现一套适用于授权数据采集、API 测试和多地区页面质量检查的代理轮换方案。这里的轮换是多个合规网络出口之间的负载分配与故障切换,不用于破坏验证流程或规避目标站点规则。
自动化采集前,应确认服务条款、robots.txt、接口授权和频率限制。代理只能改变网络出口,不能替代访问许可。
一、aiohttp代理配置的关键点
根据 aiohttp 3.14.1 官方文档:
- 普通 HTTP 代理可通过请求的
proxy参数设置; - 访问 HTTPS 目标时,通常通过 HTTP
CONNECT建立隧道; - aiohttp 默认不读取
HTTP_PROXY、HTTPS_PROXY等环境变量; - 需要环境变量时,应显式设置
ClientSession(trust_env=True); proxy_auth已弃用,3.14 推荐使用encode_basic_auth()生成代理认证头;- SOCKS 不属于 aiohttp 原生代理能力,需要第三方连接器。
真实项目不应为每个请求创建 ClientSession。Session 内部维护连接池,应在一批任务中复用。
二、代理轮换需要控制三种容量
高并发代理池不是随机选择一个 IP,而是同时限制:
- 连接池总容量:由
TCPConnector(limit=...)控制; - 单个代理容量:防止所有协程集中到一个出口;
- 目标域名并发:即使代理很多,也要遵守目标接口的频率要求。
本文使用代理令牌队列:每个令牌代表一个并发请求,请求结束后归还队列。
三、最小可运行代码
安装依赖:
python -m pip install "aiohttp>=3.14,<4"
import asyncio
import aiohttp
async def fetch(
session: aiohttp.ClientSession,
queue: asyncio.Queue[str],
url: str,
) -> dict[str, object]:
proxy = await queue.get()
proxy_name = proxy.split("@")[-1]
try:
async with session.get(url, proxy=proxy) as response:
await response.content.read(1024)
return {
"proxy": proxy_name,
"status": response.status,
"retry_after": response.headers.get("Retry-After"),
}
except (aiohttp.ClientError, asyncio.TimeoutError) as exc:
return {
"proxy": proxy_name,
"status": None,
"error": type(exc).__name__,
}
finally:
queue.put_nowait(proxy)
async def main() -> None:
queue: asyncio.Queue[str] = asyncio.Queue()
proxies = [
"http://USER:PASSWORD@proxy-a.example.com:8000",
"http://USER:PASSWORD@proxy-b.example.com:8000",
]
# 每个代理两个令牌,单代理最多并发两个请求。
for proxy in proxies:
for _ in range(2):
queue.put_nowait(proxy)
timeout = aiohttp.ClientTimeout(
total=30,
connect=8,
sock_connect=5,
sock_read=20,
)
connector = aiohttp.TCPConnector(
limit=16,
limit_per_host=8,
ttl_dns_cache=300,
)
urls = ["https://httpbin.org/status/200"] * 8
async with aiohttp.ClientSession(
connector=connector,
timeout=timeout,
headers={"User-Agent": "AuthorizedCollector/1.0"},
) as session:
results = await asyncio.gather(
*(fetch(session, queue, url) for url in urls)
)
for result in results:
print(result)
if __name__ == "__main__":
asyncio.run(main())
四、代码中的关键设计
1. 令牌队列限制单代理并发
每个代理放入两个相同令牌,最多允许两个协程同时使用该出口。请求完成或抛出异常后,finally 都会归还令牌。
2. 复用ClientSession
ClientSession 保存连接池和 Cookie。批次内复用 Session 可以减少重复建连,并避免频繁创建 Session 导致资源警告。
3. 分层设置超时
total 控制请求总时间,connect 包含等待连接池的时间,sock_connect 和 sock_read 分别控制建连与读取阶段。连接超时也可能来自连接池排队,并不一定说明代理不可达。
4. HTTP响应和网络错误分开处理
ClientError 与超时会写入 error;403、429、5xx 则保留为 HTTP 状态码。429 应按 Retry-After 降速,403 应检查权限、路径和会话,不能直接判断代理失效。
5. 连续会话固定出口
分页、Cookie 或连续事务不应逐请求轮换。可以在任务组开始时从队列取出一个代理,所有分页完成后再归还。
五、健康检查与轮换规则
| 情况 | 处理建议 |
|---|---|
| 单次传输错误 | 记录并有限重试 |
| 连续传输错误 | 进入冷却,稍后健康检查 |
| 403 | 检查权限、路径和会话 |
| 429 | 按Retry-After降速 |
| 5xx | 有限退避,不直接淘汰代理 |
| 地区不符合预期 | 从对应地区池移除 |
| 分页或连续会话 | 任务组内固定出口 |
六、部署与安全建议
代理账号和密码不应写入源码。建议通过环境变量或密钥管理服务注入,日志只记录代理编号,不输出完整代理 URL、Cookie 和认证头。
需要地区选择、会话保持和稳定出口时,可在工具评估阶段测试网络出口配置的协议兼容、并发容量、地区准确性、P95 延迟和故障响应能力。正式接入前应使用自己的授权接口进行小流量验证。
不要通过 ssl=False 长期关闭证书校验。使用自定义 CA 时,应创建正确的 ssl.SSLContext。大量协程也不等于需要同等数量的连接。
总结
aiohttp 高并发代理调度的重点不是频繁换 IP,而是分别控制连接池总容量、单代理容量和目标域名并发。
轮换只应用于任务分配、地区路由和传输故障;403、429 与解析失败则分别检查权限、频率和页面结构。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)