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

使用Parallel.ForEach搭配Puppeteer时无法打开URL的问题求助

Puppeteer并行爬坑:Parallel.ForEach失效与性能优化方案

问题根源

Parallel.ForEach是为CPU密集型任务设计的多线程并行工具,它完全不兼容异步委托——你传入的async lambda会被当作async void执行,这会导致异步操作脱离生命周期管理,加上Puppeteer的Page实例并非线程安全,多线程环境下直接操作必然会出现GoToAsync失效、数据获取失败等问题。

正确的异步并行方案

改用Task.WhenAll实现异步IO任务的并行,同时用SemaphoreSlim控制并发数,避免无限制创建Page导致浏览器资源耗尽:

// 限制最大并发数,根据机器性能调整,建议5-10之间
var concurrencySemaphore = new SemaphoreSlim(8);
var taskList = Urls.Select(async url =>
{
    await concurrencySemaphore.WaitAsync();
    try
    {
        Uri uri = new Uri(url);
        var myDomain = uri.Host;
        var objectDomain = domains.FirstOrDefault(item => item.Domain == myDomain);
        
        // 容错:跳过无配置的域名
        if (objectDomain == null)
        {
            Console.WriteLine($"域名 {myDomain} 无配置,跳过URL:{url}");
            return;
        }

        var priceSelector = objectDomain.Price;
        var oldPriceSelector = objectDomain.oldPrice;
        
        var page = await browser.NewPageAsync();
        try
        {
            // 性能优化:禁用图片、CSS等非必要资源加载
            await page.SetRequestInterceptionAsync(true);
            page.Request += (_, requestArgs) =>
            {
                var resourceType = requestArgs.Request.ResourceType;
                if (resourceType is ResourceType.Image or ResourceType.StyleSheet or ResourceType.Font)
                {
                    _ = requestArgs.Request.AbortAsync();
                }
                else
                {
                    _ = requestArgs.Request.ContinueAsync();
                }
            };

            // 设置导航超时,避免单个请求卡住
            await page.GoToAsync(url, new NavigationOptions { Timeout = 30000 });
            
            var priceNode = await page.QuerySelectorAsync($".{priceSelector}");
            var oldPriceNode = await page.QuerySelectorAsync($".{oldPriceSelector}");
            
            // 提取数据示例
            if (priceNode != null)
            {
                var currentPrice = await priceNode.GetTextContentAsync();
                // 处理价格数据,比如存入数据库或集合
            }
            if (oldPriceNode != null)
            {
                var originalPrice = await oldPriceNode.GetTextContentAsync();
                // 处理原价数据
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"处理URL {url} 失败:{ex.Message}");
        }
        finally
        {
            // 必须关闭Page释放资源
            await page.CloseAsync();
            concurrencySemaphore.Release();
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine($"初始化URL {url} 时出错:{ex.Message}");
        concurrencySemaphore.Release();
    }
});

// 等待所有异步任务完成
await Task.WhenAll(taskList);

额外性能优化点

  • 复用Page(同域名场景):如果多个URL属于同一域名,可以复用单个Page,通过page.GoToAsync跳转不同地址,减少Page创建销毁的开销。
  • 启用浏览器缓存:在启动Browser时配置缓存路径,避免重复请求静态资源:
    var browser = await Puppeteer.LaunchAsync(new LaunchOptions
    {
        Headless = true,
        UserDataDir = "./browser-cache" // 指定缓存目录
    });
    
  • 调整导航参数:设置WaitUntil = WaitUntilNavigation.Networkidle2,在网络请求基本停止时就结束导航,无需等待完全加载:
    await page.GoToAsync(url, new NavigationOptions 
    { 
        Timeout = 30000,
        WaitUntil = WaitUntilNavigation.Networkidle2
    });
    
  • 禁用JavaScript(如果允许):如果目标页面不需要JS渲染就能获取数据,直接禁用JS可以大幅提升加载速度:
    await page.SetJavaScriptEnabledAsync(false);
    

内容的提问来源于stack exchange,提问作者Long

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 19:50:21