不能直接用requests+asyncio,因其同步阻塞会卡住事件循环;必须用aiohttp或httpx.asyncclient原生异步客户端,配合semaphore限流和clientsession正确管理连接池。

异步抓取外部API数据不是单纯加 async 就能并发,关键在用对 aiohttp + asyncio.gather,且必须限制并发数,否则容易触发服务端限流或本地连接池耗尽。
为什么不能直接用 requests + asyncio
requests 是同步阻塞库,即使包在 async 函数里,也会让整个事件循环卡住;它不支持 await,也不能复用连接。强行搭配 loop.run_in_executor 虽然可行,但失去异步轻量优势,还可能因线程池配置不当导致内存暴涨或句柄泄漏。
- 真实错误现象:
RuntimeWarning: coroutine 'ClientSession.get' was never awaited(忘了加await) - 更隐蔽的问题:未关闭
aiohttp.ClientSession,导致 TCP 连接堆积、ResourceWarning或后续请求超时 - 推荐替代:只用
aiohttp或httpx.AsyncClient,二者原生支持await
如何安全控制并发数量?
无节制并发(比如 asyncio.gather(*[fetch(url) for url in urls]))在面对几十个 URL 时极易打崩目标 API 或本地系统。必须用 asyncio.Semaphore 限流。
- 典型误操作:把
Semaphore实例放在循环内创建 → 每次都新建,限流失效 - 正确做法:在最外层初始化一次,传入每个
fetch协程中 - 经验值:一般设为 10–20,并发过高反而因 TCP 重传/拥塞导致整体耗时上升
sem = asyncio.Semaphore(15) <p>async def fetch(session, url): async with sem: # 关键:这里才真正限流 async with session.get(url) as resp: return await resp.json() </p>
aiohttp 解析 JSON 响应要注意什么?
不是所有 API 都返回标准 JSON;有些返回空体、HTML 错误页、或 Content-Type 不是 application/json,直接调 resp.json() 会抛 aiohttp.ContentTypeError 或 json.JSONDecodeError。
- 必须检查
resp.status,4xx/5xx 状态码不该进解析流程 - 建议用
resp.text()先取原始响应体,再按需json.loads(),便于调试非 JSON 响应内容 - 如果 API 返回压缩内容(如
Content-Encoding: gzip),aiohttp默认自动解压,无需额外处理
实际运行时最容易被忽略的点
很多人写完协程就直接 asyncio.run(main()),却忘了 aiohttp.ClientSession 必须显式关闭,尤其在异常中断时;另外,DNS 解析默认不复用,高并发下可能成为瓶颈。
- 务必用
async with aiohttp.ClientSession(...),而不是裸调ClientSession() - 可传参
connector=aiohttp.TCPConnector(limit=100, limit_per_host=30)控制连接池大小 - 若目标域名固定,加
trust_env=True可读取系统代理和 DNS 缓存设置
并发本身不难,难的是在失败率、响应差异、网络抖动和资源约束之间找平衡——多数线上问题,其实出在没做状态码判断、没设连接池上限、或者忘了关 session。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!











