asyncio异步爬虫超时与并发数控制功能异常问题求解
问题根源
你的代码出现该问题的核心逻辑缺陷有两个:
- 仅在
active_calls归0时才执行event.set()唤醒等待的任务,导致所有任务必须分批执行,每批要等上一批全部跑完才会启动下一批。 - 任务被唤醒后没有再次校验当前并发数,所有等待的任务会同时进入执行逻辑,自然就突破了5个并发的限制。
另外你手动维护全局计数器+Event的实现方式很容易出现竞态条件,多个任务同时读写active_calls时可能出现计数不准的问题。
修复方案
asyncio提供了原生的并发限制工具asyncio.Semaphore,天生就是用来控制异步任务并发数的,不需要你手动维护计数器和Event,逻辑更简洁也更稳定。
修改后的完整代码
import json from bs4 import BeautifulSoup import asyncio import aiohttp # 配置项 MAX_CALLS = 5 DELAY = 0.3 PATH = "./你的存储路径/" # 请补充你自己的文件存储路径 def write_to_txt_file(text, name): # 用上下文管理器避免文件句柄泄露 with open(f'{PATH}{name}.txt', 'w', encoding='utf-8') as f: f.write(text) async def getData(item, session, request_delay, semaphore): # 先获取Semaphore许可,最多同时5个任务获取到 async with semaphore: print('start', item['ID']) # 执行请求前延迟 await asyncio.sleep(request_delay) try: async with session.get(url=item['Link']) as response: soup = BeautifulSoup(await response.text(), 'html.parser') name = f"{item['ID']}. {item['Title'][:100]}" text = soup.find(id="begin").get_text() write_to_txt_file(text, name) except Exception as e: # 建议加上异常捕获,避免单个请求出错导致整个脚本崩溃 print(f"请求{item['Link']}出错:{str(e)}") async def parseFromJson(): with open('./data2/csvjson.json', encoding='utf-8') as data_file: data = json.load(data_file) # 初始化Semaphore,指定最大并发数 semaphore = asyncio.Semaphore(MAX_CALLS) async with aiohttp.ClientSession() as session: tasks = [] next_delay = 0.1 for item in data: # 每个任务的延迟在创建时就计算好,避免全局变量冲突 task = asyncio.create_task(getData(item, session, next_delay, semaphore)) next_delay += DELAY tasks.append(task) await asyncio.gather(*tasks) def main(): asyncio.run(parseFromJson()) if __name__ == "__main__": main()
核心修改点说明
- 去掉了手动维护的
active_calls计数器和asyncio.Event,换成asyncio.Semaphore来自动控制并发数,每个任务执行前先获取许可,执行完自动释放,天然保证最多5个并发。 - 修复了延迟参数的传参问题,每个任务的延迟在创建时就计算好传入,避免全局变量读写冲突。
- 优化了文件写入逻辑,用上下文管理器避免文件句柄泄露,增加了请求异常捕获,避免单个请求失败导致整个脚本崩溃。
- 补充了编码声明,避免读写中文时出现乱码。
内容的提问来源于stack exchange,提问作者Rodin Oleksandr
相关产品推荐
相关产品推荐

