部署在DigitalOcean 5美元/月Droplet上的7×24运行Puppeteer脚本内存耗尽崩溃的优化方案咨询
Puppeteer 内存泄漏问题:循环重启浏览器是否可行?
首先直接给你明确答案:是的,每次循环关闭并重新打开浏览器绝对是解决你当前内存泄漏问题的有效方案,而且你的内存增长问题,本质上并不是因为“永不停歇的.js脚本”本身,而是因为你一直在复用同一个浏览器和页面实例,长时间运行后累积了未被回收的资源导致的。
为什么你的脚本会内存泄漏?
你当前的逻辑是:启动一次浏览器、打开一个页面,登录后就在这个页面里无限循环跳转目标页面。虽然每次page.goto会加载新页面,但Chromium(Puppeteer依赖的浏览器内核)会残留很多内存碎片——比如之前页面的DOM节点、JS上下文、缓存资源、甚至一些未被正确回收的浏览器进程资源。这些东西单次循环看起来占比不大,但12小时的累积下来,就会把你5美元Droplet的小内存彻底占满,同时拖慢页面加载速度(从5秒涨到30秒就是内存不足导致的卡顿)。
重启浏览器为什么能解决问题?
每次循环结束后关闭浏览器,会彻底销毁所有和这个浏览器实例关联的进程、内存资源,相当于每次循环都“从零开始”,从根本上避免了资源累积。不过要注意必须正确关闭浏览器和页面,不能只关闭页面不关闭浏览器,否则浏览器进程还是会留在后台占用内存。
修改后的脚本示例(带优化)
我帮你调整了脚本逻辑,把浏览器的创建/销毁放到循环内部,还修复了你原脚本里的一个明显错误(waitForTimeout应该是waitForSelector),同时加了错误处理和延迟,避免脚本崩溃或被网站封禁:
const puppeteer = require('puppeteer-extra') puppeteer.use(require('puppeteer-extra-plugin-stealth')()) const fs = require('fs'); const performance = require('perf_hooks').performance; (async () => { let runcount = 0 let foundcount = 0 while(true){ let browser = null; let page = null; try { const t0 = performance.now(); // 每次循环重新启动浏览器 browser = await puppeteer.launch({ args: ['--no-sandbox'], headless: true }) page = await browser.newPage() // 登录流程 await page.goto('https://example.com/login', {waitUntil: 'load', timeout: 0}); await page.type('[name=email]','xxx@xxx.com'); await page.click('button[type="submit"]'); // 原脚本这里写错了,应该是等待密码选择器出现,不是waitForTimeout await page.waitForSelector('input[name=password]', { timeout: 0 }); await page.type('[name=password]','xxx'); await page.click('button[type="submit"]'); // 跳转到目标页面检查优惠 await page.goto('https://www.example.com/xxx', {waitUntil: 'load', timeout: 0}); // 这里放你的page.$$eval逻辑 // do some await page.$$eval here runcount++; const t1 = performance.now(); console.log(`Run: ${runcount} | Runtime: ${Math.floor((t1 - t0)/1000)}s`); } catch (err) { console.error(`第${runcount}次循环出错:`, err); } finally { // 确保无论成功失败,都关闭页面和浏览器释放内存 if (page) await page.close().catch(err => console.error('关闭页面失败:', err)); if (browser) await browser.close().catch(err => console.error('关闭浏览器失败:', err)); } // 加个1秒延迟,避免请求太频繁被反爬 await new Promise(resolve => setTimeout(resolve, 1000)); } })()
额外优化建议
如果每次登录太耗时,你可以把登录后的Cookie保存下来,后续循环直接加载Cookie跳过登录步骤,节省时间和资源:
- 第一次登录成功后,添加代码保存Cookie:
const cookies = await page.cookies(); fs.writeFileSync('auth_cookies.json', JSON.stringify(cookies)); - 后续循环时,启动页面后先加载Cookie再跳转目标页面:
await page.setCookie(...JSON.parse(fs.readFileSync('auth_cookies.json'))); // 直接跳转到目标页面,不用再登录 await page.goto('https://www.example.com/xxx', {waitUntil: 'load', timeout: 0});
总结
- 内存泄漏的核心原因是复用单一浏览器/页面实例导致资源累积,和脚本持续运行本身无关
- 每次循环重启浏览器是根治内存泄漏的有效方法
- 搭配错误处理、延迟控制和Cookie复用,能让你的脚本在小内存Droplet上稳定运行更长时间
内容的提问来源于stack exchange,提问作者haliofin
相关产品推荐
相关产品推荐

