直接用 aiohttp.ClientSession() 做批量请求出错,因 session 必须与创建它的事件循环绑定;正确做法是用 async with aiohttp.ClientSession() 包裹请求逻辑,配合 asyncio.Semaphore 控制并发(建议5~10),显式处理编码和证书验证。

为什么直接用 aiohttp.ClientSession() 做批量请求会出错?
Python 3.12 默认启用 asyncio.run() 的新事件循环策略(asyncio.WindowsProactorEventLoopPolicy 在 Windows,或 asyncio.DefaultEventLoopPolicy 在 Linux/macOS),而 aiohttp 的 ClientSession 必须在同一个事件循环中创建和使用。常见错误是:在函数外初始化 session = aiohttp.ClientSession(),然后在多个 async 函数里复用——这会导致 RuntimeError: Session is closed 或 RuntimeError: Event loop is closed。
正确做法是让 session 生命周期严格绑定到 async 上下文:
- 用
async with aiohttp.ClientSession() as session:包裹整个批量请求逻辑 - 不要提前创建 session 并传入协程;若需复用,应通过参数传入已打开的 session,并确保调用方负责
async with - 避免在
__init__或模块顶层创建 session 实例
如何控制并发数防止被封或超时?
aiohttp 本身不提供内置并发限制,靠 asyncio.Semaphore 控制同时发起的请求数。设得太大(如 >20)容易触发目标站反爬或服务端拒绝连接;太小(如 1)则失去异步优势。Python 3.12 的 asyncio.to_thread() 不适用于 HTTP 请求,别误用。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 起始并发数建议设为
5~10,根据目标响应时间动态调整 - 用
asyncio.Semaphore(8)包裹每个session.get()调用 - 配合
timeout=aiohttp.ClientTimeout(total=10)防止单个请求拖垮整批任务 - 务必加
raise_for_status()检查 HTTP 状态码,否则 403/429 错误会静默吞掉
怎么安全地处理 JSON 响应和编码问题?
不少接口返回非 UTF-8 编码(如 GBK、ISO-8859-1),而 response.json() 默认按 UTF-8 解码,直接调用会抛 UnicodeDecodeError。Python 3.12 中 aiohttp v3.9+ 已移除自动编码探测,必须显式指定。
推荐写法:
- 先用
await response.read()获取原始 bytes,再用chardet.detect()或charset_normalizer.from_bytes()推测编码(注意:这两个库不是标准库,需pip install charset-normalizer) - 更轻量的做法:对已知接口,用
response.content_type和response.charset判断,例如if response.charset == "gbk": text = await response.text(encoding="gbk") - JSON 解析前,优先用
await response.json(content_type=None)跳过 MIME 类型校验,避免因Content-Type: text/plain导致解析失败
遇到 ClientConnectorCertificateError 怎么办?
这是 Python 3.12 + aiohttp 对 HTTPS 证书验证更严格导致的,尤其在访问自签名、过期或中间证书缺失的站点时。不是简单关掉 SSL 验证就完事——生产环境禁用 ssl=False 会带来中间人风险。
分场景处理:
- 开发调试:在
ClientSession初始化时加connector=aiohttp.TCPConnector(ssl=False),但仅限本地测试 - 内网服务:用
ssl.create_default_context(cafile="/path/to/cert.pem")指定可信 CA 文件,传给TCPConnector(ssl=context) - 临时绕过(不推荐):设置环境变量
SSL_CERT_FILE指向系统证书路径,或升级certifi库(pip install -U certifi)
真正难搞的是某些老系统返回 TLS 1.0 握手或弱加密套件——这时得降级 aiohttp 的 connector 参数,或换用 httpx.AsyncClient(verify=False) 临时过渡。


















