数据抓取

构建目标健康评分:识别网站正在针对你

网站很少一步就封锁爬虫,而是先进行验证、软封锁和降速。如何将这些信号转化为每个网站的单一评分,并据此采取行动。

Matt Brown

Matt Brown

2026年9月22日 · 2 分钟阅读

一个网站几乎从不会在单一步骤中,从正常响应你的爬虫直接变成彻底封锁。最先发生的变化更加安静。多几个请求碰到验证页面。一些响应返回 200 OK,但里面什么有用的东西都没有。延迟悄悄上升。少数记录未能通过校验。每个信号单独看都像噪音,而且各自分布在不同的仪表盘上,所以在数据集出现缺口之前,没人会把它们联系起来。

目标健康分数就是解决办法:每个网站对应一个数字,说明这个网站是否仍然表现得像它自己的常态,并附带原因。本文将介绍哪些信号构成这个分数、如何在不自欺的前提下组合它们,以及当这个数字下降时爬虫应该做什么。

目标健康不等于代理健康

有必要精确说明这里衡量的到底是什么,因为这两者常被混淆。

代理健康问的是一条路线是否可用:一个网关、一个国家、一个会话、一个出口。目标健康问的是某个特定网站是否仍然愿意并且能够为你提供服务。一个失效的代理路线会在所有网站上都失败。而一个开始反对你的网站会在所有路线上都失败。

这个差异就是最初的诊断方式。当失败率上升时,按路线拆分。如果失败集中在某一个国家、某一个池或某一种会话模式上,那就是路线问题,应采用大规模监控住宅代理健康状况中的方法。如果失败在你发往该网站的所有路线上均匀上升,那说明该网站改变了对你的态度,这正是本文这个分数要处理的问题。

信号

按信号揭示的内容对它们进行分组。有些信号很明显,有些几乎无声无息,而那些无声的信号才是最容易被忽视、代价也最大的。

信号表现形式为何重要
硬性封锁403429、连接重置明确拒绝;容易统计
验证挑战CAPTCHA 或过渡页面网站怀疑存在自动化行为,正在进行测试
软性封锁200 OK 但带有封锁页面、空结果或被剥离的布局伪装成成功的拒绝
延迟漂移p95 响应时间相对该网站自身常态上升常是有意放慢,有时只是负载问题
提取失败必需字段缺失,结构不再匹配页面发生了变化,或你被提供了不同的内容
成本漂移每条有效记录所需的重试次数、字节数或额度增加上述所有情况,以金钱形式体现

软性封锁值得特别关注,因为状态码会说谎。在2023年对来自古巴的地理封锁的一次测量中,32个域名以 200 OK 状态返回了它们的封锁页面,详见哪些国家最常遭受地理封锁。一个信任状态码的爬虫会把这些记录为成功。要通过内容来检测软性封锁:已知的封锁页面标记、响应大小远低于该页面类型的常规大小,或者提取结果为空而它本应有内容。

两个责任方,两个分数

一个区分能省下大量困惑:把”网站变了”和”网站对你产生了敌意”分开。

一次重新设计会破坏你的解析器。每个页面都能正常加载,但必需字段消失了。这是一个提取问题,有代码上的修复方案,由维护解析器的人负责。而一个开始发起验证挑战和软性封锁的网站,则是一个关系问题,修复方式是改变收集的方式、数量,或者是否继续收集。责任方不同,应对方式也不同。

因此,把封锁、验证挑战、软性封锁和延迟计算为敌意程度,作为健康分数,同时把提取有效性作为一个独立的解析器健康信号来追踪。当两者同时下降时,先看敌意程度:一个正在提供验证挑战页面的网站,也会破坏所有的提取器。

相对该网站自身的常态打分

最常见的错误是使用全局阈值。5%的验证挑战率,对于一个从未向你发出挑战的网站来说是令人担忧的,而对于一个在首次访问时就对所有人发出挑战的网站来说则完全正常。每个信号都必须与该网站自身的基线相比较,基线要基于足够长的时间窗口才稳定,例如过去两周,并且要排除最近一天,以免糟糕的一天变成新的常态。

然后进行加权、限幅并求和:

from dataclasses import dataclass


@dataclass
class Window:
    requests: int
    hard_blocks: int      # 403, 429, connection resets
    challenges: int       # CAPTCHA or interstitial pages
    soft_blocks: int      # 200 OK carrying a block page or an empty result
    p95_latency_ms: float


MIN_REQUESTS = 50
WEIGHTS = {"hard_block": 35, "challenge": 25, "soft_block": 25, "latency": 15}


def signals(w):
    n = max(1, w.requests)
    return {
        "hard_block": w.hard_blocks / n,
        "challenge": w.challenges / n,
        "soft_block": w.soft_blocks / n,
        "latency": w.p95_latency_ms,
    }


def badness(name, now, base):
    if name == "latency":
        # Full penalty at three times the site's own normal latency.
        return min(1.0, max(0.0, (now / max(base, 1.0) - 1) / 2))
    # Full penalty at 20 percentage points above the site's own normal rate.
    return min(1.0, max(0.0, (now - base) / 0.20))


def health_score(current, baseline):
    if current.requests < MIN_REQUESTS:
        return None, ["not enough requests to judge"]
    now, base = signals(current), signals(baseline)
    penalty = {k: w * badness(k, now[k], base[k]) for k, w in WEIGHTS.items()}
    score = round(100 - sum(penalty.values()))
    reasons = [k for k, p in sorted(penalty.items(), key=lambda kv: -kv[1]) if p >= 1]
    return score, reasons

这里有几个设计选择是刻意为之。

  • 只有超出基线的部分才计分。 一个一直有3%验证挑战率的网站,今天继续这样做也不会扣分。
  • 每个信号都有上限。 一个失控的信号不能把分数拉到零以下,也不能淹没其他信号,而原因列表会显示是哪个信号占主导。
  • 窗口太小则不返回分数。 5个请求中有1个被封锁,这不是20%的封锁率,而是数据不足。没有分数比一个自信却错误的分数更诚实。
  • 权重是主观判断。 可以从类似上面这样的设置开始,再根据审查几次真实事件的结果进行调整。硬性封锁和软性封锁应获得最大权重,因为它们意味着你没有拿到数据。

同时也要对分数进行时间上的平滑处理,例如使用指数加权平均,这样一分钟的糟糕表现不会引发警报的反复跳动,而持续的下滑仍能在一小时内被察觉。

金丝雀:你能掌控的真实基准

上面所有的信号都是推断出来的。金丝雀能给你更接近真实情况的东西。为每个网站挑选几个稳定的页面,你知道正确答案应该是什么样子:一个你可以核实价格的商品、一个你知道条目数量的列表、一个结构几个月都没变过的页面。按计划,通过与生产流量相同的路径去抓取这些页面。

当一个金丝雀返回的页面看起来正常,但携带的值是错误的时,你就发现了最难检测的失败:内容解析得完美无缺,却根本不是真实访客会看到的内容。没有任何状态码或延迟图表能告诉你这一点。金丝雀也让基线变得可信,因为你独立于爬虫本身就知道它们的正确行为。

把行动与分数区间绑定,并保留人工介入

一个没人采取行动的分数只是一个仪表盘。给它划分区间,并为每个区间设定系统自行采取的行动:

区间含义自动行动
80 到 100表现如同自身常态
50 到 79正在退化降低对该网站的并发量,延长重新访问间隔,检查失败是全站范围还是路线特定的
低于 50网站明显在抵制暂停该网站,只保留金丝雀运行,提醒人工介入

退化区间直接对接爬虫的其他部分。降低并发量正是背压与流量控制中按主机限流器的作用,而分数下降应在成本感知调度器中提高该网站的有效成本,让预算转向仍能换回数据的网站。

最底部的区间刻意没有设置超出暂停之外的自动化操作。一个强烈抵制你的网站是在传递某种信息,正确的应对方式是人工决策:进一步放慢速度,检查你的采集是否仍符合该网站的条款,寻找官方 API 或数据源,或者停止采集。每当分数下降就自动升级到更激进的采集手段,只会把一个信号变成一场军备竞赛,而这恰恰是健康分数应该帮你避免的事情。速率限制与请求节流介绍了如何解读一个网站对你的要求。

像对待其他警报一样审查它

把这个分数当作一个有误报率的警报来对待,并对其进行审查。每次事件之后,问一问分数是否足够早地做出了反应,原因是否指向了真正的根源,以及自动行动是否起到了作用。大多数调优来自两三次真实事件,而不是提前设计好的权重。保留历史记录:数月间的分数变化,是你记录每个网站对自动化流量态度变化的最好证据。

结论

网站会逐渐而悄悄地对爬虫产生敌意,通过验证挑战、伪装的拒绝和变慢的响应,远早于彻底封锁之前。每个信号单独看都是模糊不清的。与网站自身的常态相比较,加权、限幅并组合起来,它们就能给出一个能提前反应的数字,并附带原因。

这个分数最大的价值在于它能让爬虫平静地做出应对:在被封锁之前放缓速度,把预算转移到别处,并把困难的决定交给面前有证据支撑的人。围绕它的更广泛的指标可参见监控网页抓取管道

准备好开始了吗?

试用 Shifter 住宅代理,205M+ 个 IP,195+ 个国家,低至 $0.10/GB。

立即开始