跳转到内容

多引擎回退与质量评分机制

多引擎回退与质量评分机制

概述

在生产级网页抓取系统中,单一引擎(如简单的 HTTP 请求或单一浏览器实例)无法覆盖所有场景。JS 渲染页面需要浏览器引擎,简单静态页面用 HTTP 即可,PDF/DOCX 需要专用解析器,反爬站点需要 stealth 代理。多引擎回退机制通过声明式能力矩阵和质量评分,自动为每个请求选择最优引擎组合,并在主引擎失败时无缝回退。

核心概念

引擎类型

一个完整的抓取系统通常包含以下类型的引擎:

引擎类型适用场景能力特点
索引缓存已抓取页面的快速返回零延迟、支持截图/Markdown/结构化数据
Chrome CDPJS 渲染页面、需要交互的场景完整浏览器能力、支持 actions/screenshot
TLS Client需要 TLS 指纹模拟的站点轻量、速度快、不支持浏览器交互
Playwright本地浏览器自动化中等开销、基础抓取能力
HTTP Fetch静态 HTML 页面最快、最轻量、不支持 JS
专用引擎PDF、DOCX、Wikipedia、X/Twitter 等针对特定格式或平台优化

质量评分(Quality Score)

每个引擎有一个质量评分值,用于在多个候选引擎中选择优先级:

索引引擎: 1000 (总是优先尝试,缓存命中零成本)
X-Twitter: 1500 (专用平台引擎,最高优先级)
Wikipedia: 500 (专用平台,低于索引但高于通用引擎)
Chrome CDP: 50 (通用最强能力引擎)
Playwright: 20 (中等能力)
TLS Client: 10 (快速轻量)
Fetch: 5 (最基础)
PDF/Document: -20 (专用格式,负值表示专用回退)

质量评分的意义:

  • 正数:通用引擎,数值越高优先使用
  • 负数:专用回退引擎,仅在请求涉及对应格式时使用
  • 值 > 0 的引擎优先于值 < 0 的引擎:只要通用引擎可用,不使用专用回退

Feature Flag 支持矩阵

每个引擎声明式地定义其支持的功能特性:

// 示例:引擎能力声明
features: {
actions: true, // 是否支持页面交互(点击、滚动、输入)
screenshot: true, // 是否支持截图
pdf: false, // 是否支持 PDF 解析
stealthProxy: false, // 是否支持隐匿代理
location: true, // 是否支持地理位置模拟
mobile: true, // 是否支持移动端模拟
...
}

引擎选择算法

步骤一:构建候选列表

根据请求的功能需求(Feature Flags),计算每个引擎的支持分数

prioritySum = sum(所有请求的 feature flag 的优先级)
priorityThreshold = floor(prioritySum / 2)
对于每个引擎:
supportedFlags = 该引擎支持的、且请求需要的 flags
supportScore = sum(supportedFlags 的优先级)
if supportScore >= priorityThreshold:
加入候选列表

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

步骤二:过滤专用引擎

if 存在 quality > 0 的候选引擎:
过滤掉所有 quality <= 0 的引擎

这保证在有通用引擎可用时,不使用专用回退引擎。

步骤三:排序

selectedEngines.sort(
(a, b) => b.supportScore - a.supportScore // 支持度优先
|| b.quality - a.quality // 同分时质量优先
)

瀑布流竞争机制

引擎选择完成后,系统不简单地按顺序尝试,而是使用瀑布流竞争策略:

关键设计:

  1. getEngineMaxReasonableTime(engine):每个引擎有一个”合理最大时间”,超过这个时间还无结果就启动下一个引擎
  2. Promise.race 并发竞争:多个引擎同时运行,先成功者胜出
  3. 超时注入(Waterfall Delay):防止第一个引擎长时间挂起但实际已失败
  4. 胜利引擎标记:记录哪个引擎最终成功,用于可观测性和优化

动态特征调整

在抓取过程中,引擎可能发现当前 Feature Flags 配置不匹配:

  • AddFeatureError:引擎发现需要额外能力(如检测到反爬,需要 stealthProxy),自动添加 flag 并重试
  • RemoveFeatureError:引擎报告某些 flag 配置错误,自动移除并重试

这形成了一个自适应循环:

重试限制

为防止无限循环,系统设置了多重限制:

限制类型说明
最大尝试次数单个请求的总引擎尝试上限
最大 Feature 切换次数AddFeatureError 触发的重试上限
最大 Feature 移除次数RemoveFeatureError 触发的重试上限
最大 PDF 预取次数PDF 反反爬预取上限

潜在陷阱

1. 引擎超时设置不当

如果 maxReasonableTime 设置过高,第一个慢引擎会阻塞整个请求。设置过低则引擎还没完成就被取消。

对策:根据请求特征动态计算超时(如有 actions 需求时给浏览器引擎更多时间)。

2. 质量评分导致过度使用重型引擎

Chrome CDP 质量分 50,Fetch 只有 5。对于简单静态页面,用 CDP 是资源浪费。

对策:支持分数(supportScore)优先级高于质量分。不需要浏览器能力的请求,CDP 的 supportScore 不会领先太多。

3. AddFeatureError 循环

如果一个引擎反复报告需要不同的 feature flag,可能导致无限循环。

对策:通过 ScrapeRetryTracker 严格限制 feature toggle 次数。

4. 索引缓存命中但数据过期

缓存引擎质量分最高(1000),但如果数据是旧的,用户可能拿到过期内容。

对策:请求可指定 maxAge 参数,控制可接受的最大缓存时间。

设计优势

优势说明
自动适配用户无需选择引擎,系统根据请求特征自动选择
容错性一个引擎失败自动回退下一个,用户无感知
资源优化简单请求用轻量引擎,复杂请求才用重型引擎
可扩展新增引擎只需声明能力矩阵和质量分,无需修改核心逻辑

替代方案对比

方案优点缺点
固定引擎顺序实现简单无法适配不同场景
用户手动选择精确控制增加用户负担,容易选错
AI 动态选择最智能延迟高、成本高、不可预测
质量评分 + Feature Flag声明式、可预测、可扩展需要维护能力矩阵