先给结论:当同一 URL 在不同层缓存里出现不同内容时,不要从“百度爬虫抓错了”开始查,而要先固定一个可复现的请求条件,逐层对比响应内容与响应头,找出第一处版本分叉的位置。下面用一个假设情境串起整个过程。
假设某站点有一篇文章页,源站、CDN 边缘节点和站内对象缓存各存了一份副本。运维人员用浏览器访问看到的是新版标题,用命令行请求看到的是旧版标题,而百度爬虫日志里记录的抓取内容又是第三种:新版正文配旧版发布时间。三者都不算“完全错误”,但组合在一起就形成了不一致。
这个情境的关键不是谁对谁错,而是版本分叉发生在哪一层。只有先定位分叉点,后面的清理、刷新或回源策略才有意义。
多层缓存最容易制造的假象,是不同请求带着不同条件,却让人误以为是同一件事。要先把以下变量固定并记录:
User-Agent、Accept-Encoding、Cookie 是否存在;实际操作上,可以先用同一台机器、同一组请求头,分别请求源站地址和对外域名,把两份响应正文与响应头保存下来。这个动作的结果决定了下一步:如果源站与对外域名内容一致,问题在更外层;如果不一致,分叉点就在源站或它前面的那一层。
正文只能告诉你“结果不同”,响应头才能告诉你“谁返回的”。重点看这几类字段:
Age:数值偏大说明这份响应在缓存里放了较久;Cache-Control、Expires:决定这层缓存是否被允许复用;ETag、Last-Modified:用来判断两个副本是否指向同一版本;Vary:如果它按 User-Agent 或 Accept-Encoding 分键,同一 URL 出现多份副本就属于预期行为;X-Cache 一类自定义字段:不同厂商命名不同,只能作为线索,不能当作通用标准。假设源站返回的 ETag 是 A,CDN 边缘返回的是 B,站内对象缓存返回的又是 A。那么可以推断:分叉最可能发生在 CDN 边缘这一层,站内缓存与源站仍保持一致。此时下一步应该是定向刷新 CDN 上这个 URL 的副本,而不是去重启源站或清空全部缓存。
这两种情况的证据不同,处理路径也不同。
如果多个缓存层返回的正文不同,但每一层自身在多次请求中保持稳定,那属于缓存版本不一致。可核对的证据是:同一层连续请求多次,响应头和正文基本不变;换一层请求,正文或 ETag 变化。
如果某一层在短时间内反复返回不同内容,甚至同一秒内两次请求结果不同,那更像是回源竞争或缓存击穿,而不是单纯的版本滞后。此时可以观察源站访问日志中同一时间段的回源次数,看是否出现同一 URL 被并发回源。
还有一种容易被误判的情况:百度爬虫抓到的内容与当前缓存版本不同,但这并不自动说明爬虫抓错了。爬虫可能在旧版本仍有效时抓取,之后缓存才更新;也可能因为 Vary 或 UA 分流拿到另一份副本。要确认这一点,需要把爬虫抓取时间与缓存刷新时间对齐,而不是只比较“现在看到什么”。
定位到分叉层之后,动作范围应当与该层匹配。可以用下面这个假设比较来说明取舍:
ETag 是否与源站一致;每次只改一层,改完立刻用固定请求条件复测。如果一次性刷新所有层,即使问题消失,也无法知道是哪一层起了作用,下次遇到同类问题仍然只能靠猜。
并非所有版本差异都是故障。如果 Vary 明确按 Accept-Encoding 分键,压缩版与未压缩版正文不同是正常的;如果站点按 UA 返回不同模板,移动端与桌面端内容不同也属预期。判断标准是:同一请求条件下,同一层缓存是否稳定返回同一版本。稳定,就不算一致性问题;不稳定,才需要继续追。
另外,robots.txt 只能限制抓取,不能替代缓存清理;站点地图也不保证缓存层会因此更新。把缓存一致性问题当成抓取规则问题处理,通常会走偏。
回到开头那个假设情境:固定请求条件后,发现源站与站内缓存一致,只有 CDN 边缘返回旧版,于是只刷新该 URL 并复测,边缘的 ETag 与源站对齐,三种结果收敛为一种。这个动作的结果也决定了下一步——如果复测后边缘仍返回旧版,就要继续查 CDN 的缓存键规则,而不是回到源站重复排查。