Puppeteer并发请求处理:内存泄漏与超时问题排查
问题分析与修复方案
核心问题
你当前的代码确实存在浏览器实例创建过多+资源未正确释放的问题,这直接导致了内存泄漏和超时,具体问题点如下:
- 重复下载浏览器:每次调用
startBrowser都会执行browserFetcher.download("901912"),不管该版本浏览器是否已经下载完成,完全没必要且浪费资源。 - 每个任务启动一个全新浏览器:
anotherFunction里每次都调用startBrowser,相当于每个任务开一个独立浏览器进程,短时间大量任务会把系统内存占满,触发内存泄漏,同时系统资源不足也会导致waitForSelector超时。 work函数的异步逻辑完全失效:stuff.map里返回的是setTimeout的ID,不是Promise,await Promise.all(workTodo)根本不会等待所有任务完成,任务会混乱执行,资源释放完全没章法。- 冗余页面未关闭:
startBrowser已经创建了一个page,但anotherFunction里又调用await browser.newPage(),新创建的页面没有关闭,直接导致内存泄漏。 - 错误分支未释放资源:如果
waitForSelector超时进入catch块,没有关闭browser和page,这些资源会一直占用内存。
修复后的代码示例
1. 优化startBrowser:只下载一次浏览器,复用浏览器实例
// 全局缓存浏览器下载状态和共享实例 let browserRevisionDownloaded = false; let sharedBrowser = null; async function startBrowser() { // 仅在未下载时执行下载操作 if (!browserRevisionDownloaded) { await browserFetcher.download("901912"); browserRevisionDownloaded = true; } // 复用已连接的浏览器,避免重复创建实例 if (!sharedBrowser || !sharedBrowser.isConnected()) { sharedBrowser = await puppeteer.launch({ executablePath: browserFetcher.revisionInfo("901912").executablePath, args: [ "--no-sandbox", "--disable-gpu", "--disable-dev-shm-usage", "--disable-setuid-sandbox", "--no-first-run", "--no-zygote", "--single-process", ], }); } // 为每个任务创建新页面 const page = await sharedBrowser.newPage(); return { browser: sharedBrowser, page }; }
2. 修复work函数的异步逻辑:用Promise包装超时任务
const work = async (stuff) => { const workTodo = stuff.map(item => { // 用Promise包装setTimeout,让Promise.all能正确等待任务完成 return new Promise(resolve => { setTimeout(async () => { await anotherFunction(item); resolve(); }, item.delayTime); // 假设每个item包含对应的延迟时间参数 }); }); await Promise.all(workTodo); };
3. 修复anotherFunction:确保所有分支都释放资源
async function anotherFunction(someParams) { let browser, page; try { ({ browser, page } = await startBrowser()); await page.goto(someParams.targetUrl); // 显式设置超时时间,避免无限等待 await page.waitForSelector(someParams.targetSelector, { timeout: 15000 }); await page.click(someParams.targetSelector); // 关闭当前任务的页面,浏览器留作复用 await page.close(); } catch (error) { console.log('error', error); // 错误分支必须关闭页面,防止内存泄漏 if (page) await page.close().catch(() => {}); } }
4. 全局清理:程序结束时关闭共享浏览器
// 在进程退出时清理共享浏览器实例 process.on('exit', async () => { if (sharedBrowser && sharedBrowser.isConnected()) { await sharedBrowser.close(); } });
额外建议
- 如果任务量极大,不要用单浏览器实例,而是创建浏览器实例池(比如限制最多5个实例),避免单个浏览器开太多页面导致卡顿。
- 根据页面加载速度调整
waitForSelector的超时时间,避免过短或过长的等待。 - 可以用
puppeteer-cluster库管理并发任务,它封装了实例池和任务调度,比手动实现更稳定。
内容的提问来源于stack exchange,提问作者tryingToBeBetter
相关产品推荐
相关产品推荐

