使用asyncio与aiohttp发送异步请求时部分请求无响应问题排查
问题原因及解决方案
核心原因分析
服务器静默限流
很多服务器面对高频批量请求时,不会返回40X类状态码(比如429请求过多),而是直接在TCP层面丢弃连接或不发送任何响应。这种静默处理比返回明确状态码更隐蔽,能有效阻止自动化脚本的持续请求,你的未响应请求就是因为服务器没回包,而代码又没设置超时,导致任务一直挂起。缺少请求超时配置
你的代码里没有给session.get设置超时时间,当服务器不响应时,请求会无限期等待,永远不会主动终止,这是任务挂起的直接原因。并发数超出连接池/服务器承受上限
aiohttp的ClientSession默认连接池上限是100个并发连接,当你创建超过100个任务时,多余的任务会排队等待空闲连接。如果已有连接因为服务器静默丢弃而挂起,排队的任务会一直卡住,无法获取执行机会。
解决方案
1. 添加请求超时
给每个请求设置超时,避免无限等待:
import asyncio import aiohttp async def make_request(session, link, i): try: # 设置10秒总超时(包含连接和响应等待时间) async with session.get(link, timeout=aiohttp.ClientTimeout(total=10)) as response: loop = asyncio.get_running_loop() print(len(asyncio.tasks.all_tasks(loop))) print(f"request:{i}") print(response.status) except asyncio.TimeoutError: print(f"request:{i} 超时无响应") except aiohttp.ClientError as e: print(f"request:{i} 连接错误: {str(e)}") async def make_task(link): tasks = [] async with aiohttp.ClientSession() as session: tasks = [asyncio.create_task(make_request(session, link, i)) for i in range(no_of_requests)] await asyncio.wait(tasks, return_when=asyncio.ALL_COMPLETED) asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) asyncio.run(make_task("https://ocw.mit.edu"), debug=True)
2. 限制并发请求数
用asyncio.Semaphore控制同时发送的请求数,避免瞬间触发服务器限流:
import asyncio import aiohttp async def make_request(session, link, i, semaphore): async with semaphore: # 控制并发数 try: async with session.get(link, timeout=aiohttp.ClientTimeout(total=10)) as response: loop = asyncio.get_running_loop() print(len(asyncio.tasks.all_tasks(loop))) print(f"request:{i}") print(response.status) except asyncio.TimeoutError: print(f"request:{i} 超时无响应") except aiohttp.ClientError as e: print(f"request:{i} 连接错误: {str(e)}") async def make_task(link): semaphore = asyncio.Semaphore(50) # 限制最多50个并发请求 tasks = [] async with aiohttp.ClientSession() as session: tasks = [asyncio.create_task(make_request(session, link, i, semaphore)) for i in range(no_of_requests)] await asyncio.wait(tasks, return_when=asyncio.ALL_COMPLETED) asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) asyncio.run(make_task("https://ocw.mit.edu"), debug=True)
补充说明
服务器选择静默丢弃而非返回40X,本质是为了避免脚本通过识别状态码自动调整请求策略(比如自动降频、重试),这种策略在反爬机制中很常见。限制并发数不仅能降低触发限流的概率,还能减少本地资源占用,让程序运行更稳定。
内容的提问来源于stack exchange,提问作者Bilal Ahmed Karbelkar
相关产品推荐
相关产品推荐

