使用Puppeteer实现并行爬取:多页面打开多链接
解决Puppeteer并行爬取的超时与卡顿问题
我来帮你排查下代码里的问题,以及给出针对性的修复方案——你的核心思路(多页面并行+分组串行处理链接)是对的,但细节上的缺失导致了不稳定:
原代码的核心问题
- 没有错误兜底:不管是页面加载还是元素查询,一旦出错就会直接中断整个Promise链,
Promise.all会直接失败,这就是你时而正常时而崩溃的主要原因之一。 - 无限制加载资源:默认情况下Puppeteer会加载页面所有资源(图片、广告脚本、视频等),3个并行页面同时疯狂拉资源,很容易把CPU/内存占满,导致浏览器卡顿超时。
- 内存泄漏风险:爬完页面后没调用
page.close(),页面实例一直占着内存,运行越久越卡。 - 超时配置缺失:
page.goto默认超时30秒,遇到慢页面直接抛出错误,没有自定义超时或重试机制。
改进后的代码
const totBrowserInstances = 3; const promises = []; // 封装单个链接的爬取逻辑,包含错误处理和基础重试 const scrapeSingleLink = async (page, link) => { // 最多重试2次 for (let retry = 0; retry < 2; retry++) { try { await page.goto(link, { waitUntil: 'domcontentloaded', timeout: 20000 // 自定义20秒超时,避免无限制等待 }); // 等待目标元素渲染完成再查询,比直接evaluate更可靠 await page.waitForSelector('.competizione .pagina h3', { timeout: 5000 }); const result = await page.evaluate(() => { return document.querySelector('.competizione .pagina h3').innerText; }); console.log('response:', result); return result; } catch (error) { console.warn(`爬取链接 ${link} 失败(第${retry+1}次):`, error.message); if (retry === 1) { console.error(`链接 ${link} 最终爬取失败`); return null; } // 重试前短暂等待 await new Promise(resolve => setTimeout(resolve, 1000)); } } }; for (let i = 0; i < totBrowserInstances; i++) { console.log('Page ID Spawned', i); promises.push( (async () => { const page = await browser.newPage(); // 拦截非必要资源,大幅降低加载压力 await page.setRequestInterception(true); page.on('request', (request) => { const blockedResources = ['image', 'stylesheet', 'font', 'media']; if (blockedResources.includes(request.resourceType())) { request.abort(); } else { request.continue(); } }); // 串行处理当前页面的所有链接(同一个页面不能同时加载多个地址) for (const link of instancesLinksMatrix[i]) { await scrapeSingleLink(page, link); } // 爬完后关闭页面,释放内存 await page.close(); })() ); } await Promise.all(promises); console.log('所有爬取任务执行完毕');
关键改动说明
- 错误处理+重试:把单个链接的爬取封装成函数,加入重试逻辑,即使某个链接临时失败也不会中断整个任务,还能输出错误信息方便排查。
- 资源拦截:拦截图片、样式表等非核心资源,每个页面的加载时间和内存占用能减少70%以上,彻底解决卡顿问题。
- 等待元素渲染:用
page.waitForSelector代替直接evaluate,确保目标元素已经出现在DOM里再查询,避免元素找不到的错误。 - 自定义超时:给页面加载和元素查询都设置了合理的超时时间,避免无限制等待。
- 及时释放资源:每个页面完成任务后立即关闭,杜绝内存泄漏。
额外优化建议
- 调整并发数:如果你的机器性能一般,
totBrowserInstances建议设为4-6,太多会导致浏览器进程过载。 - 使用浏览器上下文:如果需要更高的隔离性(比如不同页面的Cookie不共享),可以用
browser.createIncognitoBrowserContext()创建独立上下文,每个页面在单独上下文里运行。 - 日志优化:可以把爬取结果和错误信息写入文件,方便后续分析。
内容的提问来源于stack exchange,提问作者Jacob
相关产品推荐
相关产品推荐

