本地正常的Puppeteer部署Vercel报错,求解决或替代方案
解决Vercel部署下页面资源总传输量统计问题(替代Puppeteer方案)
一、核心需求明确
你需要统计页面及所有关联资源的总传输量,当前使用Puppeteer在Vercel部署时遇到Chrome版本匹配问题,以下是可行的替代方案及优化建议:
二、替代方案推荐
1. Playwright(推荐,精度与Puppeteer一致)
Playwright是微软维护的自动化工具,对Vercel这类Serverless环境兼容性更好,无需手动配置Chromium路径,会自动适配运行环境。
实现步骤:
- 安装依赖:
npm install playwright
- 替换统计函数代码:
import { chromium } from 'playwright'; async function calculateTotalDataTransferWithResources(url) { let browser; try { // Vercel环境下自动适配Chromium browser = await chromium.launch({ headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox'] }); const page = await browser.newPage(); // 等待页面完全加载(包括所有资源) await page.goto(url, { waitUntil: 'networkidle0' }); // 复用Puppeteer的performance API逻辑获取资源数据 const networkData = await page.evaluate(() => { return window.performance.getEntriesByType('resource').map(resource => ({ name: resource.name, transferSize: resource.transferSize })); }); const totalData = networkData.reduce((total, resource) => total + resource.transferSize, 0); return { totalData, networkData }; } catch (error) { console.error('统计错误:', error.message); return { totalData: 0, networkData: [] }; } finally { if (browser) await browser.close(); } }
- Vercel部署说明:Playwright会在
postinstall阶段自动下载对应版本的Chromium,无需额外配置脚本,直接部署即可。
2. 纯Node.js网络请求方案(轻量,无浏览器依赖)
如果目标页面以静态资源为主,无需统计JS动态加载的资源,可以用axios+cheerio模拟资源加载流程,计算总传输量。
实现步骤:
- 安装依赖:
npm install axios cheerio
- 统计函数代码:
import axios from 'axios'; import cheerio from 'cheerio'; import { URL } from 'url'; async function calculateTotalDataTransfer(url) { let totalSize = 0; const visitedUrls = new Set(); // 递归请求资源并统计大小 async function fetchResource(resourceUrl) { if (visitedUrls.has(resourceUrl)) return; visitedUrls.add(resourceUrl); try { const response = await axios.get(resourceUrl, { responseType: 'stream', validateStatus: () => true // 忽略错误状态码,继续统计 }); // 优先用Content-Length,否则通过流计算大小 const contentLength = response.headers['content-length']; if (contentLength) { totalSize += parseInt(contentLength, 10); } else { let size = 0; for await (const chunk of response.data) { size += chunk.length; } totalSize += size; } // 解析HTML页面,提取内嵌资源 const contentType = response.headers['content-type']; if (contentType && contentType.includes('text/html')) { const html = await axios.get(resourceUrl); const $ = cheerio.load(html.data); const baseUrl = new URL(resourceUrl); // 提取脚本、样式、图片等资源URL const resources = [ ...$('script[src]').map((_, el) => $(el).attr('src')).get(), ...$('link[rel="stylesheet"]').map((_, el) => $(el).attr('href')).get(), ...$('img[src]').map((_, el) => $(el).attr('src')).get(), ...$('link[rel="preload"]').map((_, el) => $(el).attr('href')).get() ].filter(Boolean); // 转换为绝对URL并并行请求 const absoluteResources = resources.map(src => { try { return new URL(src, baseUrl).href; } catch { return null; } }).filter(Boolean); await Promise.all(absoluteResources.map(fetchResource)); } } catch (error) { console.error('资源请求失败:', resourceUrl, error.message); } } await fetchResource(url); return { totalData: totalSize, networkData: Array.from(visitedUrls).map(url => ({ name: url })) }; }
- 局限性:无法统计JS动态渲染加载的资源(如点击事件触发的请求),仅适合静态页面场景。
3. Edge Functions + Undici(极致轻量,适配Vercel Edge环境)
如果使用Next.js Edge API Routes,可采用undici做HTTP请求,结合HTML解析,适合Edge环境的低延迟需求。
代码示例(Edge API Route):
import { fetch } from 'undici'; import { parse } from 'node-html-parser'; import { URL } from 'url'; export const config = { runtime: 'edge', }; export default async function handler(req) { const { url } = await req.json(); let totalSize = 0; const visitedUrls = new Set(); async function fetchResource(resourceUrl) { if (visitedUrls.has(resourceUrl)) return; visitedUrls.add(resourceUrl); try { const response = await fetch(resourceUrl); // 统计资源大小 const contentLength = response.headers.get('content-length'); if (contentLength) { totalSize += parseInt(contentLength, 10); } else { const buffer = await response.arrayBuffer(); totalSize += buffer.byteLength; } // 解析HTML资源 const contentType = response.headers.get('content-type'); if (contentType && contentType.includes('text/html')) { const html = await response.text(); const root = parse(html); const baseUrl = new URL(resourceUrl); const resources = [ ...root.querySelectorAll('script[src]').map(el => el.getAttribute('src')), ...root.querySelectorAll('link[rel="stylesheet"]').map(el => el.getAttribute('href')), ...root.querySelectorAll('img[src]').map(el => el.getAttribute('src')), ...root.querySelectorAll('link[rel="preload"]').map(el => el.getAttribute('href')) ].filter(Boolean); const absoluteResources = resources.map(src => { try { return new URL(src, baseUrl).href; } catch { return null; } }).filter(Boolean); await Promise.all(absoluteResources.map(fetchResource)); } } catch (error) { console.error('资源请求失败:', resourceUrl, error.message); } } await fetchResource(url); return new Response(JSON.stringify({ totalData: totalSize, networkData: Array.from(visitedUrls) }), { headers: { 'Content-Type': 'application/json' }, }); }
三、方案选择建议
- 需要精确统计所有资源(包括动态加载):选Playwright,兼容性和精度与Puppeteer一致,Vercel部署无额外配置。
- 静态页面场景,追求轻量:选纯Node.js网络请求方案,无浏览器依赖,部署成本更低。
- 低延迟需求,适配Edge环境:选Edge Functions方案,响应更快,适合高并发场景。
内容的提问来源于stack exchange,提问作者Info Applikation
相关产品推荐
相关产品推荐

