aiohttp 异步爬虫如何配置代理?连接池复用与超时控制实战

一、为什么异步爬虫需要代理?

同步爬虫(如 requests)在并发量上来后,往往需要开大量线程,资源消耗大且效率低。aiohttp 基于 asyncio,单线程即可处理数百个并发请求,非常适合大规模数据采集。

但大规模请求会带来两个问题:

  1. 单 IP 请求过于集中,容易触发目标网站的频率管理机制。

  2. 网络链路不稳定,单个请求超时可能拖慢整个任务。

代理 IP 可以分散请求来源,提高采集的稳定性和成功率。而 aiohttp 的代理配置与同步库不同,需要正确设置连接池和超时。

二、aiohttp 代理基础配置

aiohttp 通过 session.get() 或 session.post() 的 proxy 参数指定代理地址。代理地址格式为 http://user:pass@host:port。

python 复制代码
import aiohttp
import asyncio

PROXY = "http://user:pass@your-proxy-gateway.com:8080"

async def fetch(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url, proxy=PROXY, timeout=aiohttp.ClientTimeout(total=10)) as resp:
            return await resp.text()

async def main():
    html = await fetch("https://httpbin.org/ip")
    print(html)

if __name__ == "__main__":
    asyncio.run(main())

如果需要认证,推荐使用 aiohttp.BasicAuth:

python 复制代码
import aiohttp

PROXY_URL = "http://your-proxy-gateway.com:8080"
PROXY_AUTH = aiohttp.BasicAuth("username", "password")

async def fetch(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url, proxy=PROXY_URL, proxy_auth=PROXY_AUTH) as resp:
            return await resp.text()

对于 SOCKS5 代理,需要安装 aiohttp-socks,使用 ProxyConnector:

python 复制代码
from aiohttp_socks import ProxyConnector
import aiohttp

async def fetch_with_socks(url):
    connector = ProxyConnector.from_url("socks5://user:pass@your-proxy-gateway.com:1080")
    async with aiohttp.ClientSession(connector=connector) as session:
        async with session.get(url) as resp:
            return await resp.text()

三、连接池复用:减少握手开销

aiohttp.ClientSession 内部维护连接池。每次请求都新建 Session 是性能杀手,因为每次都要重新建立 TCP 连接和 TLS 握手。

正确做法:整个采集周期复用一个 Session,并配置 TCPConnector。

python 复制代码
import aiohttp
import asyncio

async def main():
    connector = aiohttp.TCPConnector(
        limit=200,                # 总连接数上限
        limit_per_host=20,        # 每个主机最大连接数
        keepalive_timeout=30,     # 空闲连接保持 30 秒
        ttl_dns_cache=300,        # DNS 缓存 5 分钟
        ssl=False,                # 测试环境可关闭 SSL 验证
    )
    timeout = aiohttp.ClientTimeout(total=30, connect=10, sock_read=15)
    async with aiohttp.ClientSession(connector=connector, timeout=timeout) as session:
        tasks = [fetch(session, url) for url in urls]
        await asyncio.gather(*tasks)

async def fetch(session, url):
    async with session.get(url, proxy=PROXY) as resp:
        return await resp.text()

关键参数说明:

  • limit:整个连接池的最大连接数。

  • limit_per_host:每个目标主机的最大连接数。对于代理网关,每个代理地址算一个主机。

  • keepalive_timeout:空闲连接保持时间,太长会占用资源,太短会频繁重建。建议 10--30 秒。

  • ttl_dns_cache:DNS 缓存时间,减少解析开销。

连接复用陷阱 :同一个 IP 高频复用 TCP 连接可能被识别为异常。建议将 keepalive_timeout 设为 5--10 秒,或定期更换代理。

四、超时控制:避免慢请求拖垮并发

aiohttp.ClientTimeout 提供四个超时字段:

字段 说明 推荐值
total 整个请求的总超时 30 秒
connect 建立连接(TCP + TLS)超时 10 秒
sock_connect TCP 连接超时 10 秒
sock_read 两次数据读取之间的最大间隔 15 秒

最容易混淆的是 sock_read :它计算的是两次 socket read 之间的间隔,而不是整个响应读取时间。如果服务器先返回响应头,然后 20 秒后才返回 body,sock_read=15 会触发超时。

代理场景下,connect 阶段涉及"客户端→代理→目标网站"的多段握手,因此 connect 需要比直连场景调大一些。

python 复制代码
DEFAULT_TIMEOUT = aiohttp.ClientTimeout(
    total=30,
    connect=10,
    sock_read=15,
)

必须设置 total,否则慢请求会长期占用连接池,导致后续请求排队。

五、完整实战:轮换代理 + 并发控制

下面是一个完整的异步爬虫示例,使用轮换代理、连接池复用、超时控制,并限制并发数。

python 复制代码
import asyncio
import aiohttp
import random
from typing import Optional

PROXIES = [
    "http://user:pass@your-proxy-gateway.com:8080",
    "http://user:pass@your-proxy-gateway.com:8081",
    "http://user:pass@your-proxy-gateway.com:8082",
]

class AsyncProxyCrawler:
    def __init__(self, proxies, concurrency=20):
        self.proxies = proxies
        self.semaphore = asyncio.Semaphore(concurrency)
        self.session: Optional[aiohttp.ClientSession] = None

    async def __aenter__(self):
        connector = aiohttp.TCPConnector(
            limit=200,
            limit_per_host=10,
            keepalive_timeout=10,
            ttl_dns_cache=300,
            ssl=False,
        )
        self.session = aiohttp.ClientSession(
            connector=connector,
            timeout=aiohttp.ClientTimeout(total=30, connect=10, sock_read=15),
            headers={
                "User-Agent": (
                    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                    "AppleWebKit/537.36 (KHTML, like Gecko) "
                    "Chrome/120.0.0.0 Safari/537.36"
                ),
            },
        )
        return self

    async def __aexit__(self, *args):
        if self.session:
            await self.session.close()

    def _pick_proxy(self):
        return random.choice(self.proxies)

    async def fetch(self, url: str, retries: int = 3) -> Optional[str]:
        for attempt in range(retries):
            proxy = self._pick_proxy()
            try:
                async with self.semaphore:
                    async with self.session.get(url, proxy=proxy) as resp:
                        if resp.status == 200:
                            return await resp.text()
                        if resp.status in (429, 503):
                            await asyncio.sleep(2 ** attempt)
                            continue
                        return None
            except asyncio.TimeoutError:
                continue
            except aiohttp.ClientError:
                continue
        return None

async def main():
    urls = [f"https://httpbin.org/ip?idx={i}" for i in range(50)]
    async with AsyncProxyCrawler(PROXIES, concurrency=10) as crawler:
        tasks = [crawler.fetch(url) for url in urls]
        results = await asyncio.gather(*tasks, return_exceptions=True)
        success = sum(1 for r in results if isinstance(r, str))
        print(f"成功: {success}/{len(urls)}")

if __name__ == "__main__":
    asyncio.run(main())

代码要点:

  • 复用 ClientSession。

  • TCPConnector 控制连接池。

  • ClientTimeout 分层超时。

  • asyncio.Semaphore 限制并发。

  • 轮换代理,失败重试。

六、常见问题(FAQ)

Q:aiohttp 支持 SOCKS5 代理吗?

A:原生不支持,需要安装 aiohttp-socks,使用 ProxyConnector。

Q:为什么设置了 proxy 参数,请求还是走本地 IP?

A:检查代理地址格式是否正确(需带 http://),认证信息是否通过 proxy_auth 独立传递,以及环境变量是否覆盖了配置。

Q:sock_read 和 total 有什么区别?

A:sock_read 是两次数据读取之间的最大间隔,total 是整个请求的总超时。必须同时设置。

Q:连接池的 limit_per_host 对代理场景有什么影响?

A:如果使用网关式代理,所有请求都发往同一个网关地址,limit_per_host 限制的是通过该网关的最大并发。需要根据网关承载能力设置。

Q:如何选择轮换会话和粘性会话?

A:批量独立请求用轮换,需要登录态或连续翻页用粘性。B2Proxy 的住宅代理支持地区和 ASN 定向,并提供轮换与粘性会话选项,可以根据任务类型灵活配置。

总结

aiohttp 异步爬虫的代理配置,核心在于三点:

  1. 代理配置 :用 proxy 参数传地址,用 proxy_auth 传认证。SOCKS5 需要 aiohttp-socks。

  2. 连接池复用 :复用 ClientSession,合理设置 TCPConnector 的 limit、limit_per_host、keepalive_timeout。

  3. 超时控制 :使用 ClientTimeout 分层超时,total 必须设置,sock_read 不要过短。

把这三点做好,异步爬虫的稳定性和效率都会有明显提升。对于需要多地区采集的任务,可以选择支持地区和 ASN 定向的住宅代理服务,配合轮换与粘性会话,进一步提升数据采集的稳定性。

相关推荐
SEO_juper1 小时前
给网页做可引用性评分:判断内容够不够让 AI 引用(GEO 工具,附代码)
开发语言·人工智能·爬虫·python·seo·独立站
雪雪爱冲浪2 小时前
JavaScript 网络爬虫教程(2026)
开发语言·javascript·爬虫
要吃这碗饭2 小时前
JavaScript 网络爬虫教程(2026)
开发语言·javascript·爬虫
重生之高冷汉堡包3 小时前
JavaScript 网络爬虫教程(2026)
大数据·网络·爬虫·安全
深蓝电商API4 小时前
爬虫任务调度:从crontab到Airflow的进阶路线
爬虫·crontab·airflow
梦帮科技19 小时前
Kali Linux Scapy 畸形报文构造学——深度解剖非法 TCP Flags 与反常序列号
linux·爬虫·神经网络·算法·数学建模·系统架构·模拟退火算法
深蓝电商API21 小时前
住宅代理、机房代理、原生IP:2026选型指南
爬虫·原生ip·住宅代理·机房代理
惊讶的猫21 小时前
2-爬虫翻页
爬虫
惊讶的猫1 天前
python爬虫入门
爬虫