Node.js+Puppeteer+Forever超时触发无限循环及内存泄漏求助
哈哈,这个问题我之前帮朋友排查过类似的,大概率是你的错误处理逻辑没做好闭环,加上Puppeteer资源没清理干净导致的!下面给你拆解原因和具体的解决办法:
问题根源分析
1. 无限循环的核心:错误处理的闭环陷阱
你提到的console.log(2)到console.log(6)无限循环,基本可以确定是错误触发后代码又重新进入了截图执行流程——比如在catch块里没设终止条件就重新调用了截图函数,或者错误处理逻辑不小心让代码再次进入了任务循环,形成了“出错→重试→再出错→再重试”的无限闭环。
2. EventEmitter内存泄漏:Puppeteer资源没清理
当超时错误发生时,你没有关闭Puppeteer的browser或page实例,这些实例上绑定的事件监听器(比如page.on('error')、browser.on('disconnected'))会一直留在内存里。随着循环次数增加,监听器数量超过Node.js的默认阈值,就会触发内存泄漏警告。
具体解决办法
1. 给重试逻辑加明确的终止条件
如果你的代码有自动重试机制,一定要设置最大重试次数,避免无限循环:
let retryCount = 0; const MAX_RETRIES = 3; // 自定义最大重试次数 async function takeScreenshot() { let browser; let page; try { console.log(1); // 初始化Puppeteer实例 browser = await puppeteer.launch(); page = await browser.newPage(); await page.goto('目标URL', { timeout: 30000 }); await page.waitForSelector('目标选择器', { timeout: 10000 }); await page.screenshot({ path: 'result.png' }); console.log(7); // 正常完成日志 } catch (error) { console.log(2); // 只处理超时类错误 if (error.name === 'TimeoutError' || error.message.includes('ERR_CONNECTION')) { retryCount++; console.log(`重试第${retryCount}次`); if (retryCount <= MAX_RETRIES) { await takeScreenshot(); // 未达上限则重试 } else { console.log(6); console.error('已达最大重试次数,终止任务'); } } else { // 非超时错误直接终止 console.error('非超时类错误:', error); } } finally { // 不管成功失败,强制清理资源 if (page) await page.close().catch(err => console.error('关闭page失败:', err)); if (browser) await browser.close().catch(err => console.error('关闭browser失败:', err)); } }
2. 用try...finally强制清理资源
不管截图成功还是失败,都要确保browser和page被关闭。finally块会在try/catch执行完毕后必跑,彻底避免资源残留:
async function takeScreenshot() { let browser; let page; try { // 截图核心逻辑 console.log(1); browser = await puppeteer.launch(); page = await browser.newPage(); await page.goto('目标URL', { timeout: 30000 }); await page.waitForSelector('目标选择器', { timeout: 10000 }); await page.screenshot({ path: 'result.png' }); console.log(7); } catch (error) { console.log(2); // 错误处理逻辑 if (error.name === 'TimeoutError') { console.log(3); // 你的自定义错误日志 } } finally { // 强制关闭资源 if (page) await page.close().catch(err => console.error('关闭page失败:', err)); if (browser) await browser.close().catch(err => console.error('关闭browser失败:', err)); console.log(6); } }
3. 解绑自定义事件监听器
如果你的代码手动给page/browser绑定了事件(比如page.on('console', ...)),一定要在关闭实例前解绑,避免监听器残留:
// 假设你绑定了console事件监听器 const consoleHandler = msg => console.log('页面日志:', msg.text()); page.on('console', consoleHandler); // 在关闭page前解绑 page.off('console', consoleHandler); await page.close();
4. 兜底:用process.exit()终止进程(极端情况)
如果以上方法都无法终止循环,可以在达到重试上限后调用process.exit(1)强制结束Node.js进程——这是兜底方案,优先推荐前面的资源清理和终止逻辑。
验证方法
- 故意让目标网站不可达,观察日志是否在达到重试上限后停止循环;
- 检查控制台是否还会出现
EventEmitter memory leak detected警告,没有则说明资源清理生效。
内容的提问来源于stack exchange,提问作者Seb
相关产品推荐
相关产品推荐

