异步爬虫性能未达预期?asyncio+aiohttp请求有效性排查求助
我编写了一个爬虫脚本,同步请求约30个URL时总耗时95秒。为提升速度,使用asyncio和aiohttp重写了脚本,32次请求的性能统计如下:
- 同步请求总耗时:95秒
- 异步请求总耗时:60秒
- 浏览器手动单次请求URL耗时:约1秒
这个仅50%的速度提升完全达不到预期——按单次请求1秒计算,32次异步请求应该几乎同时发起,总耗时不应超过1.5秒。我怀疑请求并未真正异步执行(刚接触asyncio),希望得到排查方向的提示。
附上我的代码:
# 单个异步请求函数 async def async_get_course(session, url_course): async with session.get(url_course) as res: response = await res.content.read() return response # 主协程函数 async def example(courses, root): starting_time = time.time() actions = [] data = [] data2 = [] async with aiohttp.ClientSession() as session: for course in courses: url_course = f"{root}{course['course_link']}" data.append(url_course) actions.append(asyncio.ensure_future(async_get_course(session, url_course))) results = await asyncio.gather(*actions) for idx, res in enumerate(results): data2.append(get_info_from_course((courses[idx], data[idx], res))) total_time = time.time() - starting_time print('总耗时', total_time) return data2 # 运行协程 courses_final = asyncio.run(example(courses, root))
排查方向提示
检查同步阻塞操作:
确认get_info_from_course函数是否为同步阻塞——如果该函数包含CPU密集操作或同步IO(如文件读写、同步数据库调用),会在asyncio.gather完成后依次阻塞执行,拖慢总耗时。同时检查async_get_course内部是否有隐藏的同步代码。调整aiohttp连接池限制:
aiohttp的ClientSession默认对同一域名的并发连接数有限制,可能导致请求排队。可以显式设置更大的单域名并发数验证:connector = aiohttp.TCPConnector(limit_per_host=32) # 匹配请求数量 async with aiohttp.ClientSession(connector=connector) as session: # 原有逻辑验证请求并发状态:
在async_get_course中添加日志,打印每个请求的开始/结束时间,确认是否所有请求几乎同时发起:import logging logging.basicConfig(level=logging.INFO) async def async_get_course(session, url_course): logging.info(f"开始请求: {url_course}") async with session.get(url_course) as res: response = await res.content.read() logging.info(f"完成请求: {url_course}") return response排查目标网站限流:
目标服务器可能对同一IP的并发请求做了限流,即使异步发起也会被排队处理。可以尝试用多IP代理测试,或对比浏览器多标签页同时请求的耗时。检查事件循环是否被阻塞:
确保代码中没有调用time.sleep()(需替换为await asyncio.sleep()),也没有使用不支持异步的第三方库,这些都会阻塞事件循环导致异步失效。测试极简异步案例:
编写只包含异步请求的极简脚本,排除后续处理的影响,看耗时是否正常:import asyncio import aiohttp import time async def fetch(session, url): async with session.get(url) as response: return await response.text() async def main(): start = time.time() # 替换为你的目标URL列表 urls = [f"{root}{course['course_link']}" for course in courses] connector = aiohttp.TCPConnector(limit_per_host=32) async with aiohttp.ClientSession(connector=connector) as session: tasks = [fetch(session, url) for url in urls] await asyncio.gather(*tasks) print(f"纯请求总耗时: {time.time() - start}") asyncio.run(main())如果这个脚本耗时符合预期,说明问题出在
get_info_from_course的处理环节。
内容的提问来源于stack exchange,提问作者LLaP

