bigywater.tech
爬虫工程 · 53

页面改版检测:提取结果校验与告警

爬虫最难受的失败是“没报错,但数据坏了”:页面改版之后提取器照常返回,只是价格全空了,或者变成了分。这篇模拟一个逐步改版的站点,看哪些检查能发现哪些改版。

0 / 5
只看“有没有抛异常”,能发现的有问题版本数——全部漏报
155772
v5 价格单位变成分之后的价格中位数(基线 1558):没有空值,但数据已经错了
5 / 5
组合检查(商品数、字段空值率、价格中位数、结构相似度任一报警)命中的有问题版本数,误报 1 个

没有一个版本抛异常

实测 · 本机执行输出
  版本                         异常     每页商品数     名称空     价格空     评分空     价格中位数     结构相似度
  v0 基线                   0      30.0      0%      0%      0%      1558      1.00
  v1 良性变化                 0      25.8      0%      0%      0%      1582      0.88
  v2 价格类名改了               0      30.0      0%    100%      0%         —      0.87
  v3 外层容器和类名变了            0       0.0    100%    100%    100%         —      0.81
  v4 价格格式变了               0      30.0      0%      0%      0%      1558      1.00
  v5 价格单位变成分              0      30.0      0%      0%      0%    155772      1.00
  v6 灰度                   0      30.0      0%     25%      0%      1547      0.97
  v7 名称移进了链接              0      30.0    100%      0%      0%      1558      1.00

XPath 取不到元素时返回空列表而不是报错,所以改版之后爬虫照常运行、照常写库。8 个版本里没有一个抛异常,只看异常等于没有监控。

哪些检查发现什么

实测 · 本机执行输出
  版本                           只看有没有抛异常      商品数是否骤降    字段空值率是否超过    价格中位数是否偏离    页面结构相似度是否   实际是否真的坏了
  v0 基线                           ·            ·            ·            ·            ·   好
  v1 良性变化                         ·            ·            ·            ·           报警   好
  v2 价格类名改了                       ·            ·           报警           报警           报警   坏
  v3 外层容器和类名变了                    ·           报警           报警           报警           报警   坏
  v4 价格格式变了                       ·            ·            ·            ·            ·   好(宽松解析照样取对)
  v5 价格单位变成分                      ·            ·            ·           报警            ·   坏(价格 ×100)
  v6 灰度                           ·            ·           报警            ·            ·   部分页面坏
  v7 名称移进了链接                      ·            ·           报警            ·            ·   坏
  检查项依次为:只看有没有抛异常;商品数是否骤降;字段空值率是否超过 10%;价格中位数是否偏离基线 3 倍;页面结构相似度是否低于 0.9

字段空值率发现类名改了、容器变了、名称位置变了、灰度;价格中位数发现价格单位变成了分(这种“有值但错了”空值率发现不了);商品数骤降只发现整个容器变了的情形;页面结构相似度能提前发现结构变化,但会把加广告这种良性变化也当成改版(1 个误报)。

组合

实测 · 本机执行输出
  只看有没有抛异常:命中 0 / 5,误报 0,漏报 5
  商品数是否骤降:命中 1 / 5,误报 0,漏报 4
  字段空值率是否超过 10%:命中 4 / 5,误报 0,漏报 1
  价格中位数是否偏离基线 3 倍:命中 3 / 5,误报 0,漏报 2
  页面结构相似度是否低于 0.9:命中 2 / 5,误报 1,漏报 3
  组合(后四项任一报警):命中 5 / 5,误报 1,漏报 0

5 个有问题的版本被后四项检查的组合全部发现,代价是 1 个误报(良性变化 v1)。阈值是我手工定的(50%、10%、3 倍、0.9),没有调优。

灰度发布

实测 · 本机执行输出
  40 个页面里,价格提取失败比例的分布:最小 0%、中位 0%、最大 100%;整体平均 22%;超过 10% 的页面有 9 个

新版页面成片地出现在部分页面上,整体平均失败率 22% 看起来不高,但个别页面是 100%。监控要同时看分布(最大值、超过阈值的页面数),不要只看均值。

分步演示

页面改版检测
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.8 / requests 2.34.2 / aiohttp 3.14.3 / lxml 6.1.1(系统 Python);所有请求只发给本机自己起的测试服务器(127.0.0.1,端口 9960 以上,不访问任何真实网站);服务器可以按实验需要制造延迟、断连、压缩、重定向等行为,并记录收到的原始请求;计时用 time.perf_counter,数字每次运行会有小幅抖动。页面由脚本按版本生成(每页 15–40 个商品,每个含名称、价格、评分),提取器是针对基线写的 lxml XPath 加宽松的价格解析(去掉非数字字符);8 个版本各生成 20 页;基线用 v0 的 20 页;检查阈值是手工设定的;灰度发布用单独的 40 个页面,其中约 30% 使用新版;全部在本机,不涉及真实站点
    • 没有验证:真实站点改版的频率与形态(这里只是我设想的 8 种);更复杂的校验(模式学习、统计控制图);告警的接入(邮件、IM)与抑制;对每个字段设置不同阈值的效果;自动修复提取器(如用大模型根据新页面重写选择器);JavaScript 渲染的页面改版的检测;误报率在更长时间序列上的统计
    京ICP备2026067502号-1京公网安备11010502063487号