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

并发运行大量Selenium任务,如何避免系统崩溃与卡顿?

问题分析与解决方案

你的代码一次性启动195个任务,每个任务都新建一个ChromeDriver实例——哪怕是无头模式,每个Chrome进程都会占用几百MB内存和不少CPU,195个实例同时跑直接把系统资源榨干,必然崩溃、卡顿。下面是针对性的优化方案:

1. 限制并发数,避免资源耗尽

用SemaphoreSlim控制同时运行的任务数量,比如设置成5-10(根据你机器性能调整),不让所有请求同时发起。

2. 复用ChromeDriver实例,减少资源开销

每个Chrome实例启动和销毁都有开销,复用实例能大幅降低资源占用。可以维护一个Driver池,或者用单例Driver(注意每次请求后清理页面状态)。

3. 优化页面等待逻辑,避免无限循环

原GetPageSourceAsync是死循环等待,加个超时时间,防止某个页面加载异常导致任务一直卡着。

4. 增加异常处理,避免单个请求失败影响全局

爬取过程中难免遇到页面加载失败、元素找不到的情况,加try-catch捕获异常,保证其他任务正常执行。


修改后的完整代码示例

// 全局或类级别的SemaphoreSlim,控制并发数
private static readonly SemaphoreSlim _semaphore = new SemaphoreSlim(8); // 同时跑8个任务,可根据机器性能调整

static async Task GetData() // 注意:把async void改成async Task,避免无法捕获异常
{
    string website = "......";
    List<string> Countries = new List<string>()
    {
        // 195个国家
    };

    // 用异步遍历替代一次性添加所有任务
    var tasks = Countries.Select(async countryName =>
    {
        await _semaphore.WaitAsync();
        try
        {
            return await FetchData(website + "/" + countryName);
        }
        catch (Exception ex)
        {
            Console.WriteLine($"爬取{countryName}失败: {ex.Message}");
            return null;
        }
        finally
        {
            _semaphore.Release();
        }
    });

    var results = await Task.WhenAll(tasks);
    foreach (var populationRank in results.Where(r => r != null))
    {
        Console.WriteLine(populationRank);
    }

    // 程序结束前清理Driver
    Cleanup();
}

// 复用单个ChromeDriver实例,而不是每次新建
private static ChromeDriver _driver;
static async Task<JObject> FetchData(string URL)
{
    // 懒加载初始化Driver
    if (_driver == null)
    {
        _driver = CustomizedDriver();
    }

    try
    {
        await _driver.Navigate().GoToUrlAsync(URL);
        await WaitForPageReady(_driver, TimeSpan.FromSeconds(10)); // 带超时的等待逻辑

        HtmlDocument Document = new HtmlDocument();
        Document.LoadHtml(_driver.PageSource);
        var popRankNode = Document.DocumentNode.SelectSingleNode("//div[@id='popRank']");
        if (popRankNode == null)
        {
            throw new InvalidOperationException("未找到人口排名元素");
        }

        return new JObject()
        {
            ["PopulationRank"] = popRankNode.InnerText
        };
    }
    finally
    {
        // 清理页面状态,避免影响下一次请求
        await _driver.Navigate().GoToUrlAsync("about:blank");
    }
}

// 优化后的页面等待方法,带超时机制
static async Task WaitForPageReady(IWebDriver driver, TimeSpan timeout)
{
    var endTime = DateTime.Now.Add(timeout);
    var jsExecutor = (IJavaScriptExecutor)driver;
    while (DateTime.Now < endTime)
    {
        string pageState = (string)jsExecutor.ExecuteScript("return document.readyState");
        if (pageState == "interactive" || pageState == "complete")
        {
            // 额外等待一小会儿,确保动态加载的元素渲染完成
            await Task.Delay(500);
            return;
        }
        await Task.Delay(100);
    }
    throw new TimeoutException("页面加载超时");
}

static ChromeDriver CustomizedDriver()
{
    ChromeDriverService chromeService = ChromeDriverService.CreateDefaultService();
    chromeService.HideCommandPromptWindow = true;
    ChromeOptions options = new ChromeOptions();
    options.PageLoadStrategy = PageLoadStrategy.None;
    // 拆分参数,避免解析异常,同时添加更多性能优化参数
    options.AddArguments("--headless=new", "--disable-cookies", "--blink-settings=imagesEnabled=false", "--disable-extensions", "--disable-gpu", "--no-sandbox");
    return new ChromeDriver(chromeService, options);
}

// 程序结束时销毁Driver,释放资源
static void Cleanup()
{
    _driver?.Quit();
    _driver?.Dispose();
}

额外优化建议

  • 如果网站没有反爬机制,完全可以不用Selenium,直接用HttpClient请求页面,再用HtmlAgilityPack解析,资源占用会低几个数量级。
  • 给ChromeDriver添加--disable-dev-shm-usage参数,解决Linux系统下内存不足的问题;添加--window-size=1920,1080避免无头模式下页面布局异常。
  • 可以把爬取结果分批写入文件或数据库,避免内存堆积。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 20:57:02