如何解决Heroku上Playwright爬虫应用频繁崩溃问题?
针对Heroku上Playwright爬虫频繁崩溃的调整方案
强制每次任务启动全新浏览器实例
绝对不要复用浏览器上下文或页面实例,每次爬虫任务执行时都调用browser.launch()创建全新实例,任务结束后必须调用browser.close()彻底关闭。Heroku容器资源有限,复用实例极易引发连接泄漏,进而触发ENOTCONN错误。示例代码:const { chromium } = require('playwright'); async function runCrawler() { let browser; try { browser = await chromium.launch({ headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox'] // 必须保留Heroku适配参数 }); const page = await browser.newPage(); // 你的爬虫逻辑代码 } catch (err) { console.error('爬虫执行出错:', err); } finally { if (browser) await browser.close(); // 无论成功失败都要关闭浏览器 } }添加资源限制与错误重试机制
Heroku Eco实例仅512MB内存,运行Chromium很容易资源耗尽。启动浏览器时添加内存相关限制参数,同时给页面跳转、资源加载等关键操作增加重试逻辑:// 启动浏览器时追加资源限制参数 browser = await chromium.launch({ headless: true, args: [ '--no-sandbox', '--disable-setuid-sandbox', '--memory-pressure-off', '--disable-dev-shm-usage' // 避免/dev/shm空间不足问题 ] }); // 封装重试函数 async function withRetry(fn, retries = 2) { try { return await fn(); } catch (err) { if (retries > 0 && (err.message.includes('ENOTCONN') || err.message.includes('Resource temporarily unavailable'))) { console.log('遇到资源错误,重试操作...'); return withRetry(fn, retries - 1); } throw err; } } // 使用重试包裹关键操作 await withRetry(async () => { await page.goto('目标页面URL', { timeout: 30000 }); });确保任务串行执行
即便设置了每20分钟执行一次,也要保证前一次任务完全结束后再启动下一次。如果前一次任务因资源泄漏未彻底清理,新任务启动会加剧资源占用。可以用Redis实现互斥锁,确保同一时间只有一个爬虫任务在运行:const redis = require('redis'); const client = redis.createClient(process.env.REDIS_URL); async function acquireLock() { const lockKey = 'crawler-running-lock'; // 设置30秒过期锁,防止任务崩溃后锁无法释放 const result = await client.set(lockKey, 'active', { EX: 30, NX: true }); return result === 'OK'; } async function runCrawler() { const hasLock = await acquireLock(); if (!hasLock) { console.log('已有爬虫任务在运行,跳过本次执行'); return; } try { // 爬虫逻辑代码 } finally { await client.del('crawler-running-lock'); // 释放锁 if (browser) await browser.close(); } }优化爬虫资源占用
执行heroku logs --tail查看实时日志,关注是否有R14内存超限提示。如果频繁出现,要么升级到Basic计划(1GB内存),要么优化爬虫逻辑:// 禁用图片、视频、字体加载,减少资源消耗 await page.route('**/*', (route) => { const resourceType = route.request().resourceType(); if (['image', 'video', 'font'].includes(resourceType)) { route.abort(); } else { route.continue(); } });
内容的提问来源于stack exchange,提问作者learncode
相关产品推荐
相关产品推荐

