Puppeteer:新建Browser与Page哪个更耗内存?Heroku资源优化咨询
复用Puppeteer Browser实例解决Heroku Dyno内存问题
绝对应该把Browser设为全局变量复用,这正是解决你当前内存溢出问题的核心方案。
为什么当前方案会炸内存?
每次处理POST请求都启动一个全新的Browser实例,代价极高——每个Chromium Browser本身就会占用几十MB甚至上百MB的内存(包括主进程、渲染进程、V8引擎等)。在512MB的免费Heroku Dyno上,同时处理十几二十个请求就可能把内存占满,更别说100次请求了,Dyno自然会因为内存超限被终止。
而单个Page实例的内存开销要小得多,复用同一个Browser创建/销毁Page,能大幅降低内存占用,同时还能节省每次启动Chromium的时间,提升API响应速度。
优化后的完整代码示例
const express = require('express'); const puppeteer = require('puppeteer'); const app = express(); // 全局Browser实例,仅启动一次 let browser; // 启动Express前先初始化Browser (async () => { try { browser = await puppeteer.launch({ args: [ '--no-sandbox', '--disable-setuid-sandbox', ], // 可选:启用新版无头模式,进一步节省内存 headless: 'new' }); console.log('Puppeteer Browser initialized successfully'); } catch (err) { console.error('Failed to initialize Puppeteer Browser:', err); process.exit(1); // 初始化失败直接退出,避免服务异常运行 } })(); // 解析JSON请求体 app.use(express.json()); app.post('/scrape-images', async (req, res) => { let page; const { url } = req.body; if (!url) { return res.status(400).send({ errormsg: 'URL parameter is required' }); } try { // 检查Browser是否正常运行,意外崩溃则重启 if (!browser || !browser.isConnected()) { browser = await puppeteer.launch({ args: [ '--no-sandbox', '--disable-setuid-sandbox', ], headless: 'new' }); } page = await browser.newPage(); // 优化:拦截不必要的资源,减少内存占用 await page.setRequestInterception(true); page.on('request', (req) => { const blockedResources = ['stylesheet', 'script', 'image', 'media', 'font']; if (blockedResources.includes(req.resourceType())) { req.abort(); } else { req.continue(); } }); await page.setViewport({ width: 1200, height: 1200 }); await page.goto(url, { waitUntil: 'load', timeout: 0 }); const imgs = await page.$$eval('.img-wrapper img[src]', imgs => imgs.map(img => img.getAttribute('src')) ); res.send({ imageList: imgs }); } catch (e) { console.error('Scraping failed:', e); res.status(500).send({ errormsg: "Something went wrong with the scrapper" }); } finally { // 确保Page被关闭,释放内存 if (page) { await page.close().catch(err => console.error('Failed to close page:', err)); } } }); // 可选:健康检查路由,用于Heroku监控 app.get('/health', (req, res) => { res.sendStatus(browser && browser.isConnected() ? 200 : 503); }); const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`Server running on port ${PORT}`); });
额外优化建议
- 限制并发Page数量:即使复用Browser,同时打开太多Page也会消耗内存。可以用
p-limit之类的库限制并发请求数,比如设置最多5个并发Page。 - 清理Page残留数据:在每次关闭Page前,可以调用
await page.deleteCookie()或清理本地存储,避免残留数据占用内存。 - 监控内存使用:可以在代码中加入内存监控日志,比如
console.log(process.memoryUsage()),方便排查内存异常。
内容的提问来源于stack exchange,提问作者adithya anil
相关产品推荐
相关产品推荐

