为什么我的异步GET请求比同步慢?如何优化100次页面抓取效率?
问题核心错误点
- 串行执行异步任务:当前
get_n_soups_async的写法是每发起一个请求就用await等它完全结束,再发起下一个请求,本质和同步串行逻辑完全一致,没有利用到异步I/O的并发特性,反而多出了异步框架的调度开销,所以耗时比同步版本更长。 - 等待逻辑冗余:你在每次请求完成后固定等待0.5秒,99次等待就有接近50秒的无效耗时,完全没有必要通过单请求间隔的方式控频。
- 未使用批量异步调度:异步的核心优势是在等待某一个请求的响应时,切换去处理其他已经发起的请求,你的写法完全没有用到这个特性。
- 注意隐形坑:如果你底层的
get_response_text是封装的同步requests方法,哪怕上层加了async/await也完全不会有异步效果,必须使用异步HTTP客户端实现。
优化方案
你需要用asyncio.gather批量发起异步请求,同时通过信号量控制并发数上限,即可在不触发IMDb频率限制的前提下大幅提升效率,修改后的参考代码如下:
import asyncio import aiohttp import time import requests from bs4 import BeautifulSoup # 异步请求文本方法,基于aiohttp实现 async def get_response_text(session, url): async with session.get(url) as resp: return await resp.text() async def get_page_soup(session, url): response_text = await get_response_text(session, url) return BeautifulSoup(response_text, features="html.parser") async def get_n_soups_async(url, num_soups=100, max_concurrent=8): # 信号量控制最大并发数,根据IMDb限频规则调整,一般5-10不会触发限制 semaphore = asyncio.Semaphore(max_concurrent) async with aiohttp.ClientSession() as session: async def bounded_get_soup(): async with semaphore: return await get_page_soup(session, url) # 批量生成100个任务同步调度 tasks = [bounded_get_soup() for _ in range(num_soups)] soups = await asyncio.gather(*tasks) return soups # 原同步版本代码 def get_n_soups_sync(url, num_soups=100): soup = None for _ in range(num_soups): soup = BeautifulSoup(requests.get(url).text, features="html.parser") time.sleep(0.5) return soup async def main(): print("Async main() has started... ") t1 = time.perf_counter() soups = await get_n_soups_async('https://www.imdb.com/name/nm0425005', 100) t2 = time.perf_counter() print(f"异步耗时:{t2 - t1:.2f}秒,返回soup数量:{len(soups)}") # 同步测试可按需开启,避免重复请求浪费时间 # t1 = time.perf_counter() # soup = get_n_soups_sync('https://www.imdb.com/name/nm0425005', 100) # t2 = time.perf_counter() # print(f"同步耗时:{t2 - t1:.2f}秒") print("Async main() is over.") if __name__ == "__main__": loop = asyncio.get_event_loop() loop.run_until_complete(main())
优化效果说明
调整max_concurrent到合理范围后,总耗时可降到10-30秒区间,远快于原同步版本的230秒。另外原代码只返回了最后一个soup对象,优化后会返回所有100个soup的列表,符合你最初生成100个BeautifulSoup对象的需求。
内容的提问来源于stack exchange,提问作者alekscooper
相关产品推荐
相关产品推荐

