并发运行大量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
相关产品推荐
相关产品推荐

