You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用asyncio.gather执行异步函数时出现挂起问题(单独执行可正常运行)

asyncio.gather执行异步函数时出现挂起问题(单独执行可正常运行)

看起来你遇到了个挺头疼的问题——单独await每个异步操作时程序跑得好好的,一用asyncio.gather并发执行就直接挂住,只输出了"Scraping product data"就没动静了。结合你用的是nodriver+FastAPI的场景,我来帮你捋捋可能的原因和解决办法:

可能的核心原因

  1. nodriver的Page对象不支持并发操作
    很多浏览器自动化工具(包括nodriver)的页面实例内部其实是有锁机制的,同一时间只能处理一个DOM查询或操作请求。当你用asyncio.gather同时发起十几个page.select_all/page.select调用时,这些请求会互相抢占资源,最后全都堵在那儿,导致整个程序挂起。

  2. 异常处理掩盖了阻塞细节
    你的safe_select和safe_select_all函数里做了全局异常捕获,就算某个协程因为页面操作卡住或者出错,也只会打印日志然后返回None,但asyncio.gather还是会一直等待所有任务完成——如果有任务一直没结束,整个gather就会无限挂着。

可行的解决方案

方案一:放弃页面级并发,改用串行执行

既然单个await能正常运行,那可以把asyncio.gather的并发逻辑改成逐个await的串行方式,虽然少了并发的效率,但能保证程序稳定运行:

# 替换原来的gather代码块
try:
    # 逐个await获取数据
    breadcrumb = await safe_select_all("#desktop-category-nav > div > div > ul > li > a")
    sales_price = await safe_select("div[data-buy-box-region='price'] > p:nth-child(1)")
    original_price = await safe_select("div[data-buy-box-region='price'] > p:nth-child(2) > span:nth-child(2)")
    product_name = await safe_select("#listing-page-cart h1")
    category_url = await safe_select(".breadcrumb_nav ul > li:last-child > a", "href")
    num_reviews = await safe_select(".reviews__shop-info h2")
    last_review_date = await safe_select(".review-card > div > div > div:nth-child(3) p")
    store_url = await safe_select("#shop_owners_content_toggle a", "href")
    brand = await safe_select("#shop_owners_content_toggle p")
    more_from_this_shop_urls = await safe_select_all("div[data-page-type='recs_ribbon_same_shop'] > a", "href")
    product_details = await safe_select_all("#product_details_content_toggle li div")
    main_image = await safe_select(".image-carousel-container > ul > li:nth-child(1) > img", "src")
    last_24_hours = await safe_select("#listing-page-cart > div:nth-child(1) > p")
    
    results = [
        breadcrumb, sales_price, original_price, product_name, category_url,
        num_reviews, last_review_date, store_url, brand, more_from_this_shop_urls,
        product_details, main_image, last_24_hours
    ]
except Exception as e:
    print(f"Error during fetching: {str(e)}", flush=True)
    results = [None] * 13

方案二:给任务添加超时机制

如果想保留并发逻辑,可以给每个协程加上超时,这样某个任务卡住时会触发超时异常,帮你定位到底是哪个选择器出了问题:

import asyncio

# 修改gather部分的代码
try:
    results = await asyncio.gather(
        asyncio.wait_for(safe_select_all("#desktop-category-nav > div > div > ul > li > a"), timeout=15),
        asyncio.wait_for(safe_select("div[data-buy-box-region='price'] > p:nth-child(1)"), timeout=15),
        # 其他任务也用同样的方式包裹
        asyncio.wait_for(safe_select("#listing-page-cart > div:nth-child(1) > p"), timeout=15)
    )
except asyncio.TimeoutError as e:
    print("某个页面操作超时了,检查对应的选择器或页面加载状态", flush=True)
except Exception as e:
    print(f"Error during concurrent fetching: {str(e)}", flush=True)
    results = [None] * 13

方案三:检查nodriver的并发支持

可以去看看nodriver的官方文档,确认它是否支持同一页面的并发DOM操作,有没有需要开启的配置项。有些工具默认不允许页面级并发,或者需要特殊的初始化参数才能启用。

额外调试建议

  • 在safe_select和safe_select_all里添加更详细的日志,比如进入函数、开始执行page.select_all、完成操作的时间戳,这样能精准定位到哪个步骤卡住了。
  • 先简化gather里的任务,只保留2-3个测试,如果正常再逐步添加其他任务,找到导致挂起的具体选择器。

备注:内容来源于stack exchange,提问作者Tharusha Jayasooriya

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.14 11:13:01