Firebase Cloud Functions上Puppeteer:goto()与waitForSelector()始终失败
我之前在无服务器环境下用Puppeteer做爬虫时也碰到过几乎一模一样的问题——本地跑完全正常,一部署到Cloud Functions就卡住超时,连日志都打不出来。结合你的代码和场景,给你几个优先级从高到低的解决方案:
1. 先修正低级拼写错误
你在puppeteerOptions里写的是headerless: true,这是个拼写错误!正确的参数名是headless。这个错误会导致浏览器无法以无头模式启动,本地环境可能因为系统自带Chrome能自动 fallback,但云端的容器环境里直接就会卡住,连浏览器都启动不起来,自然不会走到后续的console.log。
修正后的配置:
puppeteerOptions: { headless: true, // 修正拼写错误 args: [ '--disable-gpu', '--no-sandbox', ] }
2. 优化Puppeteer启动参数适配云端环境
Cloud Functions的容器环境资源有限,默认的Puppeteer参数很容易导致浏览器崩溃或卡住。建议添加几个关键参数,同时开启日志输出方便排查:
puppeteerOptions: { headless: "new", // 推荐用新版无头模式,兼容性更好 args: [ '--disable-gpu', '--no-sandbox', '--disable-dev-shm-usage', // 解决云端/dev/shm内存不足的问题,这是高频坑点 '--single-process', // 减少进程数量,节省资源 '--no-zygote', // 配合single-process,避免额外的进程开销 ], dumpio: true, // 输出浏览器的日志到Cloud Functions日志,能帮你看到启动或加载时的报错 }
3. 控制并发数量,避免资源耗尽
你的代码里用Promise.all一次性启动了所有页面的浏览器实例,Cloud Functions的CPU和内存根本扛不住这么多并发的浏览器进程,直接会导致资源耗尽卡住。建议用p-limit这类库限制并发数,比如每次最多跑2个实例:
首先安装依赖:
npm install p-limit
然后修改start函数:
const pLimit = require('p-limit'); const limit = pLimit(2); // 根据函数内存配置调整,1GB内存建议设2-3 ScraperManager.prototype.start = async function() { var webpagePromises = [] for (const agency of agencies) { for (page_num = 0; page_num < num_of_pages; page_num++) { const url = setupUrl(agency, page_num); // 用limit包装getWebpage,控制并发执行 const webpagePromise = limit(() => getWebpage(agency, page_num, url)) webpagePromises.push(webpagePromise) } } return Promise.all(webpagePromises) }
4. 升级Cloud Functions的资源配置
Puppeteer跑浏览器非常吃内存,默认的Cloud Functions内存(256MB)肯定不够用。建议把内存调到1GB甚至2GB,同时把超时时间拉到最大允许的540秒:
你可以在firebase.json里配置:
{ "functions": { "runtimeOptions": { "memory": "1GB", "timeoutSeconds": 540 } } }
或者部署时用命令行指定:
firebase deploy --only functions:scraper --memory 1GB --timeout 540
5. 改用专为无服务器优化的Chrome包
官方Puppeteer自带的Chrome体积大,在云端环境兼容性一般。推荐用@sparticuz/chromium,这是专门为无服务器环境优化的轻量Chrome,启动更快更稳定:
首先安装依赖:
npm install @sparticuz/chromium puppeteer-core
然后修改getWebpage函数:
const chromium = require('@sparticuz/chromium'); const puppeteer = require('puppeteer-core'); async function getWebpage(agency, page_num, url) { var data = {} let browser; try { const executablePath = await chromium.executablePath(); browser = await puppeteer.launch({ executablePath: executablePath, headless: chromium.headless, args: [...chromium.args, '--disable-dev-shm-usage'], dumpio: true, }); const page = await browser.newPage(); await page.setUserAgent(constants.userAgent); // 加上waitUntil确保页面加载完成,避免过早等待选择器 await page.goto(url, {timeout: 0, waitUntil: 'networkidle2'}); console.log(`goto completes for ${url}`); await page.waitForSelector('div.main_container', {timeout: 0}); console.log(`waitFor completes for ${url}`); const body = await page.$eval('body', el => el.innerHTML); data['html'] = body; return data; } catch (err) { console.error(`Puppeteer error for ${url}:`, err); return; } finally { if (browser) await browser.close(); } }
按这个顺序排查,应该能解决你的问题。先从拼写错误和参数优化开始,这两个是最常见的原因。
内容的提问来源于stack exchange,提问作者Koh

