长时间运行Puppeteer脚本后浏览器内存耗尽问题求助
Puppeteer长时间运行内存泄漏问题分析与解决方案
一、你的标签页处理方式确实可能引发内存泄漏
从代码片段来看,当前的实现存在几个内存泄漏风险点:
- 每次循环都创建
targetcreated监听的Promise,如果某次操作未触发新标签页,这个Promise会一直处于pending状态,占用内存 - 关闭标签页后,页面对象(
newTab/newNewTab)未主动解除引用,V8垃圾回收器可能无法及时回收这些对象 - 频繁创建新标签页时,浏览器可能残留未释放的DOM节点、事件监听器或网络连接,累计后导致内存占用飙升
二、针对长时间任务的Puppeteer优化实践
1. 标签页管理优化
- 复用标签页而非频繁创建销毁:固定一个专门的下载标签页,每次导航到PDF链接后清理页面,避免反复创建新标签
- 关闭标签后强制解除引用:手动将页面对象置为
null,帮助垃圾回收:await newNewTab.close(); newNewTab = null; // 解除引用 await newTab.close(); newTab = null; - 替换
targetcreated监听逻辑:不要依赖全局监听新标签,改用更可控的方式,比如通过page.evaluate点击元素并获取PDF URL,再用同一个标签页导航下载,减少标签创建次数
2. 启动参数优化(大幅降低内存占用)
启用无头模式+限制浏览器资源,是长任务的核心优化:
const browser = await puppeteer.launch({ headless: 'new', // Puppeteer v19+新无头模式,比旧模式省内存 defaultViewport: null, args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', // 避免/dev/shm内存不足问题 '--disable-gpu', '--no-first-run', '--single-process', // 单进程模式,减少内存开销(部分功能受限,按需启用) '--max-old-space-size=8192', // 限制Node.js堆内存为8GB '--memory-pressure-off' // 关闭浏览器内存压力自动冻结逻辑 ] });
3. 内存回收与周期清理
- 定期强制垃圾回收:启动Node时添加
--expose-gc参数,在每次日期循环结束后触发:if (global.gc) { global.gc(); } - 定期重启浏览器:不要长期复用同一个浏览器实例,比如每处理10天的数据就重启一次,彻底释放内存:
if (dateProcessCount % 10 === 0) { await browser.close(); browser = await puppeteer.launch({/* 启动参数 */}); page = await browser.newPage(); // 重新执行登录逻辑 } - 清理页面缓存:每次分页处理后,清除本地存储和Cookie:
await page.deleteCookie(); await page.evaluate(() => { window.localStorage.clear(); window.sessionStorage.clear(); });
4. 下载逻辑优化
- 绕过浏览器直接下载PDF:如果能拿到PDF的直接URL,用Node.js的
axios/fetch直接下载,完全节省浏览器标签的内存开销:const axios = require('axios'); const fs = require('fs'); // 带上登录后的Cookie确保权限 const pdfStream = await axios.get(pdfUrl, { responseType: 'stream', headers: { Cookie: await page.cookies().map(c => `${c.name}=${c.value}`).join('; ') } }); pdfStream.data.pipe(fs.createWriteStream(`./pdfs/${filename}.pdf`)); - 配置浏览器直接下载不预览:如果必须用浏览器下载,设置下载路径避免预览渲染:
await page.setDownloadBehavior({ behavior: 'allow', downloadPath: './pdfs' });
5. 代码结构优化
- 拆分嵌套逻辑:将分页处理、PDF下载拆成独立异步函数,减少闭包导致的内存泄漏
- 清理事件监听器:如果手动添加了浏览器/页面的事件监听,用完后必须移除:
// 示例:添加监听后记得移除 const targetHandler = (target) => resolve(target); browser.on('targetcreated', targetHandler); // 操作完成后移除 browser.off('targetcreated', targetHandler);
三、额外排查点
- 监控内存使用:每分钟打印内存情况,定位内存增长阶段:
setInterval(() => { const mem = process.memoryUsage(); console.log(`当前堆内存占用: ${(mem.heapUsed / 1024 / 1024).toFixed(2)} MB`); }, 60000); - 检查CSV写入逻辑:确保分批写入磁盘,不要把大量数据累积在内存中
- 捕获浏览器端日志:查看是否有内存相关警告:
page.on('console', msg => console.log(`浏览器日志: ${msg.text()}`));
内容的提问来源于stack exchange,提问作者jC61
相关产品推荐
相关产品推荐

