一个小型抓取任务不是能用就是不能用,而且你很快就能发现。大型任务从来不处于这两种状态之一。在任何时刻,你的一部分流量都在失败,而具有操作意义的问题不是”代理是否在工作”,而是”哪一部分出了问题,是我们、是提供商,还是目标网站。”
要得到有用的答案,意味着要沿着可能独立失效的维度进行埋点,这与单纯添加更多指标不是一回事。以下是应该测量什么、如何在不浪费带宽的情况下探测,以及如何区分这三种失败来源。
健康状况是按路由划分的,而不是按代理划分的
第一个需要纠正的认识:在池化网关上,你没有可监控的”代理”。你从未选择过出口地址,也不会保留它们,一个曾经失败过的地址并不是一个你可以随时间追踪的实体。你能追踪的是路由,即你所能控制的要素的组合:目标网站、国家,以及在相关情况下的城市或 ASN。
因此,健康状况的度量单位是路由。amazon-de、serp-us-chicago、marketplace-jp。每一个都有自己的成功率、延迟特征和失败构成,而且每一个都可能在其他路由保持完好时单独恶化。一个全局的”代理健康度”数字恰恰会把你需要的信号平均掉:整体百分之九十五可能是二十条路由达到百分之九十九、一条路由为零,只有第二种解读才会告诉你需要采取行动。
再加入会话健康度作为第二个、更短暂的单位。一个开始被出验证挑战的粘性会话应该被淘汰并替换,而不是继续使用,这个决策应该属于分发会话的同一个组件,具体做法参见构建代理管理器。
先做被动监测:每一个真实请求都是一次健康检查
最便宜的监控就是你已经在发送的流量。每一个生产请求都会产生一个结果,如果你把它记录在对应的路由下,就能以零额外带宽成本获得持续的健康数据。
关键细节在于什么算作成功。不是 200。一个验证挑战页面、一个通用或空的结果、一个被截断的列表,或者一个跳转到落地页的重定向,这些都会返回 200,但都意味着你的采集失败了,因此一个只统计状态码的监控系统会在数据集恶化时报告”健康”。在记录结果之前,应根据每个目标的预期对响应体进行验证,这正是检测被屏蔽或虚假内容中所讲的方法。正是这一个改变,决定了一个健康仪表盘是能发现问题,还是只能印证你的偏见。
每个请求至少要记录:路由、经过验证的结果、延迟、字节数,以及如果失败,失败的类别。这些足以计算下面所有内容。
对失败分类,因为类别就是诊断结果
统计失败次数只能告诉你出了问题。对失败分类才能告诉你出了什么问题。五个类别几乎涵盖了所有情况,每一个都指向不同的方向。
认证失败意味着凭证问题或格式错误的定向标志,这是你这一侧的配置问题,重试也不会解决,参见修复 407 和凭证错误。无匹配失败,即网关在当时没有符合你筛选条件的地址,意味着你的定向条件过窄,而不是有什么东西坏了;从城市放宽到国家级别就能解决。速率信号,即 429 及类似情况,意味着你对该目标的请求节奏过于激进,这是一个限流问题。屏蔽信号,即验证挑战和持续的 403,意味着目标拒绝了该身份,因此应淘汰该会话,并考虑你的请求头或指纹是否才是真正的原因。传输失败,即超时和连接重置,是最模糊的一类,值得单独调查,因为请求超时的原因包括目标响应慢、路由不健康,以及你自己的并发数过高。
构成比例比总数更重要。一条成功率百分之八十、构成以速率信号为主的路由需要放慢节奏;同样是百分之八十但以屏蔽信号为主的路由需要不同的身份策略;而以无匹配错误为主的路由需要更宽泛的定向。相同的数字,三种不同的解决方案。
谨慎使用主动探测
被动监控有一个盲点:它只覆盖你当前在使用的路由,所以一条计划在凌晨三点运行的路由,不会在晚上十点给你任何它已经出问题的预警。一小组主动探测可以填补这个空白,但它们要消耗带宽,所以要让它们保持轻量且有针对性。
有两种探测值得运行。一种是按国家进行的连通性与地理探测,访问一个能回显地址及其位置信息的小端点,以确认网关可达,并且你请求的国家就是你得到的国家。要让它保持轻量,因为这是你运行频率最高的探测。一种是针对每个重要目标的目标哨兵探测,抓取一个已知稳定的页面并验证它,能告诉你该特定目标是否正常响应,这是区分目标问题与代理问题的检查方式。
import requests
def geo_probe(country):
proxy = f"http://customer-USERNAME-country-{country}:PASSWORD@p.shifter.io:443"
try:
r = requests.get("https://ipinfo.io/json",
proxies={"http": proxy, "https": proxy}, timeout=15)
d = r.json()
return {"ok": d.get("country","").lower() == country,
"got": d.get("country"), "org": d.get("org")}
except Exception as e:
return {"ok": False, "error": type(e).__name__}
在你实际使用的国家范围内,以较慢的频率运行地理探测,而哨兵探测的频率应与静默失败给你带来的损失成本相匹配。当一个探测连续多次失败时才报警,而不是失败一次就报警,因为在轮换池上,单次失败是正常的噪声。
区分三种失败来源
这是事故发生时真正会被问到的问题,而答案来自于比较各种信号,而不是来自任何单一指标。
你自己这一侧的问题表现为许多互不相关的目标同时出现失败,通常恰好从某次部署开始。到处出现的认证失败、某个工作节点池中传输错误的骤增,或者有人收紧了筛选条件之后无匹配错误的上升,这些都指向内部。判断标志是”广度”:你自己的 bug 很少会遵守目标边界。
提供商的问题表现为许多目标同时出现失败,但都局限在网络层:连通性探测失败、地理探测返回错误的国家、传输错误上升,而那些确实能连通的目标哨兵探测仍然返回有效页面。这也是公开状态页面发挥作用的地方,因为将你自己的下滑与提供商的事故历史进行对照,能立即回答这个问题,而决定你能获得何种赔偿的分级标准写在代理 SLA 与正常运行时间保证中。
目标网站的问题表现为失败局限在一个目标网站上,而其他所有路由都保持健康,并且该目标的哨兵探测失败,但其地理探测通过。如果它在所有地区同时失败,那么该网站很可能自身出了问题;如果它只在某一个地区失败,那你面对的是特定地区的屏蔽或某个区域性的边缘节点问题。
要做好埋点,让这些对比只需一次查询就能完成,而不是花一个下午:同样的失败类别和结果,打上路由、地区和工作节点的标签,就足够了。
该在什么情况下报警
仪表盘用于调查,报警用于叫醒某个人。要让报警集合小而精,并让每一条都具有可操作性。
当某条路由的经过验证的成功率低于其自身的滚动基线时报警,而不是设一个全局阈值,因为一条对某个高强度反爬目标通常只能跑到百分之七十的路由,在百分之七十时是健康的,而在百分之四十时才是坏的。当失败构成发生变化时报警,因为一条成功率保持不变但屏蔽信号取代了速率信号的路由,其性质已经发生了预示麻烦的变化。当重试比例上升时报警,因为它会在成功率下降之前上升,因此是你能得到的最早预警。当覆盖率出问题时报警,即一条计划运行的路由产生的记录数量明显少于其近期历史,这能捕捉到成功率无法察觉的静默萎缩。以及当你依赖的某个地区的探测连续多次失败时报警。
要求持续的偏离,而不是单个时间区间的偏离,并与滚动基线进行比较。这些指标背后的定义在代理关键性能指标中,而管道层面的埋点方法在如何监控网页抓取管道中。
让系统据此采取行动
只产生图表的监控,会让人始终留在本该由机器处理的问题的处理环节中。同样的信号应该驱动自动化行为:淘汰累积屏蔽信号的会话,在某条路由成功率崩溃时打开断路器,以停止向一个没有响应的目标持续发送请求,当一个地区恶化时将工作转移到另一个地区(参见多地区管道中的故障切换),并在出现速率信号时自动降速。人应该只在需要判断力的事情上被提醒,而不是在需要一条规则的事情上被提醒。
结论
在规模化场景下,健康状况不是代理的属性,而是你运行的每条路由的属性,所以要按目标和地区进行埋点,并让会话健康度成为其自身独立的短暂信号。要基于经过验证的响应体而非状态码来判断每一个结果,因为这正是监控与自我欺骗之间的区别。要对失败进行分类,因为其构成比例会告诉你应该放慢速度、更换身份、放宽定向条件,还是修复自己的配置。要加入一层轻量的地理探测和目标哨兵探测,以覆盖当前没有运行的路由,并区分提供商问题和目标问题。然后针对每条路由自身基线的持续偏离、失败构成的变化和重试比例进行报警,并将同样的信号接入自动淘汰、退避和故障切换机制,让系统在叫醒任何人之前先修复它能修复的问题。
底层依托是住宅代理,国家和城市定向以及会话控制都是按请求表达的,这正是让路由级健康监控成为对自身请求打标签的问题,而不是一个集成项目的原因,配合按 GB 计费的定价,精简的探测策略相对于采集本身几乎不产生什么成本。