Crawlee 的好处在于,它的抓取编排同时兼容 HTTP 解析和浏览器执行。所以,同一个 URL,会因为你选的爬虫类型和页面就绪条件不同,最后拿到完全不一样的结果。
在公开的 Quotes to Scrape JS 页面上,CheerioCrawler 就算等了 .quote,还是只找到 0 条目标 quote,而 PlaywrightCrawler 却找到了 10 条。两者的抓取生命周期看起来很像,但这绝不是把类名改一处就能搞定的事:Cheerio 处理函数用的是 $,Playwright 处理函数则依赖 page、显式等待,以及浏览器端提取。
Crawlee 到底是什么
Crawlee(apify/crawlee 项目,版本 3.17.0)是一个面向 Node.js 和 TypeScript 的网页抓取与浏览器自动化库。它既支持基于 Cheerio 或 JSDOM 的 HTTP 抓取,也支持基于 Playwright 或 Puppeteer 的浏览器抓取。该项目采用 Apache-2.0 许可证;如果你要分发相关内容,请务必查看其声明和署名要求。
真正要建立的心智模型,是把“获取页面”和“读取页面”分开看。一条路径会下载原始 HTML,不会执行 JavaScript。另一条路径会启动 Chromium,能执行页面脚本,但还是需要合适的就绪条件,而且可能漏掉需要交互才能出现、懒加载、shadow DOM、接口失败或被反爬拦截的内容。Crawlee 在这些路径上提供的是对应的生命周期概念,而不是可以随便互换的 DOM 原语。
在你写下第一个选择器之前,这一点就很值得先弄清楚,因为到底选哪种引擎,决定了你的爬虫是在某个网站上拿到数据,还是直接空手而归。
核心特性:两种引擎,一套 API

CheerioCrawler 会抓取 HTML 并用 Cheerio 解析;PlaywrightCrawler 则会驱动 Chromium,还能截图。两者都使用 requestHandler,都有 run(),并且共享队列、链接发现等爬取概念。它们的处理上下文不同:这次测试里的 Cheerio 路径通过 $ 提取内容,而浏览器路径则使用 page、waitForSelector 和 $$eval。队列和生命周期相关的基础设施可以保持熟悉,但提取代码可能需要适配,甚至重写。
在这些表层之下,Crawlee 还提供了真实爬虫需要的底层能力。RequestQueue 负责管理待访问 URL 前沿、去重并跟踪已完成项。enqueueLinks 会发现并加入新的 URL(可按选择器和同域名过滤),让爬虫能够自动扩展。Dataset 则负责收集你的抓取结果,方便导出。默认情况下,Crawlee 会把这些内容持久化到本地 storage/ 目录里——这在需要断点续跑时很方便,但第一次在项目里看到一个你没要求的 storage/ 文件夹时,也会有点烦(我的测试框架把它重定向到了临时目录,并关闭了持久化,以保持测试环境干净)。
这些组件单独看都不算惊艳。重点在于,它们在两种引擎之间是共用的,所以不管你是通过 HTTP 还是浏览器去抓,队列、链接发现和数据集的行为都一样。你只需要学一套 API,却能拿到两种抓取策略。
安装:浏览器需要单独装
在测试环境里,安装完包之后并不会自带 Chromium 可执行文件。

对我来说,npm install crawlee playwright 安装得很顺,85 个包、0 个漏洞,没出什么幺蛾子。只要停在这一步并运行 CheerioCrawler,一切都能正常工作,因为 HTTP 抓取不需要浏览器。
但在这个环境里,还得额外执行 npx playwright install chromium 安装 Chromium;否则 PlaywrightCrawler 根本起不来。观察到的浏览器体积大约是 82 MiB,但原始记录里没有保留这个数字到底是传输大小还是磁盘占用。它只是机器环境下的一项安装观察,不是固定的产品属性。文档路径和包行为也可能变化,所以本文并不声称这个缺失是普遍存在或永久未记录的。
因此,测试环境可以按两步准备:先装 Node 依赖,再装 Playwright 路径所需的浏览器。实际部署时,请重新查看当前版本和平台对应的 Crawlee 与 Playwright 安装说明。
实战:同一页面,两种完全不同的答案

核心测试把同一个 JavaScript 渲染示例页面交给两种爬虫处理。URL 和目标字段是共享的,但提取原语并不相同。
在本地示例中,CheerioCrawler 返回了 0 个目标卡片,因为它们根本不在原始 HTML 里。PlaywrightCrawler 则先等待 #dynamic-products article.product-card,然后顺利返回全部 8 个预期卡片,并截取了截图。这个结果只能说明:在该等待条件之后,示例中指定字段的完整性是成立的;它并不意味着浏览器能看到所有可能的页面状态。原始文件和 截图 都在基准测试仓库里。

在公开的 Quotes to Scrape JS 页面 上,CheerioCrawler 找到了 0 条目标 quote,而 PlaywrightCrawler 在等待 .quote 后提取到了 10 条。这再次说明,在公开目标上,HTTP 和浏览器之间的边界是很明确的;同时,两边用到的爬虫类、处理上下文、等待条件和提取原语也都不同。
更有价值的结论其实更窄一点:先在 HTTP 路径上验证所需字段;如果原始响应里没有这些字段,再升级到浏览器爬虫。但浏览器处理函数也必须等待与这些字段相关的条件。
在受控的静态目录和文章示例上,HTTP 路径返回了所有预期记录,能从直接 JSON 响应里解出全部 8 个预期项,访问了一个包含 11 个页面的有限内部链接图,并把一个 500 错误转交给 failedRequestHandler。这些都是独立的能力检查,不是一个统一的准确率分数。对公开的 Books to Scrape 页面,所配置的选择器也能抓到 20 个商品,算是一次烟雾测试。
| 测试 | 引擎 | 结果 |
|---|---|---|
| 静态提取:目录 + 分页 | CheerioCrawler | 12/12 个预期商品 |
| 文章提取 | CheerioCrawler | 标题 + 3/3 段正文 |
| 传输:直接 JSON 响应 | CheerioCrawler | 8/8 个预期商品 |
| 遍历:内部链接图 | CheerioCrawler | 11 个页面,深度 {0:1, 1:3, 2:7} |
| 故障路由:HTTP 500 | CheerioCrawler | 状态码进入失败处理器 |
| 渲染:本地示例 | CheerioCrawler | 原始 HTML 中 0 个目标卡片 |
| 渲染:本地示例 | PlaywrightCrawler | 在目标选择器等待后获得 8/8 |
| 渲染:Quotes JS | CheerioCrawler | 原始 HTML 中 0 条目标 quote |
| 渲染:Quotes JS | PlaywrightCrawler | 在目标选择器等待后获得 10 条 |
完整的耗时和每项测试数据都在 results/crawlee-test-summary.json 里。
接下来也得坦白说一下局限,因为单机单轮测试总有边界,我不想假装这些不存在。这些只是耗时记录,不是正式 benchmark——只有一台机器、每项只跑一次,所以浏览器路径更高的单页成本应理解为“明显慢于不到一秒的 Cheerio 运行”,而不是一个正式发布的数值。还有一长串我这次没测的内容:代理轮换、会话池、数百到数千页面的大规模运行、RequestQueue 持久化与崩溃后恢复、Puppeteer 引擎,以及 Dataset / KeyValueStore 的导出易用性(这里的导出是我手写的)。我能确认的是两引擎方案和示例级准确性;但我不能替你证明它的规模能力或反封锁表现,所以我也不会这么说。
哪些是共享的,哪些必须改变

共同的部分是爬取编排。两种爬虫类都接受 requestHandler 并提供 run()。队列、请求元数据、链接发现、失败钩子和存储概念,都可以围绕任一执行路径进行统一组织。这能明显降低团队在某个目标突然需要浏览器时,重新学习基础设施的成本。
但页面访问层并不共用。CheerioCrawler 的处理函数接收的是面向 Cheerio 的访问方式,比如 $,可以在没有浏览器的情况下处理响应体。本次测试里的 PlaywrightCrawler 处理函数接收的是 page;它会等待选择器,并在浏览器 DOM 上执行。即使两边最后输出相同的记录结构,它们走到这一步的 API 也不一样。你当然可以写一个可复用的适配器来掩盖一部分差异,但本次测试并没有实现或展示这种适配器。
这一区别在做工时估算时尤其重要。更换爬虫类,可能会保留队列、数据集和 URL 策略,但选择器、就绪检查、截图、交互步骤和错误处理还是可能变化。所以,本文把“共享的爬取基础设施”视为已验证的收益,而把“一行代码迁移”视为没有依据的承诺。
一个实用的引擎选择流程
当返回的 HTML 或直接的 JSON 响应里已经包含所需字段时,先走 HTTP 路径。定义一个完整性契约——比如必需键、最少条目数,或一个目标选择器——并在不满足时明确失败。空数组并不等于页面没有数据;在这里的两个 JavaScript 场景中,它只是说明所选表示里没有目标元素。
| 目标条件 | 先用 | 何时升级 |
|---|---|---|
| 所需字段已在返回的 HTML 中 | CheerioCrawler | 缺少所需选择器或字段 |
| 可复现的 JSON 响应中已包含数据 | CheerioCrawler | 请求依赖浏览器专属状态 |
| 页面在执行后插入目标元素 | PlaywrightCrawler | 不适用;请定义目标专属的就绪检查 |
| 目标情况未知 | 先用 HTTP,并做完整性校验 | 校验失败并返回类型化的“表示不完整”结果 |
当确实需要执行页面脚本时,再把这个类型化失败上升级到浏览器处理器。在本次测试中,本地页面等待的是 #dynamic-products article.product-card,而公开 quotes 页面等待的是 .quote。这些条件本身就是提取契约的一部分。通用的 load 事件并不能证明应用数据已经到达,而本次测试也不支持一个放之四海而皆准的等待规则。
升级到浏览器后,即使 DOM 原语不同,也要保持输出结构稳定。记录是哪种引擎产出了结果、哪个就绪条件通过了,以及必需字段校验是否成功。这样,HTTP 到浏览器的回退就是可观测的,而不会悄悄把缺失字段当成有效记录接受下来。
最后,把浏览器安装和运行成本视为部署输入。大约 82 MiB 的观察结果只适合作为本地量级参考;你应在自己的环境中测量浏览器版本、平台、缓存行为以及镜像体积影响。代理轮换、会话、持久化、崩溃恢复和持续并发,在这个示例能用于生产级决策之前,都还需要单独测试。
优点与缺点
优点:
- HTTP 爬虫和浏览器爬虫共享生命周期概念,同时保留各自的提取上下文。
- 在静态目录、文章和 JSON API 上,HTTP 提取召回率达到 1.0。
- 两种引擎共享基础设施:
RequestQueue、带深度控制的enqueueLinks、Dataset。 - 浏览器路径执行了示例脚本,并在两个 JS 渲染测试中拿回了所有预期目标项。
- 失败处理干净——HTTP 500 能正常进入失败处理链,不会直接把程序搞崩。
- Apache-2.0 许可证;下游使用者应查看其声明和署名要求。
缺点:
- 在测试环境中,浏览器引擎需要单独安装 Chromium;如果没有它,
PlaywrightCrawler就无法启动。 - HTTP 路径看不到原始 HTML 中不存在的目标元素;如果没有完整性校验,这种情况很容易被误当成合法的空结果。
- 浏览器路径除了多一个浏览器二进制外,这次运行中的单页本地成本也更高;体积和耗时会随版本和平台变化。
- 默认运行会在磁盘上留下
storage/目录。 - 只支持 Node/TypeScript——如果你的技术栈是 Python,它就帮不上忙。
适合谁,不适合谁
如果你的团队使用 Node 或 TypeScript,又希望在共享队列和生命周期概念下同时支持 HTTP 和浏览器抓取,Crawlee 很合适。一个实用做法是:先尝试 HTTP 爬虫,校验必需字段,再把类型化的完整性失败升级给浏览器处理器,并配上目标专属的就绪条件。即便队列和链接发现的基础设施是共享的,处理函数里的 DOM 访问代码仍然是引擎专属的。
如果你是 Python 团队(Crawlee 是 Node/TS;虽然有单独的 Python 移植版,但这次测试的是 Node 库),如果你的目标全是静态页面,更愿意用更轻量的单用途 HTTP 抓取器,或者你需要已经在大规模场景中验证过的能力——比如代理轮换、会话池、崩溃后恢复——那就该重新设定预期,或者考虑别的方案。本次实测没有覆盖这些内容。还有,如果你打算用 PlaywrightCrawler,先把 Chromium 装好,不然它根本跑不起来。
替代方案,以及 Thunderbit 的位置
Crawlee 是你自己运行和维护的开源软件。它没有按调用收费,但浏览器算力、带宽、代理、存储、可观测性和工程投入都是真实的运营成本。爬虫选型、浏览器二进制、存储状态和就绪逻辑,都由你自己负责。
相关评测:scrapy-playwright review。
托管型提取服务会把数据采集和 schema 设计责任转移给服务商。我们确实在做 Thunderbit,但这次没有拿它和这些示例做对比,所以本文不支持任何质量、延迟、功能对等或成本比较。真正要考虑的是:你的团队更想要 Crawlee 这种进程内控制,还是按调用划分的服务边界。
相关基准评测:完整开源爬虫对比、Playwright 与 Puppeteer 在同一页面上的对比、以及 Scrapy 的无浏览器请求重放评测。
结论
对于想在 HTTP 和浏览器执行之间共享爬取编排的 Node 或 TypeScript 团队来说,Crawlee 是一个很强的候选方案。本次测试中的处理函数并不能互换:迁移到 Playwright 需要 page、目标选择器等待,以及浏览器端提取。代理、会话、持久化、断点续跑和大规模行为,仍然是开放问题。
试用 Thunderbit 进行网页数据提取 Get Started Free
常见问题
Crawlee 的两个爬虫到底有什么区别?
CheerioCrawler 通过 HTTP 获取 HTML,不执行 JavaScript。PlaywrightCrawler 则驱动 Chromium,能执行页面脚本并截图,但单页本地成本更高。两者共享生命周期概念,但处理器上下文并不相同:本次测试里,HTTP 路径使用 $,而 Playwright 路径使用 page、目标选择器等待以及浏览器端评估。
为什么我安装了 Crawlee 之后,PlaywrightCrawler 还是跑不起来?
在测试环境中,包安装并不会自动提供浏览器可执行文件。使用 npx playwright install chromium 安装 Chromium 后,启动失败就解决了。观察到的体积大约是 82 MiB,但原始测量没有保留这到底是传输大小还是磁盘大小,所以你最好在自己的平台和构建环境中重新测量。
CheerioCrawler 能抓取 JavaScript 渲染页面吗?
它不能执行页面的 JavaScript。不过,如果客户端使用了一个可访问的 JSON 接口,它仍然可以直接请求那个接口,就像直接响应示例所展示的那样。当所需数据只有在浏览器执行后才出现时,就应该使用浏览器爬虫,并配套与这些字段绑定的就绪条件。
Crawlee 在普通静态提取场景下准确吗?
在受控示例中,这些处理函数拿到了 12/12 个预期目录商品、3/3 个预期文章段落,以及 8/8 个预期直接 JSON 项。这些都是示例完整性检查,不是对所有未测试网站的通用准确率评分。
Crawlee 可以免费用于商业用途吗?
它采用 Apache-2.0 许可证发布。请在 仓库 中确认当前许可证,并检查你的分发是否需要遵守声明和署名要求。
在投入生产之前,还应测试这个示例没有覆盖的部分:代表性页面上的持续并发、代理和会话行为、中断后的持久化队列恢复、浏览器进程清理,以及故障场景下的数据集导出。把最终确认的浏览器版本和安装路径一并保存下来。两种爬虫类确实减少了编排差异,但并不会消除对引擎专属就绪检查、资源预算和运行故障处理的需要。


