Python aiohttp 异步网络请求实践

7次阅读

必须用 async with aiohttp.clientsession() 管理会话生命周期,显式设置 timeout 防止协程挂起,post json 用 json= 参数而非 data+headers,限制并发数避免连接耗尽或 429 错误。

Python aiohttp 异步网络请求实践

async with aiohttp.ClientSession() 必须用,不能只写 await session.get()

直接 await session.get("https://example.com") 会报 RuntimeError: Session is closed —— 因为 aiohttp.ClientSession 不是“即用即弃”的工具,它管理连接池、cookie、DNS 缓存等资源,必须用 async with 确保正确生命周期。不用上下文管理器,连接可能泄漏,短时间高频请求后出现 Too many open files 或连接超时。

  • 错误写法:session = aiohttp.ClientSession(); await session.get(...); await session.close()(手动 close 容易遗漏或抛异常中断)
  • 正确写法:async with aiohttp.ClientSession() as session: await session.get(...)
  • 如果要复用 session(比如多个请求),把它作为参数传入协程,或放在类实例中,但别在函数里反复创建/关闭

timeout 参数不设就等于没设,aiohttp 默认不超时

aiohttpClientTimeout 默认是 None,意味着 DNS 查询、TCP 握手、TLS 握手、首字节等待、整个响应读取……全都不设限。线上服务一旦上游卡住,你的协程就永远挂起,拖垮整个 asyncio Event loop。

  • 必须显式配置:timeout=aiohttp.ClientTimeout(total=10, connect=3, sock_read=5)
  • total 是总上限,其他子项不能超过它;connect 控制建连阶段(含 DNS),sock_read 控制从 socket 读响应体的时间
  • 注意:如果用 raise_for_status=True,HTTP 4xx/5xx 错误仍会抛异常,但超时是独立机制,二者不冲突

POST 发 JSON 数据,别手动 json.dumps + headers,用 json= 参数

session.post(url, data=json.dumps({...}), headers={"Content-Type": "application/json"}) 看似没问题,实则埋雷:手动序列化可能忽略 ensure_ascii=False 导致中文乱码;headers 写错大小写或漏掉 charset;更严重的是,data= 会绕过 aiohttp 对 JSON 的自动编码和 content-type 设置逻辑,某些服务器(尤其 fastapi/Starlette)会拒绝解析。

  • 正确做法:await session.post(url, json={"key": "中文值"}) —— aiohttp 自动处理编码、header、content-type
  • 如果需要控制 JSON 序列化选项(如 default 函数),得自己构造 json.dumps(...),但此时必须配 data= + 手动 header,且确保 Content-Typeapplication/json; charset=utf-8
  • json=data= 互斥,同时传会报 ValueError

并发请求数量失控导致 ConnectionRefusedError 或 429

asyncio.gather(*[session.get(...) for _ in range(1000)]) 看起来高效,实际会让 aiohttp 在默认配置下瞬间发起上千 TCP 连接,远超系统文件描述符限制或目标服务器承受能力,结果不是本地报 [errno 24] Too many open files,就是对方返回 429 Too Many Requests 或直接 ConnectionRefusedError

立即学习Python免费学习笔记(深入)”;

  • 限制并发数:用 asyncio.Semaphore(10) 包裹请求逻辑,确保最多 10 个并发
  • ClientSession 默认启用连接池(connector=aiohttp.TCPConnector(limit=100, limit_per_host=30)),但这个 limit 是“最大空闲连接数”,不是并发请求数,别混淆
  • 对同一 host 大量请求,建议调高 limit_per_host,否则可能因连接复用不足反复建连

aiohttp 的异步行为高度依赖底层 connector 和 event loop 配置,真正上线前一定要压测并观察连接状态(netstat -an | grep :443 | wc -l)、timeouts 日志、以及是否出现 Unclosed client session 警告。

text=ZqhQzanResources