引擎系统:能力打分与回退列表
30 秒导读: 一个网页可能是普通 HTML、可能是重 JS 的 SPA、可能是 PDF、可能是被反爬墙挡住的站点。 Firecrawl 把「用什么后端去抓」抽象成一个统一的
Engine,再根据「这次请求要哪些能力」给每个引擎 打分排序,算出一条从最优到兜底的回退列表。上层抓取循环只管顺着列表挨个试——这就是 Firecrawl 号称高覆盖率的工程底座。本章只讲引擎怎么注册、怎么挑;抓取主循环怎么执行这条列表 见 01-scrape-pipeline。
1. 这是什么(零基础也能懂)
一句话定义: 引擎系统 = 一套「抓网页的后端」的统一抽象 + 一个「按需选后端」的挑选算法。
它解决什么问题? 网页千奇百怪,没有一种抓法能通吃:
| 网页/场景 | 单一抓法的困境 |
|---|---|
| 静态博客 | 用重型浏览器抓也行,但慢、贵、浪费 |
| React/Vue 单页应用 | 纯 HTTP fetch 抓回来是空壳,必须跑浏览器渲染 |
| PDF / Word 文档 | 根本不是 HTML,要专门的解 析器 |
| 有反爬墙的站点 | 普通请求被 403,得换隐身代理(stealth proxy) |
| 刚抓过的页面 | 再抓一遍是浪费,应该走缓存(index) |
Firecrawl 的答案: 不赌单一方案,而是养一群专才引擎,每个引擎声明「我支持哪些能力、我有多好用」, 然后按每次请求的真实需求动态排出一条回退列表。抓失败了就自动降到下一个,直到成功或列表耗尽。
一句话直觉/类比: 像医院分诊台。病人(URL)来了,先看症状(要截图?要 PDF?被墙了?), 再从一排科室(引擎)里挑出最对症的排在前、能兜底的排在后,让病人依次去看,第一个治好就走。
用起来什么样(引擎系统对外的样子): 你不会直接调某个引擎。你给 /v1/scrape 传选项
(要不要 screenshot、proxy: "stealth"、URL 是不是 .pdf……),引擎系统把这些翻译成能力需求,
自动决定「先 index 查缓存 → 没有就 fire-engine 浏览器渲染 → 再不行 fetch 兜底」这样一条链。
本章要拆开的就是这个「自动决定」的黑盒。核心全在一个文件:
apps/api/src/scraper/scrapeURL/engines/index.ts。
2. 顶层全景(它大概怎么转)
这一节先给你一张「大盘」:引擎系统由四张并行的登记表 + 一个挑选算法 + 一个分发器组成。
2.1 五个部件,各管一摊
| 部件 | 干什么 | 在 engines/index.ts |
|---|---|---|
Engine 联合类型 | 枚举所有引擎的字符串标识(如 "fetch"、"fire-engine;chrome-cdp;stealth") | :42-57 |
engines[] 清单 | 按环境变量条件启用的可用引擎数组 | :74-92 |
| 四张登记表 | 每个引擎的 handler / 超时估算 / 能力矩阵 / 质量分 | :177-548 |
buildFallbackList() | 挑选算法:输入需求,输出排好序的回退列表 | :577-785 |
scrapeURLWithEngine() | 分发器:拿引擎名找到 handler 真正去抓 | :787-812 |
「四张登记表」是关键设计:同一个 Engine 名字,分别在四张以引擎名为键的对象里登记它的
handler、超时、能力、质量。类型系统用 { [E in Engine]: ... } 强制每张表都覆盖每个引擎,
漏一个都编译不过。
2.2 主线走一遍(高层,不进代码)
从「一次抓取请求」到「拿到一条引擎列表」,数据这样流:
一次 scrape 请求(URL + options)
│
[01 抓取循环] 先把 options 翻译成 featureFlags(要哪些能力)
│ buildFeatureFlags() scrapeURL/index.ts:162
▼
buildFallbackList(meta) ← 本章主角
│
┌───────────────────────┼───────────────────────────────┐
│ ① 特殊短路 │ ② 组装候选引擎 │ ③ 打分排序
│ data-layer 命中? │ 按环境变量过的 engines[] │ 能力门槛过滤
│ → 直接只返回它 │ + lockdown/index-only 收窄 │ + 质量分兜底排序
│ │ + wikipedia/x-twitter 域名特判│
└───────────────────────┴───────────────────────────────┘
│
▼
[{ engine, unsupportedFeatures }, ...] ← 从最优到兜底
│
[01 抓取循环] 顺着列表:scrapeURLWithEngine(meta, engine)
│ engines/index.ts:787
▼
engineHandlers[engine](meta) → 真正去抓
怎么读这张图: 左到右是「先粗筛、再排序」。
buildFallbackList只算出列表;真正 挨个执行、失败降级的循环在 01。本章聚焦中间那个方框。
记住两个核心概念,后面反复用到:
- FeatureFlag(能力标志): 「这次请求需要的能力」,如
screenshot、pdf、stealthProxy。 由上层从 options 推导(:94-111是全集)。 - Engine(引擎): 「能满足能力的后端」。每个引擎在能力矩阵里声明支持哪些 FeatureFlag。
挑选算法本质就是一句话:用请求要的 FeatureFlag,去匹配每个 Engine 声明支持的 FeatureFlag, 匹配得多的排前面。
3. 引擎清单:有哪些引擎、何时启用
3.1 Engine 联合类型 —— 所有引擎的名字
引擎标识是带分号的字符串,分号后是「变体修饰」。同一个底层后端可以有多个变体:
// engines/index.ts:42-57 —— Engine 联合类型(节选)
export type Engine =
| "data-layer"
| "fire-engine;chrome-cdp" // 浏览器渲染(Chrome via CDP)
| "fire-engine(retry);chrome-cdp" // 同上,但是「重试」变体
| "fire-engine;chrome-cdp;stealth" // 同上 + 隐身代理
| "fire-engine;tlsclient" // 轻量 TLS 客户端(不跑浏览器)
| "fire-engine;tlsclient;stealth"
| "playwright" | "fetch" | "pdf" | "document"
| "index" | "index;documents" // 缓存命中
| "wikipedia" | "x-twitter"; // 域名特化
读法: fire-engine;<engine>;<变体>。fire-engine 是外部抓取服务的统称;chrome-cdp /
tlsclient 是它内部两种抓法;stealth / (retry) 是叠加的修饰。这些字符串既是类型,
也是四张登记表的键,还是日志里能直接 grep 的标识。
3.2 engines[] —— 按环境变量条件启用
不是所有引擎都随时在线。engines[] 用布尔开关 + 展开语法把「配了才启用」的引擎拼进来:
// engines/index.ts:59-92 —— 条件启用(节选)
const useFireEngine = config.FIRE_ENGINE_BETA_URL !== "" && ... !== undefined;
const usePlaywright = config.PLAYWRIGHT_MICROSERVICE_URL !== "" && ...;
// useWikipedia 需要企业版账号密码;useXTwitter 需要 XAI_API_KEY 或开了 DB 鉴权
const engines: Engine[] = [
...(useXTwitter ? ["x-twitter" as const] : []),
...(useWikipedia ? ["wikipedia" as const] : []),
...(useIndex ? ["index" as const, "index;documents" as const] : []),
...(useFireEngine ? [ /* 6 个 fire-engine 变体 */ ] : []),
...(usePlaywright ? ["playwright" as const] : []),
"fetch", "pdf", "document", // ← 这三个永远在线,是最终兜底
];
几个要点:
fetch/pdf/document无条件在列——它们不依赖外部服务,是自托管(self-hosted) 部署下也能跑的保底方案。useIndex由config.INDEX_DATABASE_URL是否配置决定(services/index.ts:170)——没有索引库就没有缓存引擎。data-layer不在engines[]里。它是一条独立短路,只在buildFallbackList开头被单独判断(见 §5.1)。- 自托管测试环境下,即使
useFireEngine为假,只要提供了 mock,buildFallbackList会临时把 fire-engine 变体注入候选(:617-627),方便离线测试。
4. 能力矩阵:FeatureFlag × 引擎能力与质量分
这是引擎系统的「知识库」:每个引擎声明支持哪些能力、以及有多好用。挑选算法全靠它。