Serverless 环境中使用 Chrome + Puppeteer 指南
为什么需要这个方案?
在 Serverless 环境(Vercel、AWS Lambda 等)中,没有预装浏览器。但很多场景需要真实浏览器来渲染页面:
- 抓取 SPA(单页应用)的动态内容
- 网页截图 / 生成 PDF
- 网页转 Markdown
本文介绍如何在 Serverless 中运行 Headless Chrome。
核心依赖
| 包 | 作用 |
|---|---|
puppeteer-core | Puppeteer 的轻量版,不自带 Chromium |
@sparticuz/chromium-min | 专为 Serverless 优化的 Chromium 二进制文件 |
puppeteer-core和puppeteer的区别:puppeteer会自动下载完整 Chromium(~170MB),不适合 Serverless。puppeteer-core不带浏览器,需要手动指定可执行路径。
安装
pnpm add puppeteer-core @sparticuz/chromium-min
Next.js 配置
在 next.config.ts 中将 Chromium 标记为外部包,避免被 Next.js 打包:
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
serverExternalPackages: ["@sparticuz/chromium-min"],
};
export default nextConfig;
核心代码:创建浏览器实例
关键点是区分本地开发和生产环境,使用不同的 Chromium 来源:
// Chromium 压缩包的远程地址(生产环境按需下载)
const CHROMIUM_PATH =
"https://github.com/Sparticuz/chromium/releases/download/v143.0.0/chromium-v143.0.0-pack.x64.tar";
async function getBrowser() {
const puppeteerCore = await import("puppeteer-core").then(
(mod) => mod.default
);
if (process.env.VERCEL_ENV === "production") {
// 生产环境:使用 @sparticuz/chromium-min
const chromium = await import("@sparticuz/chromium-min").then(
(mod) => mod.default
);
const executablePath = await chromium.executablePath(CHROMIUM_PATH);
return await puppeteerCore.launch({
args: chromium.args,
executablePath,
headless: true,
});
} else {
// 本地开发:使用系统安装的 Chrome
return await puppeteerCore.launch({
executablePath:
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
headless: true,
});
}
}
使用浏览器实例
export async function POST(request: NextRequest) {
const { url } = await request.json();
const browser = await getBrowser();
const page = await browser.newPage();
// 设置 User-Agent 避免被识别为爬虫
await page.setUserAgent(
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36'
);
await page.setExtraHTTPHeaders({
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
});
await page.goto(url, { waitUntil: "networkidle0" });
const html = await page.content();
// 用完必须关闭,释放资源
await browser.close();
return NextResponse.json({ html });
}