如何在 AWS Lambda 上部署 Puppeteer(3 种方式对比)

最后更新于 August 11, 2026
Deploying a headless browser to serverless infrastructure
AI 摘要
一篇部署指南,对比了用于 Puppeteer 的 Lambda Layers、容器镜像和 ZIP 包方案,并提供了最新的打包、兼容性和排障建议。

Chromium 大约有 280MB。AWS Lambda 解压后的包体上限是 250MB。如果你曾经试过直接 npm install puppeteer 然后部署到 Lambda,应该很清楚这笔账怎么算——根本算不过来。

我已经花了足够多的时间在凌晨 2 点排查“Failed to launch the browser process”这类报错,深知这个话题值得认真比较,而不是那种只讲一种方案、却让你对另外两种还是一头雾水的教程。所以这篇文章就是来做这件事的:Layers、Container Images 和直接上传 ZIP 三种方案对比,一份更新到 2026 年仍然有效的版本兼容矩阵,以及一个专门整理了最容易踩到的 5 类错误的排障部分。

什么是 AWS Lambda 上的 Puppeteer?为什么要用它?

Puppeteer 是一个 Node.js 库,可以通过 Chrome DevTools Protocol 控制无头 Chromium。Lambda 是 AWS 的无服务器计算服务——按调用次数计费,自动扩缩容,你完全不用碰服务器。把两者组合起来,你就得到了一套浏览器自动化方案,可以在不需要预先配置任何 EC2 实例的情况下,轻松并发扩展到数百次执行。

它在各团队中的典型用途其实很固定:网页抓取、截图和 PDF 生成、合成监控、为 SEO 预渲染单页应用,以及自动化 UI 测试。问题始终还是前面提到的那一个——Chromium 体积太大,而 Lambda 的包体限制太小。所以没人会把完整的 puppeteer(它自带 Chromium 下载)直接部署到 Lambda。通常的做法是使用不包含浏览器的 puppeteer-core,再配上一个为 Lambda 优化过的 Chromium 二进制文件,最常见的是 @sparticuz/chromium

这个替换——用 puppeteer-core 代替 puppeteer——在你写任何部署配置之前,就已经解决了 80% 的体积问题。

Layers、容器镜像和 ZIP:先选路线,再动手

我在研究时最在意的一点是:几乎所有现有教程都只讲一种部署方式。AWS SAM 教程讲的是 Layers,CDK 示例用的是 Docker,某篇 Substack 文章又在讲把 Chromium 二进制放在 S3 上再配合原始 ZIP 上传。没有人把这些方案放在一起比较,这就导致你真正需要先做的那个决定——“我的场景更适合哪种部署方式?”——被完全跳过去了。

所以我们现在把它补上。

对比项Lambda Layers容器镜像(Docker)直接 ZIP 上传
最大包体解压后 250 MB(所有层总和)10 GB 镜像解压后 250 MB
部署复杂度中等(需要管理 layer ARN)更高(Dockerfile + 推送到 ECR)最低(打包 ZIP 然后上传)
冷启动影响中等略高(镜像拉取更大)中等
Chromium 更新流程重新发布 layer 版本重新构建镜像重新上传 ZIP
最适合快速原型、Serverless Framework 用户生产环境、团队已有 Docker CI简单的一次性函数
IaC 支持SAM、Serverless FrameworkCDK、SAM、Terraform控制台、任意 IaC

这两个限制——250MB 和 10GB——都直接来自 AWS 官方 Lambda 配额文档。这不是随便拍脑袋的数字,而是会在一开始就决定你整套部署策略的关键约束。

如果你想要一个简单的判断标准,我的建议是:如果你在做原型验证,或者本来就在用 Serverless Framework,就先从 Layers 开始。如果你要上生产,而且团队已经有 Docker CI/CD,那就选容器镜像——10GB 的空间会让你从容很多。如果你只是偶尔需要某个函数截个图,直接 ZIP 是最省事的方案。

这三条路径底层都依赖同一个核心组合:puppeteer-core + @sparticuz/chromium。区别只是你怎么打包它们,而不是打包什么。

Three abstract packaging choices for a browser workload

2026 版本兼容矩阵(别再靠猜)

这部分才是真正会把人拖几个月的地方,而不是几小时。Stack Overflow 和 GitHub issue 里最常见、最刺耳的抱怨,不是“怎么部署”,而是“为什么我原本能跑的部署,在一次 npm 更新后悄悄挂了”。罪魁祸首通常都是 @sparticuz/chromiumpuppeteer-core 和 Node.js 运行时版本之间不匹配。

先说最重要的一点:chrome-aws-lambda(最初的 alixaxel 包)已经弃用。 它在 Node 18 及以上版本上已经不可靠,而且也没有跟上 Chromium 的更新节奏。如果你看到某篇教程还在引用它,直接关掉那个标签页——那篇内容已经过时了。现在所有新的教程都应该改用 @sparticuz/chromium

不要靠肉眼去配包的大版本号,直接用下面这套兼容原则:

组件版本规则部署前要确认什么
puppeteer-core选择你的应用所需的 Puppeteer 版本查清楚该 Puppeteer 版本支持哪个 Chromium 构建
@sparticuz/chromium它的大版本跟 Chromium 的大版本走,不跟 Puppeteer 走对照 Puppeteer 的支持表匹配 Chromium 构建,并阅读 Sparticuz 的发布说明
AWS Lambda Node.js 运行时使用当前仍受支持的 Lambda 运行时每次更新运行时或依赖后都要做一次调用测试
架构npm 包里包含的是 x64 二进制;arm64 支持从 Chromium v135 开始,可通过 arm64 layer 或 remote pack 实现Lambda 架构、layer/pack 产物和 Chromium 版本必须完全一致

我这里故意没有直接写死一个固定的包版本组合,因为 @sparticuz/chromium 跟的是 Chromium 的发布节奏,并不是标准语义化版本逻辑。正确的做法是先查看 官方 Puppeteer Chromium 支持页面,确认你所选 Puppeteer 版本对应的 Chromium 主版本,然后再选择同主版本的 @sparticuz/chromium。最后,再去看 Sparticuz 的发布说明,确认补丁级破坏性变更和架构细节。不要仅仅因为两个包的主版本号看起来一样,就直接安装同号版本,除非前面这两个来源已经明确证实了这种映射关系。

Abstract serverless browser deployment architecture

如何使用 Lambda Layers 在 AWS Lambda 上部署 Puppeteer

Lambda Layer 可以把 Chromium 和函数代码分开打包,这样你的实际处理函数会更小,而且同一个 Chromium layer 还能复用于多个函数。这几种方案里,它最接近“快速上手”。

第 1 步:安装 puppeteer-core 和 -min

当 Chromium 文件放在 Lambda Layer 中时,可以通过 @sparticuz/chromium-min 保持函数包体尽量小。请把下面的占位版本替换成你前面确认过的兼容版本:

npm install puppeteer-core@$PUPPETEER_VERSION \
  @sparticuz/chromium-min@$CHROMIUM_VERSION

这里安装的是 puppeteer-core,不是 puppeteer,因为它不会自动下载浏览器。-min 包提供启动辅助能力,而 layer 负责在 /opt/chromium 下提供 Brotli 压缩后的 Chromium 文件。

第 2 步:创建或引用 Chromium Lambda Layer

你可以使用官方 Sparticuz release 附带的架构专用 layer 压缩包,也可以直接从官方仓库自己构建。对于 x86_64 Lambda,文档里的构建方式是:

git clone --depth=1 https://github.com/sparticuz/chromium.git
cd chromium
make chromium.x64.zip

这样会生成 chromium.x64.zip。把它上传到 S3,然后按你实际使用的运行时和架构发布成 Lambda Layer。如果你用的是 arm64,就用对应的 arm64 release 产物或者构建目标;不要把 x64 压缩包挂到 arm64 函数上。

如果你使用 SAM,可以在 template.yaml 里直接引用 layer ARN:

Resources:
  PuppeteerFunction:
    Type: AWS::Serverless::Function
    Properties:
      Layers:
        - arn:aws:lambda:us-east-1:XXXXXXXXXXXX:layer:chromium-layer:1

第 3 步:编写 Lambda Handler

下面是一个可工作的 handler 示例,它会打开页面并返回标题:

import puppeteer from "puppeteer-core";
import chromium from "@sparticuz/chromium-min";

export const handler = async () => {
  const browser = await puppeteer.launch({
    args: puppeteer.defaultArgs({ args: chromium.args, headless: "shell" }),
    executablePath: await chromium.executablePath("/opt/chromium"),
    headless: "shell",
  });
  try {
    const page = await browser.newPage();
    await page.goto("https://example.com", { waitUntil: "domcontentloaded" });
    return { title: await page.title() };
  } finally {
    await browser.close();
  }
};

注意这里的 finally 块。一定要在那里关闭浏览器——如果不关,Lambda 的 warm 环境会在多次调用之间不断积累“僵尸”浏览器进程,最后你会碰到一些莫名其妙的内存错误,而这些错误和你的业务代码根本没关系。

第 4 步:配置内存、超时和架构

内存至少设为 1024 MB——如果任务不只是简单截个图,我建议直接用 1536–2048 MB。超时至少设为 60 秒。架构默认固定为 x86_64,除非你已经针对你所使用的 Chromium 版本明确验证过 arm64 支持(这个会随版本变化)。

第 5 步:部署并测试

sam build && sam deploy --guided

先发一个测试事件,然后如果出问题,立刻去看 CloudWatch Logs——下面排障部分 90% 的错误都能在这些日志里直接找到线索。

如何使用容器镜像(Docker)在 AWS Lambda 上部署 Puppeteer

容器镜像的优势就是彻底解决 250MB 的烦恼,直接把上限抬到 10GB。对于生产环境来说,这通常是更好的选择,尤其是你们团队的 CI 流水线里本来就已经用了 Docker。

第 1 步:创建 Dockerfile

先从官方的 AWS Lambda Node.js 基础镜像 开始,安装依赖,然后设置 handler:

FROM public.ecr.aws/lambda/nodejs:20

COPY package*.json ./
RUN npm install --production

COPY . .

CMD ["index.handler"]

根据你使用的 Chromium 包,你可能还需要通过 yum install 补几个共享库(这个我们在排障部分再展开)——和手动安装完整 Chrome 相比,@sparticuz/chromium 已经内置了大多数所需内容,因此摩擦要小得多。

第 2 步:构建并推送到 Amazon ECR

aws ecr create-repository --repository-name puppeteer-lambda
docker build -t puppeteer-lambda .
docker tag puppeteer-lambda:latest <account-id>.dkr.ecr.<region>.amazonaws.com/puppeteer-lambda:latest
aws ecr get-login-password | docker login --username AWS --password-stdin <account-id>.dkr.ecr.<region>.amazonaws.com
docker push <account-id>.dkr.ecr.<region>.amazonaws.com/puppeteer-lambda:latest

尽量让镜像和 Lambda 函数处在同一个区域——跨区域拉取镜像只会带来你不需要的额外延迟。

第 3 步:从容器镜像创建 Lambda 函数

通过 CLI 或 CDK 把函数指向 ECR 镜像 URI,然后像使用 layer 方案一样设置内存(1536–2048 MB)和超时(60–120 秒)。

第 4 步:部署并测试

发起测试调用并检查输出。和 Layers 相比,这种方式的主要代价是冷启动会略高一些,因为镜像拉取更大,但你会获得更大的依赖空间。

如何通过直接 ZIP 上传在 AWS Lambda 上部署 Puppeteer

这是最朴素的方案——不用管 layer,也不用构建 Docker。适合原型项目,或者某个不需要扩展成完整浏览器自动化平台的单个函数。

第 1 步:在本地安装依赖

如果要做成一个自包含的 ZIP,使用 puppeteer-core + @sparticuz/chromium,并把两个版本都锁定。完整包包含压缩后的 Chromium 文件,并会在运行时解压到 /tmp。只有当这些文件会通过 Lambda Layer 或高速 remote pack URL 单独提供时,才使用 @sparticuz/chromium-min-min 包本身并不包含 Brotli 文件。

第 2 步:打包并压缩函数

npm install --production
zip -r function.zip . -x "*.git*"

这里 --production 很重要——开发依赖会白白吃掉你的 250MB 配额。

第 3 步:上传并配置 Lambda 函数

aws lambda update-function-code --function-name my-puppeteer-fn --zip-file fileb://function.zip

如果你的 ZIP 超过 50MB,就不能直接通过控制台或简单 CLI 调用上传了——你需要先上传到 S3,然后改用 S3 URI。内存、超时和架构的配置方式与前两种方法相同。

第 4 步:部署并测试

照样使用“调用 + 看日志”的流程。对于完整包,chromium.executablePath() 不需要传参数。对于 chromium-min,你必须传入精确的 layer 目录或 remote pack URL,例如前面 layer 结构里用的 chromium.executablePath("/opt/chromium")。remote pack 会把下载工作放到第一次冷启动里,所以要尽量把它部署到离函数更近的地方,并确认产物版本和架构都正确。

在 Lambda 上真正可用的 puppeteer.launch() 参数

这段代码是大家最常复制粘贴的,所以我们最好把它写对。Lambda 的执行环境没有 /dev/shm,不给 GPU 访问权限,而且权限受限——这意味着你在笔记本上能正常工作的默认 puppeteer.launch() 调用,在这里大概率就是跑不起来。

const viewport = {
  width: 1920,
  height: 1080,
  deviceScaleFactor: 1,
  isMobile: false,
  hasTouch: false,
  isLandscape: true,
};

const browser = await puppeteer.launch({
  args: await puppeteer.defaultArgs({ args: chromium.args, headless: "shell" }),
  executablePath: await chromium.executablePath(),
  headless: "shell",
  defaultViewport: viewport,
});

@sparticuz/chromium 里的 chromium.args 数组已经内置了无服务器环境所需的关键参数——比如 --no-sandbox--disable-gpu--disable-dev-shm-usage 等等。使用这个包而不是自己手写参数列表,意义就在于此:它会跟着 Chromium 的需求变化,你就不用自己盯着了。

A calm diagnostic view of browser and cloud workload health

排障:每个开发者都会遇到的 5 个错误

我找到的现有教程里,没有一篇认真写排障部分,这多少有点离谱——毕竟你大概率就是因为遇到了错误,才会读到这篇文章。

“Failed to launch the browser process”

根本原因: 缺少共享库(libnss3.solibatk 等),或者 executablePath 配置不正确。

解决办法: @sparticuz/chromium 已经打包了大部分必需依赖,这也是为什么它比自己手动维护 Chromium 二进制更推荐。如果你用的是 Docker 部署但还是报这个错,那就把缺少的库显式写进 Dockerfile 里,用 yum install 装上。

“Unzipped size must be smaller than 262144000 bytes”

根本原因: 你安装了完整的 puppeteer 包,它自带 Chromium 下载,体积大约 400MB。

解决办法: 改用 puppeteer-core + @sparticuz/chromium。如果你真的需要更大的空间,那就切到容器镜像方案,直接用 10GB 上限。

“Browser disconnected” 或 browser.newPage() 超时

根本原因: Lambda 内存不足,或者你的启动参数里缺少 --disable-gpu 之类的选项。

解决办法: 把内存至少调到 1024MB(我建议更高,下面的基准测试会说明原因),并确保传入的是 chromium.args,而不是一个被删减过的自定义参数列表。

代码原本能跑,但在 Lambda 运行时更新后坏掉了

根本原因: AWS 会定期修补底层运行时,这可能会改变共享库版本,或者在你不知不觉间更换 Node.js 的补丁版本。

解决办法: 显式固定 @sparticuz/chromium 的版本,固定函数配置里的 Node 运行时版本,并且——很多人都会跳过这一步——每次 AWS 发布运行时公告后都重新测试,而不只是等出问题了再修。

大约 30 秒后出现 “Protocol error: Connection closed”

根本原因: Lambda 超时时间比页面真正加载和渲染所需的时间更短。

解决办法: 把超时提高到 60–120 秒,显式设置 page.setDefaultNavigationTimeout(),如果你并不需要等所有网络请求都彻底结束,再把 waitUntil: 'networkidle0' 换成 waitUntil: 'domcontentloaded'

生产环境加固:内存、冷启动和成本

大多数教程只会告诉你“加大内存”,然后就结束了。这种建议一点也不够可执行——下面我们看看随着内存增加,真正会发生什么。

内存与性能

Lambda 的 CPU 分配和内存是成比例的,这正是很多人容易忽略的细节。更多内存不只是“有更多 RAM 可用”,它还意味着更快的 CPU,而这会直接加速 Chromium 的渲染。实际经验里,运行 Puppeteer 基准测试的团队普遍发现,从 512MB 提升到 1536–2048MB 区间后,执行速度会明显改善,不过具体数值当然会强烈依赖你要渲染的页面。与其引用一张你读到这篇文章时可能已经过时的测试表,不如在你自己的目标页面上分别跑 512MB、1024MB、1536MB 和 2048MB 的测试——这十分钟就能告诉你真实的成本/性能平衡点在哪。

用 Provisioned Concurrency 降低冷启动

如果你做的是对延迟敏感的事情——比如合成监控、实时截图 API——冷启动就是敌人。Provisioned Concurrency 会保留一批已预热的执行环境,消除冷启动惩罚,但代价是你要为这些空闲容量付费。只有当延迟比绝对成本更重要时,它才真正值得。

用 arm64(Graviton)节省成本

基于 Graviton 的 Lambda 函数通常比 x86_64 方案便宜大约 20%。但需要注意的是:@sparticuz/chromium 对 arm64 的支持历史上一直比 x86_64 更有限,所以如果你准备在生产中切换到 Graviton,一定要先对你锁定的版本做明确验证。

VPC vs. 不使用 VPC

把函数放进 VPC 过去会显著增加冷启动延迟;AWS 近年来已经缩小了这个差距,但并没有完全消除。只有当你的函数确实需要访问 RDS 或 ElastiCache 这类私有资源时,才把它放进 VPC,否则就别加这个负担。

什么时候该彻底离开 Lambda

如果你的浏览器任务经常超过 15 分钟、需要超过 10GB 内存,或者要求在多次请求之间保持持久的浏览器会话,那 Lambda 其实已经在和你作对了。这种场景更适合 ECS Fargate——它就是为长时间运行、资源可配置、按秒计费的计算任务设计的。Lambda 非常适合短时、突发、可并行的浏览器任务;一旦你的工作负载开始像一个持续在线的服务,它就不是合适的工具了。

什么时候不该把 Puppeteer 部署到 Lambda

有一点值得坦白想清楚:大量搜索“Puppeteer + Lambda”教程的开发者,其实解决的是数据提取问题,而不是真正的浏览器自动化问题。如果你真正需要的是网页上的结构化数据——比如商品列表、联系方式、页面内容——那上面这些 Chromium 打包、版本锁定和 layer 管理,其实都是你不一定需要承担的额外开销。

继续用 Lambda + Puppeteer,如果你需要的是真正的浏览器控制:自定义表单交互、截图/PDF 流水线、合成监控,或者需要程序化操作 DOM 的浏览器测试。

考虑使用抓取 API,如果你的最终目标是从网页中拿到结构化 JSON,而不是自己去驱动一个浏览器会话。Thunderbit 的 Open API 可以通过一个 HTTP 请求处理 JS 渲染、反爬机制和验证码——POST /extract 配合 JSON Schema 就能返回结构化数据,POST /distill 可以得到干净的 Markdown。它还有一个 MCP 服务器(thunderbit_extractthunderbit_distill),如果你在构建 AI Agent,需要在工作流中途拉取数据,而不想自己起一个浏览器,这会很方便。

因素Lambda + Puppeteer(自己搭)抓取 API(例如 Thunderbit)
搭建时间数小时(打包、layer、调试)几分钟(API Key + HTTP 调用)
维护成本持续存在(版本锁定、运行时更新)由服务商处理
反爬处理手动(隐身插件、代理)内置支持
输出格式原始 HTML/截图,需要自己解析通过 Schema 返回结构化 JSON
最适合完整浏览器自动化、测试、自定义流程数据提取、网页抓取、内容导入

说得直接一点:如果你只是为了从商品页里提取 JSON,就花好几个小时去排查 Chromium 二进制,那说明你可能在解决错误的问题。只有当你真的需要“驱动一个浏览器”时,才值得走 DIY Lambda 这条路;如果目标是提取数据,完全有更直接的做法。如果你正在为某个具体项目权衡这两条路线,我们的 AI 网页爬虫指南 会更系统地梳理整个方案,而如果你想先体验“先抓数据、再决定”的方式,Thunderbit Chrome 扩展 也值得试试。

总结

三种部署方式,一个共同主题:锁定版本、给 Chromium 足够的内存,并根据你的真实约束来选择部署方式,而不是按你最先找到的教程来决定。想快速迭代就用 Layers,想要生产级规模就用容器镜像,想省事处理一次性任务就用 ZIP。如果你真正做的是数据提取,而不是浏览器自动化,那么也许一个专门的提取 API 能帮你把这些打包和维护的麻烦完全省掉。

常见问题

2026 年还能在 AWS Lambda 上运行 Puppeteer 吗? 可以——使用 puppeteer-core 搭配 @sparticuz/chromium,并通过 Layers、容器镜像或直接 ZIP 部署。完整的 puppeteer 包和已弃用的 chrome-aws-lambda 包在当前 Lambda 运行时上都不再可靠。

AWS Lambda 的最大包体是多少? Layers 和 ZIP 部署的解压后上限是 250MB;容器镜像部署的上限是 10GB,依据 AWS Lambda 配额

chrome-aws-lambda 还在维护吗? 没有。最初由 alixaxel 提供的 chrome-aws-lambda 包已经弃用,并且在 Node 18 及以上版本上会失效。请改用 @sparticuz/chromium——它现在才是积极维护中的标准方案。

Puppeteer 在 AWS Lambda 上需要多少内存? 1024MB 是实际可用的最低线;1536–2048MB 才会让性能真正变得舒服。低于 1024MB 时,由于 Lambda 的 CPU 分配与内存挂钩,执行速度通常会明显变慢。

如何减少 Puppeteer 在 Lambda 上的冷启动时间? 提高内存配置(这也会带来更多 CPU),如果你的场景对延迟特别敏感,可以考虑 Provisioned Concurrency,同时尽量把部署包做精简——每多一个依赖,就多一点冷启动时间。

了解更多

Ke
Ke
Thunderbit 首席技术官 | 高级数据科学家与机器学习专家 Ke Shen 拥有近十年的机器学习和数据科学经验,毕业于哥伦比亚大学,曾任 Walmart Labs 高级数据科学家。他在 Python、R、Java 和统计学方面拥有深厚且备受同行认可的专业能力,并分享如何将复杂的 AI 算法从理论落地到生产级架构的实战经验。
Topics
Puppeteer AWS Lambda无服务器浏览器自动化无头 Chromium
目录
Thunderbit · AI 网页数据代理

1 次点击 中从任何页面提取数据

受到超过 250,000+ 用户的信赖
提供免费计划
使用 AI 提取数据
轻松将数据传输到 Google Sheets、Airtable 或 Notion
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week