必须用 aiohttp.clientsession() 并配合 async with 管理生命周期,否则因事件循环关闭导致 runtimeerror;需配置连接池、超时、重定向等细节才能真正发挥异步性能。

为什么不用 aiohttp.ClientSession() 会报 RuntimeError: Event loop is closed
直接调用 aiohttp.get() 或 aiohttp.post()(旧版 API)看似简单,但它们内部每次都会新建一个 ClientSession,且不保证正确关闭。Python 3.8+ 默认策略下,事件循环在主协程退出后立即关闭,而 session 还没来得及清理资源,就会抛出这个错误。
正确做法是显式创建、复用并手动关闭 ClientSession:
import aiohttp
import asyncio
<p>async def fetch():
async with aiohttp.ClientSession() as session:
async with session.get("<a href="https://www.php.cn/link/4d2fe2e8601f7a8018594d98f28706f2">https://www.php.cn/link/4d2fe2e8601f7a8018594d98f28706f2</a>") as resp:
return await resp.text()</p><p>asyncio.run(fetch())</p>
- 必须用
async with确保 session 正确初始化和释放 - 不能把
ClientSession()实例定义在 async 函数外再传入——它绑定当前事件循环,跨协程/线程复用会出错 - 如果需要长期复用(比如 Web 服务中),应将 session 存为全局变量或依赖注入,但务必确保其生命周期覆盖所有请求,并在应用退出前调用
.close()
POST 表单 vs JSON:headers 和 data 参数怎么配才不 400
session.post() 的 data 参数行为取决于你是否手动设了 Content-Type。不设时,aiohttp 默认按 application/x-www-form-urlencoded 处理字典;设成 application/json 后,却仍传字典,服务器就收不到合法 JSON。
常见错误写法:
# ❌ 错误:header 写了 json,data 却是 dict
await session.post(url, headers={"Content-Type": "application/json"}, data={"key": "val"})
<h1>✅ 正确:json 参数自动序列化 + 设 header(推荐)</h1><p>await session.post(url, json={"key": "val"})</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill7154" title="python-pro"><img
src="https://img.php.cn/upload/skill/000/000/081/179134208595348.jpg" alt="python-pro" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill7154" title="python-pro" class="overflowclass">python-pro</a>
<p class="overflowclass">高级 Python 特性、异步编程、性能调优、静态类型、内存管理、Python 内部机制及生态库方面的专家。</p>
</div>
<a rel="nofollow" href="/xiazai/skill7154" title="python-pro" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div><h1>✅ 或手动处理(需 import json)</h1><p>await session.post(url, data=json.dumps({"key": "val"}), headers={"Content-Type": "application/json"})</p>
-
json=参数会自动设 header 并调用json.dumps(),比手拼更安全 -
data=接收 bytes、str、文件对象等原始载荷,不会自动编码,适合上传二进制或自定义格式 - 表单提交用
data=传dict即可,无需额外 header
并发 100 个请求卡死?得控速 + 处理连接池
直接 asyncio.gather(*[fetch(i) for i in range(100)]) 会瞬间发起全部请求,触发连接数超限、远端限流或本地文件描述符耗尽(尤其 Linux 默认 ulimit 较低)。
解决靠两点:连接池限制 + 并发控制。
connector = aiohttp.TCPConnector(limit=30, limit_per_host=5)
async with aiohttp.ClientSession(connector=connector) as session:
tasks = [fetch(session, i) for i in range(100)]
await asyncio.gather(*tasks)
-
limit控制总并发连接数(默认 100),建议设为 20–50 避免打崩自己或目标站 -
limit_per_host防止单域名请求过密,符合 RFC 合理性,也减少被封概率 - 不要依赖
asyncio.Semaphore做二次限速——它只锁协程,不控底层 socket,容易绕过连接池 - 若需精细调度(如优先级、失败重试),用
asyncio.create_task()+ 手动 await 更可控
响应体大、超时、重定向这些细节不设好,异步就白写了
异步不是万能的,一个没设超时的请求卡住,整个协程就挂起。默认重定向不限次数,遇到跳转环直接死循环。
- 必设
timeout:用aiohttp.ClientTimeout(total=10, connect=3)分开控制总耗时和建连时间 - 禁用自动重定向:
allow_redirects=False,自己解析resp.status == 302和resp.headers.get("Location"),避免隐藏耗时 - 大响应体别用
await resp.text()—— 它会一次性读完并解码,内存爆炸。改用resp.content.iter_any()流式读取 - SSL 验证失败(如内网自签证书)要关验证:
connector = aiohttp.TCPConnector(ssl=False),但生产环境慎用
异步请求真正快起来,靠的不是“写个 async 就行”,而是连接复用、池控、超时、流式处理这些细节全对上。漏掉任意一环,都可能让并发变成阻塞。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










