如何在Python中结合asyncio.as_completed使用Semaphore限制API并发?
解决思路
1. 修正Semaphore的作用范围
你当前的代码把Semaphore放在asyncio.as_completed的循环里,只限制了任务结果的处理步骤,而非实际的API请求并发。100+个任务已经提前启动,连接池早被占满,导致部分请求因连接耗尽失败。
正确的做法是把Semaphore绑定到每个API请求的执行阶段,包括初始请求和后续分页请求:
async def fetch_data(sem, client, initial_url): async with sem: res = await client.get_json_async(initial_url) all_data = res["data"].copy() progress.update(len(all_data)) # 分页请求也受Semaphore控制 while res["links"].get("next"): async with sem: res = await client.get_json_async(res["links"]["next"]) all_data.extend(res["data"]) progress.update(len(res["data"])) return all_data # 初始化Semaphore,设置合理并发数(比如10-20,根据服务器承受能力调整) sem = asyncio.Semaphore(10) # 重新创建受Semaphore控制的任务列表 tasks = [fetch_data(sem, client, url) for url in your_initial_urls] q = Queue(-1) progress = tqdm(total=total_hits) for task in asyncio.as_completed(tasks): try: data = await task q.put_nowait(data) except Exception as e: print(f"请求失败: {str(e)}") # 可选:添加重试逻辑
2. 检查并调整客户端连接池配置
客户端连接池(比如aiohttp的TCPConnector)默认有最大连接数限制,若超过服务器允许的TCP连接数,会导致请求被拒绝或超时:
- 查看客户端文档,找到连接池的最大连接数配置项(比如aiohttp的
limit参数) - 让连接池的最大连接数与
Semaphore的并发数匹配,避免连接池耗尽
3. 添加错误处理与重试机制
未获取全量数据大概率是因为部分请求失败(超时、限流返回429、服务器错误),但你的代码没有异常捕获,失败的任务直接跳过导致数据丢失:
- 给所有
await client.get_json_async添加try-except块,捕获网络异常、HTTP错误码 - 对429(限流)这类可重试的错误,添加指数退避重试逻辑
4. 验证服务器限流规则
很多API服务器会限制并发请求数或请求速率(比如每分钟最多100次请求),超过限制会返回错误或丢弃请求:
- 查看API文档的限流说明
- 检查响应头中的
Retry-After、X-RateLimit-Limit等字段,确认限流规则 - 调整
Semaphore的并发数,使其低于服务器的限流阈值
5. 调整Semaphore的并发数值
当前设置Semaphore(1)会让所有请求串行执行,效率极低且没必要。建议从10开始测试,逐步调整到能稳定获取全量数据的最大值。
内容的提问来源于stack exchange,提问作者j7skov
相关产品推荐
相关产品推荐

