Crawlee 在 AWS Lambda 上运行 Playwright 爬虫:浏览器二进制、存储与部署完整指南
【免费下载链接】crawleeCrawlee—A web scraping and browser automation library for Node.js to build reliable crawlers. In JavaScript and TypeScript. Extract data for AI, LLMs, RAG, or GPTs. Download HTML, PDF, JPG, PNG, and other files from websites. Works with Puppeteer, Playwright, Cheerio, JSDOM, and raw HTTP. Both headful and headless mode. With proxy rotation.项目地址: https://gitcode.com/GitHub_Trending/cr/crawlee
导读:本文基于 Crawlee 官方部署文档,系统讲解如何在 AWS Lambda 这一无服务器环境中运行 Playwright 浏览器爬虫。核心难点在于 Lambda 的函数包无法直接携带浏览器二进制文件,且运行时文件系统只读、内存受限。读完本文,你将掌握通过
@sparticuz/chromium与 Lambda Layer 管理 Chromium 二进制的完整链路,学会为 Crawlee 配置独立的Configuration(persistStorage: false)以保证无状态运行,并了解内存、超时等关键调优参数。
为什么在 AWS Lambda 上跑浏览器爬虫比较棘手
在本地或常规服务器上,Crawlee 的 Playwright 爬虫可以直接使用 Playwright 自行下载的浏览器二进制。但 AWS Lambda 的运行环境有三个先天约束,让这件事变得"有点复杂,但并非不可能"(原文档的定位):
- 函数包体积限制:上传的代码压缩包有体积上限(直接上传约 50MB),而完整的 Chromium 二进制本身就在这个量级附近,更别说还要加上
node_modules; - 只读文件系统:Lambda 运行时的文件系统除
/tmp/目录外是只读的,浏览器二进制无法像本地那样写到任意路径; - 无状态运行环境:Lambda 会在一次执行后把容器环境保留一段时间以降低冷启动延迟,如果爬虫实例和存储状态被复用,就会出现难以排查的"状态残留"问题。
Crawlee 本身是纯 Node.js 的爬虫库,代码层面并不区分运行环境,因此上述问题全部需要由部署方案来解决:浏览器二进制走 Lambda Layer,代码存储走内存,爬虫实例每次执行重新创建。
第一步:管理浏览器二进制——@sparticuz/chromium+ Lambda Layer
用 NPM 包携带 Chromium
Crawlee 官方推荐借助现成的 NPM 包来管理浏览器二进制的安装:
- @sparticuz/chromium:一个内置brotli 压缩 Chromium 二进制的 NPM 包。当它在 Lambda 环境中运行时,会先把压缩的二进制解压到
/tmp/路径下,并返回可执行文件的路径。
这个包的设计恰好规避了 Lambda 的两个约束:压缩包在部署时占空间小,运行时解压到可写的/tmp/,再通过executablePath()把路径交给 Playwright。
只需要把这个包加入项目依赖,并把node_modules打包即可:
# 安装依赖包 npm i -S @sparticuz/chromium # 打包依赖(用于上传为 Lambda Layer) zip -r dependencies.zip ./node_modules为什么不能直接上传 Layer
AWS 对直接上传到 Lambda Layer 的内容有50MB 的体积限制,而压缩后的 Chromium 构建本身就接近这个大小,直接上传很可能超限。因此正确做法是:
- 先把
dependencies.zip上传到S3 存储桶; - 在创建 Lambda Layer 时,选择"从 S3 上传",提供该对象的链接;
- Lambda 会从 S3 拉取并挂载这个 Layer。
这样 Layer 的创建过程不再受 50MB 直接上传限制约束,同时 Layer 还可以在多个 Lambda 函数之间共享,把每个函数自身的代码包保持精简。
关于无服务器 Chromium 的补充(谨慎表述)
需要说明的是,@sparticuz/chromium是社区中广泛使用的无服务器 Chromium 方案,Crawlee 官方文档明确引用了它作为推荐路径。它的核心价值在于:把"压缩 → 上传 → 运行时解压 → 返回可执行路径"这一流程封装成开箱即用的 NPM 包。如果你希望完全自行掌控,也可以手动完成"下载 Linux 版 Chromium → brotli 压缩 → 放入 Layer → 运行时解压"的等价步骤,但维护成本会高很多。
第二步:改造 Crawlee 代码
传递独立的Configuration实例
在 Lambda 中,每个爬虫实例都要拥有自己的存储,避免与其他并发实例互相干扰。原文档给出的做法是:在构造PlaywrightCrawler时,把第二个参数从默认的全局配置替换为一个全新的Configuration实例:
// 更多信息见 https://crawlee.dev/ import { Configuration, PlaywrightCrawler } from 'crawlee'; import { router } from './routes.js'; const startUrls = ['https://crawlee.dev']; const crawler = new PlaywrightCrawler({ requestHandler: router, }, new Configuration({ persistStorage: false, })); await crawler.run(startUrls);关于persistStorage: false的底层含义,可以从仓库源码得到印证:
- 在 packages/core/src/configuration.ts 中,配置项定义为
persistStorage: field(coerceBoolean.default(true), 'CRAWLEE_PERSIST_STORAGE'),即默认值为true,且可被环境变量CRAWLEE_PERSIST_STORAGE覆盖; - 在同一文件的配置优先级说明中明确写道:
constructor options > environment variables > crawlee.json > schema defaults(构造函数选项 > 环境变量 > crawlee.json 配置文件 > 模式默认值)。因此这里通过构造函数传入false,拥有最高优先级,会稳定地关闭持久化存储,改为纯内存存储,适配 Lambda 只读文件系统; Configuration对象是**不可变(immutable)**的——从 registerAccessors 的实现可以看到,所有字段只注册了 getter,任何赋值都会抛出TypeError: Configuration is immutable,所以配置必须在构造时一次性定好。
你还可以用环境变量CRAWLEE_PERSIST_STORAGE=false达到同样的效果,这在不想改动代码、只想通过 Lambda 环境变量配置时更灵活。
注入 Chromium 可执行路径与启动参数
@sparticuz/chromium包能返回解压后的可执行文件路径;同时,AWS Lambda 的执行环境缺少 GPU 加速等硬件支持,需要通过启动参数告诉 Chromium。把这两点合并到launchContext.launchOptions中:
// 更多信息见 https://crawlee.dev/ import { Configuration, PlaywrightCrawler } from 'crawlee'; import { router } from './routes.js'; import aws_chromium from '@sparticuz/chromium'; const startUrls = ['https://crawlee.dev']; const crawler = new PlaywrightCrawler({ requestHandler: router, launchContext: { launchOptions: { executablePath: await aws_chromium.executablePath(), args: aws_chromium.args, headless: true } } }, new Configuration({ persistStorage: false, }));各参数含义:
executablePath:@sparticuz/chromium解压 Chromium 后返回的可执行文件绝对路径,Playwright 将直接使用该二进制而不是自行下载的版本;args:aws_chromium.args是一组为 AWS Lambda 环境准备的 Chromium 启动参数(例如禁用 GPU、规避无沙箱环境的报错等),原文档要求必须原样传入;headless: true:Lambda 中不存在显示器,必须使用无头模式。
从源码看,这个注入点在 Playwright 启动链路的根部:packages/playwright-crawler/src/internals/playwright-launcher.ts 中会把launchContext.launchOptions与默认executablePath合并后交给底层BrowserLauncher;其中getDefaultExecutablePath(同文件 L133-L153)的逻辑是:如果用户在launchOptions里显式指定了executablePath,则优先采用用户值,这正是我们在 Lambda 场景下注入@sparticuz/chromium路径时实际生效的分支。换句话说,这里传入的executablePath会覆盖 Crawlee 的任何默认浏览器解析逻辑。
把全部逻辑包进handler并返回数据
最后,把所有代码包进导出的handler函数——这就是 AWS Lambda 将要执行的入口:
import { Configuration, PlaywrightCrawler } from 'crawlee'; import { router } from './routes.js'; import aws_chromium from '@sparticuz/chromium'; const startUrls = ['https://crawlee.dev']; export const handler = async (event, context) => { const crawler = new PlaywrightCrawler({ requestHandler: router, launchContext: { launchOptions: { executablePath: await aws_chromium.executablePath(), args: aws_chromium.args, headless: true } } }, new Configuration({ persistStorage: false, })); await crawler.run(startUrls); return { statusCode: 200, body: await crawler.getData(), }; }crawler.getData()会把本次爬取写入默认 Dataset 的数据读取出来,放进响应体的body中,这样外部调用方(API Gateway、EventBridge 或直接调用)就能拿到结构化抓取结果。
无状态原则(重要):AWS 在首次执行后会将环境保留一段时间以减少冷启动,因此后续每次 Lambda 调用都可能复用上一次的容器。如果爬虫实例被复用,会访问到已运行过的实例,导致脏数据与难以定位的问题。务必保证每次调用都新建 crawler 实例、每次调用都新建
Configuration——即让 Lambda 保持无状态。这也是原文档在无浏览器场景(如 Cheerio on AWS Lambda)中反复强调的同一原则:TLDR: Keep your Lambda stateless.
路由与请求处理器的组织方式
示例代码中的router来自项目模板里的routes.js,通常是一个由createRouter创建的请求分发器。例如:
// src/routes.js import { createPlaywrightRouter } from 'crawlee'; export const router = createPlaywrightRouter(); router.addDefaultHandler(async ({ page, enqueueLinks, log }) => { log.info(`抓取页面: ${page.url()}`); // 在这里用 page.locator() 等 API 提取数据 });requestHandler: router会把所有抓取到的请求交给这个分发器处理。你可以参考仓库中的 PlaywrightCrawler 基础示例 和 Crawl multiple URLs(Playwright) 了解更完整的 handler 与路由写法。
第三步:部署到 AWS Lambda
打包代码
代码侧只打包业务代码(不含node_modules,因为依赖已经放进了 Layer):
zip -r package.zip .然后在 AWS 控制台:
- 创建 Lambda 函数,上传
package.zip作为代码源; - 在Layers配置中附加之前创建的
dependenciesLayer(包含node_modules与@sparticuz/chromium的 Layer); - 在Runtime settings中设置 handler 名称。handler 是
src/main.js中导出的handler函数,因此填入src/main.handler(用/表示目录层级,用.表示命名导出); - 点击Test发送一个测试事件。事件内容此时无关紧要——如果你想进一步参数化爬虫(比如把起始 URL 放进事件),可以解析 AWS 作为第一个参数传入 handler 的
event对象。
内存与超时设置(原文档重点提示)
使用完整尺寸的浏览器意味着 Lambda 配置必须调整,原文档给出两条硬性建议:
- 内存设置为 1024 MB 或更多。浏览器进程的内存占用远超纯 HTTP 爬虫,内存不足会直接导致 Lambda 被杀进程;
- 更新 Lambda 超时时间。超时值取决于你的爬虫运行时长:先在本地运行爬虫测量实际执行时间,再据此设置超时,留出合理余量。
除此之外,在 AWS Lambda 控制台的Configuration页签里,你还可以调整ephemeral storage(临时存储)大小——/tmp/目录就来自这块空间,而@sparticuz/chromium要把压缩的浏览器解压到这里,因此临时存储过小同样会导致运行失败。原文档提醒,内存大小会显著影响 Lambda 的执行速度(更多内存通常对应更强的 CPU 配额),需要在实际成本与性能之间权衡。
完整工作流回顾
把整个方案串起来,一次完整的部署流程是:
npm i -S @sparticuz/chromium安装浏览器二进制包;zip -r dependencies.zip ./node_modules打包依赖;- 将
dependencies.zip上传到 S3,并以 S3 对象为来源创建 Lambda Layer(规避 50MB 直接上传限制); - 改造代码:传入独立
Configuration({ persistStorage: false })→ 注入executablePath与aws_chromium.args→ 包进handler并返回crawler.getData(); zip -r package.zip .打包业务代码并上传为 Lambda 函数代码;- 附加依赖 Layer,设置 handler 为
src/main.handler; - 在 Configuration 中把内存设为 ≥1024MB、按需调整超时与临时存储;
- Test 触发,验证返回的
statusCode: 200与抓取数据。
如果只是做纯 HTTP 抓取而不需要浏览器渲染,可以改用 Cheerio 方案,部署会更轻量(无需 Layer、无需浏览器二进制),参见 Cheerio on AWS Lambda;若仍想用浏览器但需要更精细的资源控制,也可以参考 GCP 上的浏览器部署文档 对比不同云平台的差异。
【免费下载链接】crawleeCrawlee—A web scraping and browser automation library for Node.js to build reliable crawlers. In JavaScript and TypeScript. Extract data for AI, LLMs, RAG, or GPTs. Download HTML, PDF, JPG, PNG, and other files from websites. Works with Puppeteer, Playwright, Cheerio, JSDOM, and raw HTTP. Both headful and headless mode. With proxy rotation.项目地址: https://gitcode.com/GitHub_Trending/cr/crawlee
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考