C# .NET Selenium网页爬虫是否可通过多线程与并行编程提升效率?
问题根因分析
你的并行完全没有生效,核心是2个致命错误导致的:
- 你将核心耗时逻辑
LoadBrowser完整包裹在全局lock块中,同一时间仅允许1个线程执行这段代码,所有线程都要排队执行,10条数据自然就是10*10=100秒,和单线程没有任何区别。 - 并行逻辑重复嵌套:
Parallel.ForEach本身已经会分配线程执行任务,你在内部又手动创建了新的Thread,属于完全多余的操作,还额外增加了线程调度、上下文切换的开销,完全没有必要。
多线程与并行编程的实际运行逻辑
Parallel.ForEach默认适配CPU密集型任务,会根据当前设备的CPU核心数自动分配并行度,你的场景是IO密集型任务(90%以上的耗时在等待网页加载、网络请求响应,CPU处于空闲状态),完全可以设置远高于CPU核心数的并行度,只要不触发目标网站的反爬限制即可。- 并行的核心是让等待IO的时间可以被其他任务利用,而不是真的同时跑10个CPU运算任务,所以IO密集型任务的并行上限通常远高于CPU核心数。
- 你的场景下
Parallel.ForEach本身可以满足需求,但错误的锁使用和冗余的线程创建抵消了所有并行收益。
代码优化方案
核心优化点
- 移除冗余的手动
Thread创建、线程状态轮询、Thread.Abort(该方法已被.NET标记为过时,会引发不可预期的进程异常) - 缩小锁的范围,仅在操作公共非线程安全集合时加锁,或者直接使用线程安全集合
ConcurrentBag<GoogleList>规避锁的使用 - 显式设置
Parallel.ForEach的并行度,匹配你的业务需求
优化后示例代码
private static List<GoogleList> MultiTreadMain(List<FileStructure> values) { // 线程安全集合,无需手动加锁即可多线程写入 ConcurrentBag<GoogleList> listGInfo = new ConcurrentBag<GoogleList>(); // 提前筛选需要处理的数据,避免在Parallel内部做无效判断 var processValues = values.Where(v => v.ID <= 10).ToList(); Parallel.ForEach(processValues, new ParallelOptions { // 按需设置并行度,测试时可以设为10,正式跑可以根据反爬情况调整为20-50 MaxDegreeOfParallelism = 10 }, value => { // 耗时逻辑不需要加锁,每个线程独立执行 var singleListGInfo = LoadBrowser("https://www.google.com", value.Address, value.City, value.State, value.FirstName, value.LastName, "USA", value.ZipCode, value.ID); // 写入线程安全集合 foreach (var item in singleListGInfo) { listGInfo.Add(item); } }); return listGInfo.ToList(); }
额外优化建议
- 每个爬虫线程使用独立的Selenium浏览器实例,避免多线程共用同一个浏览器导致的操作冲突
- 可以开启Selenium无头模式,减少浏览器UI渲染的额外开销
- 若需要更高的性能,可以考虑把Selenium替换为轻量级的HTTP请求库(如HttpClient)结合HTML解析库(如AngleSharp),性能可以提升数倍。
内容的提问来源于stack exchange,提问作者SANOSUKE
相关产品推荐
相关产品推荐

