跳转到内容

抓取引擎的 Feature Flag 与质量评分机制

抓取引擎的 Feature Flag 与质量评分机制

学习目标

  • 理解 buildFallbackList 算法的完整流程
  • 掌握 Feature Flag 优先级求和与阈值过滤机制
  • 了解动态特征调整(AddFeatureError / RemoveFeatureError)的循环重试逻辑

前置知识

本章涉及多引擎回退的通用原理,建议先阅读:

下文聚焦 Firecrawl 的具体算法实现。


项目实践

Feature Flag 声明式定义

engines/index.ts 中,每个引擎通过一个对象声明其支持的所有特性:

const engineOptions: { [E in Engine]: { features, quality } } = {
"fire-engine;chrome-cdp": {
features: {
actions: true, // 支持页面交互
screenshot: true, // 支持截图
pdf: false, // 不支持 PDF
stealthProxy: false, // 不支持隐匿代理
branding: true, // 支持品牌提取
// ... 共 17 种特性
},
quality: 50,
},
// ... 其他引擎
};

所有 Feature Flag 及其优先级定义在 featureFlagOptions 中:

const featureFlagOptions = {
actions: { priority: 20 }, // 最高优先级
atsv: { priority: 90 }, // 反反爬解算器
pdf: { priority: 100 }, // PDF 解析
document: { priority: 100 }, // 文档解析
stealthProxy: { priority: 20 }, // 隐匿代理
// ... 其他
} as const;

buildFallbackList 算法详解

算法分四个步骤:

步骤 1:确定候选引擎列表

根据环境配置和 URL 特征,构建初始引擎列表:

  • 如果 FIRE_ENGINE_BETA_URL 未配置,所有 Fire Engine 变体不包含在内
  • 如果 URL 是 X/Twitter,仅保留 x-twitter 引擎
  • 如果 URL 是 Wikimedia,随机 50% 保留 wikipedia 引擎
  • 如果 shouldUseIndex 返回 false,移除 indexindex;documents

步骤 2:计算支持分数

prioritySum = sum(所有请求的 flag 的优先级)
priorityThreshold = floor(prioritySum / 2)
for each engine:
supportedFlags = engine 支持的 & 请求需要的 flags
supportScore = sum(supportedFlags 的优先级)
if supportScore >= priorityThreshold:
加入候选

这个算法确保候选引擎至少能满足请求一半以上的功能需求。

步骤 3:过滤低质量引擎

if any engine has quality > 0:
keep only engines with quality > 0

只要存在可用的通用引擎,就不使用专用回退引擎(如 PDF/Document,质量分为负值)。

步骤 4:排序

sort by: supportScore DESC, quality DESC

动态特征调整循环

scrapeURL 的主循环中,引擎执行可能返回两种动态调整错误:

AddFeatureError 的典型场景:

  • 引擎返回 403 → 需要 stealthProxy
  • 引擎返回 PDF 反爬错误 → 需要先用 stealth 代理预取

RemoveFeatureError 的典型场景:

  • 引擎报告某个 flag 配置导致抓取失败(如某个特定代理配置不兼容)

重试追踪器(ScrapeRetryTracker)

每次动态调整后,系统记录到 ScrapeRetryTracker

retryTracker.record("feature_toggle", error);
retryTracker.record("feature_removal", error);
retryTracker.record("pdf_antibot", error);
retryTracker.record("document_antibot", error);

限制配置:

  • SCRAPE_MAX_ATTEMPTS:最大总尝试次数
  • SCRAPE_MAX_FEATURE_TOGGLES:最大 Feature 添加次数
  • SCRAPE_MAX_FEATURE_REMOVALS:最大 Feature 移除次数

超过限制时抛出 ScrapeRetryLimitError,不再重试。

URL 强制引擎映射

engine-forcing.ts 允许为特定 URL 或域名模式强制使用指定引擎:

特定域名配置 → 强制引擎
*.example.com → fire-engine;chrome-cdp

这在处理已知需要特定引擎的站点时非常有用。

问题与规避

1. 优先级阈值算法的边界情况

priorityThreshold = floor(prioritySum / 2) 意味着如果一个请求需要 actions(20) + screenshot(10) = 30,阈值为 15。一个只支持 actions(20) 的引擎通过(20 >= 15),但一个只支持 waitFor(1) 的引擎不通过。

陷阱:如果请求需要很多低优先级 flag,prioritySum 很高,但没有任何引擎能满足一半。此时所有引擎都被过滤。

规避:系统中还有 quality > 0 过滤兜底 —— 即使没有引擎通过阈值,至少基础引擎(Fetch)仍可用。

2. forceEngine 绕过所有选择逻辑

internalOptions.forceEngine 被设置时,buildFallbackList 跳过所有自动选择,直接使用指定引擎。

陷阱:forceEngine 可能不支持请求需要的功能,但没有回退引擎。

规避:当 forceEngine 是数组时,系统仍然会执行支持分数过滤,确保数组中的引擎至少满足基本需求。

设计取舍

为什么用优先级阈值而非最低支持数?

阈值算法的好处:对于简单请求(不需要任何特殊功能),prioritySum = 0,threshold = 0,所有引擎都通过,由质量分决定使用哪个。对于复杂请求,阈值自动提高,确保只选择能力匹配的引擎。

如果改用”至少支持 N 个 flag”的方案,N 的值对所有请求都是固定的,无法适配简单和复杂请求的差异。

参考来源