先给结论:把两者当作两条独立证据链,分别固定“谁产出了这份HTML”和“谁在事后改了DOM”,再在同一个URL、同一组请求头上做差分。多数差异不是缓存坏了,而是缓存层保存的版本与浏览器执行脚本后的版本本就不同。定位的关键是找到分叉点:分叉发生在回源、缓存键、还是客户端脚本。
静态响应与脚本渲染结果不一致,通常落在两类里。第一类是内容型差异:静态HTML里是A价格、A库存,脚本跑完变成B。第二类是结构型差异:静态HTML缺少某块列表或某个区块,脚本注入后才出现。两类差异的排查方向不同。内容型差异往往指向缓存保存了旧版本,或缓存键没把影响内容的参数算进去;结构型差异往往指向脚本本身承担了渲染职责,而缓存层拿到的只是骨架。
假设情境:某商品详情页,静态抓取返回的HTML里价格是旧值,用无头浏览器执行脚本后价格是新值。单看一个URL,很容易判定“缓存没更新”。但把这个结论直接推到全站,就会出错。
第一步是控制变量。对同一个URL,用完全相同的请求头、Cookie、User-Agent、Accept-Encoding 发两次请求:一次只看原始响应体,一次执行脚本后取最终DOM。把两次结果里的关键字段(价格、库存、标题、主图URL)逐项比对,标出哪些字段变了、哪些没变。
第二步是判断差异是否随缓存状态变化。对同一URL连续请求多次,观察原始响应体是否稳定。如果原始响应体一直返回旧值,说明缓存层持有的就是旧版本;如果原始响应体时新时旧,说明缓存键或回源选择不稳定。这一步能区分“缓存内容陈旧”和“缓存路由抖动”,两者的修复动作完全不同。
第三步是检查脚本是否在覆盖内容。如果原始响应体里根本没有价格字段,只有脚本注入后才出现,那么“静态与脚本不同”是设计使然,不是缓存故障。此时该问的是缓存该不该保存脚本渲染后的结果,而不是缓存有没有失效。
缓存键决定了“哪些请求被视为同一个资源”。如果内容随某个请求头变化,而缓存键或 Vary 没有覆盖它,就会出现同一URL对不同客户端返回同一份缓存副本的情况。可核查的动作是:对比带与不带某请求头时原始响应体的差异,并检查响应头里的 Vary 是否声明了该维度。
结果如何影响下一步:若带某请求头就返回不同内容,而 Vary 未声明,则差异来自缓存键设计,应调整缓存策略而非清理缓存。若无论带不带该请求头,原始响应体都相同,则差异更可能来自脚本或回源逻辑。
单样本成立不代表可照搬。一个URL上“清缓存后恢复正常”不能证明全站都该清缓存,因为可能只是该URL恰好命中了旧副本。规模化排查时,需要按模板、按缓存层级、按参数组合分组抽样,而不是逐页清理。
不能直接照搬的边界包括:
一个可执行动作是:先按页面模板分组,每组抽若干URL,记录“原始响应体版本”和“脚本渲染后版本”是否一致。若某组内多数一致、少数不一致,优先查该组的参数与缓存键;若整组都不一致,优先查该模板的缓存策略与脚本职责划分。
抓取限制、站点地图与HTTPS状态都可能干扰判断,但都不构成直接结论。robots.txt 的抓取限制不等于可靠的索引移除;站点地图不保证收录;HTTPS 不保证安全无漏洞或排名。这些与“静态与脚本结果不同”不是同一层问题,不应混入同一次差分。
另外,请求量或抓取量归零不能单独证明缓存处理正确。它也可能是采集端调整、网络波动或统计口径变化造成的。要证明缓存行为符合预期,仍需回到原始响应体与脚本渲染结果的逐项比对。
最终判断顺序建议固定为:先确认差异属于内容型还是结构型,再用固定请求做原始响应体与最终DOM的差分,然后检查缓存键与 Vary 是否覆盖变化维度,最后才决定是调整缓存策略、修改脚本职责,还是清理特定副本。顺序颠倒容易把脚本问题误判为缓存问题。