Puppeteer部署至Heroku后仅运行三次即终止问题求助
Heroku上Puppeteer爬取超时(内存不足)的解决方案
你遇到的核心问题是每次爬取都新建浏览器实例,Heroku免费/基础dyno内存有限(512MB),连续创建3个浏览器后内存耗尽,导致第4次启动浏览器时触发超时错误。以下是针对性的解决步骤:
1. 复用浏览器实例(最关键优化)
不要在scrapeData函数里每次调用puppeteer.launch(),改为全局初始化一个浏览器实例,每次爬取仅创建新页面,用完立即关闭页面释放资源。
修改后的爬虫模块示例:
// 全局维护一个浏览器实例 let browser; // 初始化浏览器(服务启动时调用一次) async function initBrowser() { if (!browser || !browser.isConnected()) { browser = await puppeteer.launch({ args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', // 避免Heroku的/dev/shm内存限制 '--disable-gpu', '--single-process', // 单进程模式大幅降低内存占用 '--no-zygote', '--headless=new' // 新版无头模式更高效 ], headless: 'new', timeout: 60000 // 延长浏览器启动超时时间 }); } return browser; } // 修改后的scrapeData函数 async function scrapeData(code) { await initBrowser(); // 确保浏览器已启动 const page = await browser.newPage(); try { // 禁用图片、CSS加载,减少内存占用 await page.setRequestInterception(true); page.on('request', (req) => { if (req.resourceType() === 'image' || req.resourceType() === 'stylesheet') { req.abort(); } else { req.continue(); } }); await page.goto("website url", { timeout: 60000 }); await page.type('#crit-keyword', code, { delay: 50 }); // 模拟真实输入速度,避免反爬 await page.click('#search-button'); await page.waitForSelector(".result__headline", { timeout: 30000 }); await page.click(".result__headline"); await page.waitForSelector("div.text:nth-child(2)", { timeout: 30000 }); const data = await page.evaluate(() => { let classTitle = document.querySelector("div.text:nth-child(2)").textContent .toLowerCase().split(' ') .map((s) => s.charAt(0).toUpperCase() + s.substring(1)).join(' ').replace('Ii', "II"); let classDesc = document.querySelector(".section--description > div:nth-child(2)").textContent.replace('Lec/lab/rec.', '').trim(); return { title: classTitle, desc: classDesc }; }); console.log(`== Finished grabbing ${code}`); return data; } catch (err) { console.error(`Failed to grab ${code}:`, err); throw err; // 抛出错误以便上层处理 } finally { await page.close(); // 必须关闭页面释放内存 } } // 服务关闭时清理浏览器实例 process.on('exit', async () => { if (browser) await browser.close(); });
2. 添加内存监控与重试机制
如果仍遇到内存不足问题,可以在爬取前检查内存状态,超过阈值则等待内存释放,同时添加超时重试逻辑:
// 检查内存是否可用(Heroku免费dyno总内存512MB,预留100MB缓冲) function canProceed() { const memory = process.memoryUsage(); return memory.heapUsed < 400 * 1024 * 1024; // 堆内存使用不超过400MB } // 等待内存释放的函数 async function waitForMemory() { while (!canProceed()) { console.log("Memory usage high, waiting for release..."); await new Promise(resolve => setTimeout(resolve, 5000)); // 每5秒检查一次 } } // 修改爬取调用逻辑 async function getClassData(classesTaken) { const results = []; for (let i = 0; i < classesTaken.length; i++) { await waitForMemory(); // 确保内存可用再爬取 try { const data = await scrapeData(classesTaken[i].code); results.push(data); } catch (err) { // 针对超时错误重试一次 if (err.name === 'TimeoutError') { console.log(`Retrying ${classesTaken[i].code}...`); await waitForMemory(); const data = await scrapeData(classesTaken[i].code); results.push(data); } else { throw err; } } } return results; }
3. 其他补充建议
- 升级dyno:如果爬虫任务较重,考虑升级到Heroku标准dyno(1GB内存),能从根本上缓解内存压力。
- 限制并发:保持串行爬取,避免同时启动多个页面导致内存暴涨。
内容的提问来源于stack exchange,提问作者jackhart591
相关产品推荐
相关产品推荐

