直接回答:把“功能开关状态”当作页面内容的一部分来记录,而不是只记录代码版本。每次开关变更时,同时保存开关标识、取值、生效时间、受影响URL规则,以及一份可对照的页面快照或关键字段哈希。这样当抓取结果出现差异时,你能分清是开关改动、模板改动还是渲染环境变化导致的,而不是把它们混成一个“页面变了”的结论。
假设你有这样一个场景:站点用同一个模板渲染商品页,但某个开关控制着“是否展示库存模块”。开关打开时页面多出一段库存文本和几个内部链接,关闭时这段内容消失。你只在测试环境用两三个URL验证过开关行为,觉得没问题就上了生产。规模化之后,抓取日志里开始出现同一批URL在不同时间返回不同内容的情况,有的抓取到了库存链接,有的没有。
这个例子成立的前提是:开关是服务端渲染、且对爬虫和普通用户返回同一份HTML。如果开关只在前端JS里生效,那么爬虫看到的HTML可能根本不含这段内容,此时记录版本状态的重点要换成“渲染后DOM”而不是原始响应。这两种情况的记录对象不同,不能直接照搬同一套方法。
只记录“代码提交号”是不够的,因为同一个提交在不同开关取值下会产出不同页面。建议每次变更至少记录以下字段,并让它们能一一对应到具体URL:
show_inventory=true,而不是笼统写“库存模块已上线”。这里有一个实际动作:先对开关打开和关闭两种状态各抓一批样本URL,把关键字段哈希存下来。如果两种状态的哈希分布几乎重合,说明这个开关对爬虫可见内容没有实质影响,后续可以降低记录频率;如果分布明显分成两簇,就必须把开关状态纳入每次变更记录,否则你无法解释抓取差异。
假设你关闭了某个开关,随后发现某个目录的抓取请求数下降甚至归零。这看起来像是开关生效了,但抓取量变化还有别的合理解释:站点地图更新延迟、内部链接被其他改动移除、抓取预算被分配到别处、服务器对该目录返回了较慢响应导致抓取节奏改变。单看请求数下降,无法区分这些原因。
要缩小范围,需要把开关状态记录与返回状态码、响应体哈希放在同一时间轴上对比。如果开关关闭时间点之后,该批URL的响应体哈希稳定地少了库存链接集合,而返回状态码和抓取频率没有同步异常,才能较有把握地说“这次变化与开关相关”。即便如此,这仍是相关性判断,不是因果证明;要确认因果,需要在受控条件下重复一次开关切换并观察同一批URL。
不需要复杂系统,一个按时间追加的记录就够用。每条记录包含:变更时间、开关标识、旧值、新值、作用范围、样本URL列表、每个样本的关键字段哈希、记录人、以及“本次是否预期改变爬虫可见内容”的明确判断。最后这一项很关键:如果预期不改变,但哈希变了,说明还有你没意识到的因素在起作用。
一个假设的短例子:某次把 show_inventory 从 false 改为 true,作用范围是 /p/ 下全部页面。记录里样本URL取了10个,其中8个哈希变化、2个没变。进一步检查发现,那2个页面本身没有库存数据,所以开关打开也不渲染库存模块。这说明“开关打开”不等于“每个页面都多出内容”,作用范围要按数据条件再收窄,否则后续对比会把正常差异误判为异常。
个别样本成立、规模化后出现例外,通常来自三类边界:数据条件差异(部分页面没有可渲染内容)、渲染路径差异(缓存命中与未命中返回不同版本)、以及规则覆盖差异(开关作用范围写得比实际影响范围大或小)。处理顺序建议是:先从例外URL里找出共同特征,再回到记录表核对那段时间的开关取值,最后判断是记录粒度不够还是开关规则本身需要修正。
如果例外集中在缓存层,那么只记录开关状态仍然不够,还要记录缓存键是否包含开关取值。否则同一个URL在缓存命中和未命中时会返回不同内容,而你的记录表里只有一个开关值,无法解释这种分裂。这个判断会影响下一步:是调整缓存键,还是把开关变更安排在缓存整体失效之后执行。两种做法的代价不同,取决于你的缓存粒度和你能否接受一段时间的页面不一致。
最后要接受一个限制:任何记录方法都只是让你在出现差异时更快定位原因,它本身不保证抓取行为按预期变化,也不保证索引状态同步更新。记录的价值在于,当有人问“这次页面变化是不是开关引起的”,你能拿出一条可复查的对照链,而不是靠回忆和猜测。