Puppeteer部署到Heroku后超时、browser未定义错误如何解决?
问题根因分析
ReferenceError: browser is not defined报错:browser变量使用const声明在try块内部,属于块级作用域,catch块无法访问该变量,出错后执行await browser.close()时直接触发未定义错误。
- 导航超时、运行卡顿:Heroku实例本身算力、带宽资源有限,且你当前的Puppeteer配置没有做性能优化,默认等待页面全量资源加载完成,很容易触发30s超时阈值。另外你配置的
unhandledRejection监听每次执行函数都会重复绑定,会导致内存泄漏进一步拖慢运行速度。
- 导航超时、运行卡顿:Heroku实例本身算力、带宽资源有限,且你当前的Puppeteer配置没有做性能优化,默认等待页面全量资源加载完成,很容易触发30s超时阈值。另外你配置的
- 定时任务逻辑风险:当前使用
setInterval每30s执行一次爬取任务,如果单次爬取耗时超过30s,会出现多个爬取任务并发执行,进一步抢占资源导致卡顿。
- 定时任务逻辑风险:当前使用
修复方案
1. 调整变量作用域 + 资源清理逻辑
将browser声明提到try块外部,同时用finally块统一处理浏览器实例关闭,避免重复绑定全局事件监听。
2. 优化Puppeteer配置
新增Heroku环境必需的--disable-dev-shm-usage参数,禁用不必要的资源加载,调整页面跳转的等待策略。
3. 优化定时任务逻辑
改用串行执行的定时逻辑,单次爬取完成后再间隔30s启动下一次,避免并发抢占资源。
修复后的参考代码
const puppeteer = require('puppeteer'); const express = require('express'); const app = express(); let GList = []; // 显卡URL提前声明,避免每次执行重复创建 const GPU_URLS = [RTX3090, RTX3080TI, RTX3080, RTX3070TI, RTX3070, RTX3060TI, RTX3060, RTX2060, GTX1660TI]; async function scrapeProduct() { let browser = null; try { console.log('2'); const array = []; browser = await puppeteer.launch({ headless: true, // 显式开启无头模式 args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', // 适配Heroku小内存环境,避免共享内存不足 '--disable-gpu', '--no-first-run', '--no-zygote' ], }); const page = await browser.newPage(); // 拦截无用资源,大幅提升加载速度 await page.setRequestInterception(true); page.on('request', (req) => { const uselessResource = ['image', 'stylesheet', 'font', 'media', 'script']; if (uselessResource.includes(req.resourceType())) { req.abort(); } else { req.continue(); } }); await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36'); for (let i = 0; i < GPU_URLS.length; i++) { console.log('page'); // 只等待DOM加载完成,不等待全量资源,超时时间调整到60s,也可设为0关闭超时 await page.goto(GPU_URLS[i], { waitUntil: 'domcontentloaded', timeout: 60000 }); const result = await page.evaluate(() => { const List = []; const allProduct = document.querySelectorAll("div.prod_info"); allProduct.forEach(ele => { if (ele.querySelector('ul.price_box > li > span.site_btn.orderNow > button') !== null || ele.querySelector('ul.price_box > li > span.site_btn.add24hCart > button') !== null) { const GraphicCard = { 'name': ele.querySelector('a').innerText, 'price': ele.querySelector('.value').innerText, 'link': ele.querySelector('a').href } List.push(GraphicCard); } }) return List; }) array.push(result); } console.log('3'); return array; } catch (err) { console.log('爬取出错:', err); return []; } finally { // 无论成功失败都关闭浏览器实例 if (browser) { await browser.close(); } } } async function createList() { try { console.log('1'); GList = []; GList.push(await scrapeProduct()); } catch (err) { console.log('任务出错:', err); } finally { // 本次任务执行完成后,间隔30s再执行下一次 setTimeout(createList, 30000); } } // 启动首次任务 createList(); app.get('/', (req, res) => { res.send(GList); }) const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`服务运行在端口${PORT}`); })
内容的提问来源于stack exchange,提问作者T.L
相关产品推荐
相关产品推荐

