AI 参与说明(Agent:Codex):本文由 Codex 根据 Crawlee 官方文档、固定版本源码与发布记录辅助调研、撰写和校验,资料整理于 2026-10-10。选型与落地顺序是本文的工程建议。运行记录:模型
gpt-6.1-sol,reasoning effortultra,执行入口 Codex Desktop,提供方openai,CLI 版本0.162.0-alpha.17.2(不代表桌面 App 版本)。
Crawlee 适合把网页采集做成持续运行的程序:发现链接、控制并发、处理失败,再保存提取结果。若任务只有几个固定 URL,一段 HTTP 请求与解析代码往往已经足够;当 URL 会不断增加、页面需要执行 JavaScript、失败需要重试时,Crawlee 提供的运行机制更有价值。
先认识本文的术语
| 英文术语 | 中文名称 | 简要解释 |
|---|---|---|
| Crawlee | 保留原名(项目名称) | 面向网页采集与浏览器自动化的开源库 |
| CheerioCrawler | 保留原名(类名) | 下载 HTML,再用选择器提取内容,不执行页面脚本 |
| PlaywrightCrawler | 保留原名(类名) | 用真实浏览器加载页面,可等待动态内容和操作页面 |
| RequestQueue | 请求队列 | 保存待处理及已处理请求,控制哪些页面还需要访问 |
| Dataset | 数据集 | 按条追加结构化结果,例如页面 URL 与标题 |
| AutoscaledPool | 自动伸缩池 | 根据本机资源状态调整并发任务数量 |
| SessionPool | 会话池 | 管理可复用会话及其 Cookie、使用状态 |
| ProxyConfiguration | 代理配置 | 为请求选择代理地址,并支持与会话关联 |
版本基线与项目定位
截至整理日,官方 Releases 的稳定版是 v3.18.2,发布于 2026-09-29;v4.0.0-rc.1,发布于 2026-10-06,仍标记为 Pre-release。本文示例固定使用 v3.18.2。v3.18.2 · v4.0.0-rc.1
The v3.18.2 package declares Node.js 16 or newer; the current master README requires Node.js 22.13 or newer. 这是不同版本线的要求,不能直接把主分支说明套到稳定版。包声明的最低版本,也不代表最新的传递依赖和浏览器依赖仍支持该版本。新项目应选择仍在维护的 Node.js 版本,固定依赖后实际验证。v3.18.2 package.json · master README
Crawlee is an Apache-2.0 library that can run independently of the Apify platform. 本地运行不需要 Apify 账号;也可以选择 Apify 托管运行和存储。JavaScript / TypeScript 使用这个仓库;Crawlee for Python 是另一个官方实现,版本与 API 要分别核对。许可证 · 运行到云端
源码采用模块化组织:crawlee 是聚合入口,重导出 @crawlee/core、HTTP、浏览器、解析器与存储等能力。阅读源码时,可以从 BasicCrawler 的请求生命周期开始,再看具体的 HTTP 或浏览器实现,最后核对 RequestQueue 和 StorageClient。这样的阅读顺序有助于区分通用运行机制与某种抓取方式的细节。v3.18.2 聚合入口 · BrowserCrawler
选哪种抓取方式
先检查目标数据是否已经出现在服务器返回的 HTML 或公开接口中,再决定是否启动浏览器。使用 React 等框架不代表每个页面都必须用浏览器采集:服务端渲染的内容依然可以从 HTML 提取。
| 场景 | 优先评估 | 需要自己处理的部分 |
|---|---|---|
| HTML 已包含正文、标题、商品字段 | CheerioCrawler | 选择器、字段清洗与完整性校验 |
| 页面脚本执行后才出现数据 | PlaywrightCrawler | 等待目标元素、提取渲染后的内容 |
| 点击、滚动、截图或多步交互 | PlaywrightCrawler | 操作顺序、完成条件与失败处理 |
| 已有 Puppeteer 自动化代码 | PuppeteerCrawler | 接入队列和处理器,复用既有浏览器逻辑 |
| JSON 接口或原始 HTTP 内容 | HttpCrawler | 请求参数、响应类型与业务解析 |
CheerioCrawler does not execute client-side JavaScript. PlaywrightCrawler supplies a browser page to the request handler. 从前者迁移到后者时,队列与限速配置可沿用,但使用 $ 的提取代码要改为 page 操作,并明确等待条件,不能只换一个类名。Playwright 需要另行安装,运行环境也需要匹配的浏览器;安装 Crawlee 本身不等于浏览器环境已经准备好。Quick Start · JavaScript rendering · HttpCrawler 示例
对于文档采集、商品目录、页面变化检查和检索语料准备,这种组合很实用。不过,正文识别、去噪、变更比较、分块、向量化和索引更新仍要自己实现。Crawlee 的采集能力不能直接等同于一套完整的检索系统。
一次请求如何运行
一次请求大致会经过以下流程。图中的处理器按下文示例先发现链接、再保存结果;真实任务可按需要组织这些动作。
flowchart TD
Seed[起始 URL] --> Queue[RequestQueue]
Queue --> Schedule[AutoscaledPool]
Schedule --> Fetch[加载页面]
Fetch --> Handler[requestHandler]
Handler --> Links[enqueueLinks]
Links --> Queue
Links --> Data[保存结果]
Data --> Done[确认请求完成]
Fetch -->|请求失败| Retry{允许重试?}
Handler -->|抛出错误| Retry
Retry -->|是| Queue
Retry -->|否| Failed[failedRequestHandler]
AutoscaledPool 的伸缩依据是资源状态。目标网站能承受多少流量是另一个问题,需要显式设置 maxConcurrency 和 maxRequestsPerMinute;前者限制同时工作的任务数量,后者控制任务启动速率。它们不是整台浏览器所有子资源请求的硬上限。Scaling our crawlers · AutoscaledPool
固定版本源码中,处理器成功结束后才确认请求已处理;发生可重试错误时,请求会被重新交给队列。由此可以推断:结果写入后、确认请求前发生中断,可能留下已经写入的结果,并在后续处理时再次写入。写入结果与确认请求不构成同一个事务。v3.18.2 BasicCrawler
一个可以运行的最小示例
目标是抓取 Crawlee 官方文档中的少量页面,只保存 URL 和标题。使用 JavaScript ESM;把下面代码保存为 crawl.mjs,在独立的练习目录安装和运行,避免与已有采集数据共用默认存储。
npm install crawlee@3.18.2
node crawl.mjs
import { writeFile } from 'node:fs/promises';
import { CheerioCrawler, Dataset } from 'crawlee';
const crawler = new CheerioCrawler({
maxConcurrency: 1,
maxRequestsPerMinute: 30,
maxRequestsPerCrawl: 5,
maxRequestRetries: 1,
respectRobotsTxtFile: true,
async requestHandler({ request, $, enqueueLinks, pushData }) {
const title = $('title').text().trim();
if (!title) throw new Error('Page title is missing');
// 先完成会失败的链接发现,再追加结果,减少重试重复写入的机会。
await enqueueLinks({
strategy: 'same-hostname',
globs: ['https://crawlee.dev/js/docs/**'],
});
await pushData({
url: request.loadedUrl ?? request.url,
title,
});
},
failedRequestHandler({ request, log }) {
log.error(`Failed: ${request.url}`);
},
});
await crawler.run(['https://crawlee.dev/js/docs/quick-start']);
const { items } = await Dataset.getData();
await writeFile('pages.json', JSON.stringify(items, null, 2));
console.log(`Saved ${items.length} pages to pages.json`);
same-hostname 与 globs 限制后续入队链接的来源和路径;起始 URL 也要自行审核,重定向后的目标需要按任务要求另行检查。respectRobotsTxtFile 显式开启 robots.txt 检查,运行时还可能发出相关辅助请求。maxRequestsPerCrawl 约束处理页数,并发时可能略超出配置值,不能当作全部网络访问次数的预算。enqueueLinks · BasicCrawlerOptions
预期产物是 pages.json 和 storage/ 中的结果及队列状态。通过标准是:至少得到一条记录,标题非空,URL 位于预期文档范围,日志中没有最终失败。若要采集正文,还需定义正文选择器与清洗规则;这个示例只提取标题。Dataset.getData
本次实际验证使用 Node.js v26.0.0 与 Crawlee v3.18.2,成功抓取 5 个官方文档页,最终失败 0 次,并生成 pages.json。5 条记录的标题均非空,URL 均位于指定文档范围。验证范围仅为标题提取与有限范围抓取。
五个容易误判的工程边界
RequestQueue 去重不会替 Dataset 去重
RequestQueue deduplicates requests by uniqueKey; Dataset appends records. 默认的 uniqueKey 从 URL 生成。相同正文出现在不同 URL、同一页面改用另一个 key,或处理器重试时再次写结果,都需要应用自己判断是否重复。RequestQueue · Dataset
正式落库时,先确定数据身份。例如商品采集可以使用站点与商品 ID,文档采集可以使用规范 URL 与内容版本。数据库写入采用唯一约束与 upsert,或在 Dataset 导出后去重。把 pushData() 放到处理器末尾可以减少后续错误造成的重复追加,但仍不能提供 exactly-once 保证;Dataset 的批量追加也没有事务安全保证。Dataset.pushData
本次还用 BasicCrawler 做了一个无网络探针:重复加入同一个 URL,处理器第一次保存结果后主动抛错,第二次正常结束。最终只有一个请求身份,但处理器执行两次,Dataset 留下两条结果。这验证了请求去重与输出幂等的区别。
默认落盘不代表重跑自动续抓
在 v3.18.2 中,persistStorage 与 purgeOnStart 默认都为 true:运行时保存数据,下一次启动又会清理默认存储。需要续跑时,要保留同一存储目录及队列,并关闭清理。例如在相同练习目录执行:
CRAWLEE_PURGE_ON_START=false node crawl.mjs
这会保留已处理请求的状态,但不保证从中断时的那一步继续执行。要定期重新检查页面,应明确每轮任务及其存储范围;不能指望保留已处理状态后,同一 URL 仍会每次重新访问。ConfigurationOptions · 固定版本配置源码
重试配置不是绝对访问次数
maxRequestRetries 管理一般失败重试;maxSessionRotations 另行控制会话轮换,后者不计入前者。浏览器还会加载页面子资源。因此,不能把“重试一次”写成“整个任务对该网站最多发两次网络请求”。持续失败应检查具体原因,并记录最终失败 URL。BasicCrawlerOptions
代理、Cookie 与浏览器指纹不是成功保证
ProxyConfiguration 负责接入和选择已有代理;SessionPool 管理会话状态。代理来源、可用性、费用及目标站的访问条件仍由使用者处理。官方也说明,内置的反封锁机制在某些保护下并不足够,不能把宣传文案理解成保证通过任意挑战页。Proxy Management · Session Management · Avoid getting blocked
本机自动伸缩不等于集群自动运维
Crawlee 提供队列、存储接口和并行运行方式,但多个进程或多台机器如何共享存储、分配任务、避免清理冲突、收集日志与限制总流量,需要另行组织。生产浏览器任务还需要浏览器二进制及系统依赖;官方 Docker 文档提供相关运行基础。Parallel Scraping · Running in Docker
是否值得采用,以及怎样试用
我的判断是:如果主要用 JavaScript / TypeScript,且采集会涉及持续扩展的 URL、动态页面或失败恢复,Crawlee 值得进入候选清单。 它让提取代码与运行机制可以分别维护。若已经有稳定的固定接口、任务规模很小,先用简单脚本通常更省事;若需要现成的正文清洗和检索服务,还应评估采集之后的处理成本。
试用时按下面顺序验证,而不是直接放开全站抓取:
- 选一组有代表性的页面,确定必须返回的字段与可访问范围。
- 先用 CheerioCrawler 抓少量 HTML,检查字段缺失、错误内容与重复记录。
- 只有原始响应确实缺少目标数据时,再用 PlaywrightCrawler 验证动态加载;为操作定义明确的等待条件。
- 在测试环境加入请求失败和处理中断,检查重试、续跑、最终失败记录与结果幂等。
- 增加并发前,测量每页耗时、内存、成功率及结果有效率,再确定限速、存储和部署方案。
验收关注“字段是否正确、失败是否可解释、重跑是否重复写入”,不能只看进程有没有跑完。数据采集链路:数据工程栏目。