多引擎回退与质量评分机制
多引擎回退与质量评分机制
概述
在生产级网页抓取系统中,单一引擎(如简单的 HTTP 请求或单一浏览器实例)无法覆盖所有场景。JS 渲染页面需要浏览器引擎,简单静态页面用 HTTP 即可,PDF/DOCX 需要专用解析器,反爬站点需要 stealth 代理。多引擎回退机制通过声明式能力矩阵和质量评分,自动为每个请求选择最优引擎组合,并在主引擎失败时无缝回退。
核心概念
引擎类型
一个完整的抓取系统通常包含以下类型的引擎:
| 引擎类型 | 适用场景 | 能力特点 |
|---|---|---|
| 索引缓存 | 已抓取页面的快速返回 | 零延迟、支持截图/Markdown/结构化数据 |
| Chrome CDP | JS 渲染页面、需要交互的场景 | 完整浏览器能力、支持 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 // 同分时质量优先)瀑布流竞争机制
引擎选择完成后,系统不简单地按顺序尝试,而是使用瀑布流竞争策略:
关键设计:
getEngineMaxReasonableTime(engine):每个引擎有一个”合理最大时间”,超过这个时间还无结果就启动下一个引擎Promise.race并发竞争:多个引擎同时运行,先成功者胜出- 超时注入(Waterfall Delay):防止第一个引擎长时间挂起但实际已失败
- 胜利引擎标记:记录哪个引擎最终成功,用于可观测性和优化
动态特征调整
在抓取过程中,引擎可能发现当前 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 | 声明式、可预测、可扩展 | 需要维护能力矩阵 |