结论先行:当两个地址返回的正文内容相同,但响应头不同,最该先判断的是“哪一个地址才是规范目标”,而不是内容是否重复。若差异出现在 Location、Content-Type、Vary 或 Cache-Control 这类头上,它影响的是重定向是否成立、缓存是否分叉、抓取工具是否把两者当作同一资源;若差异只在 Date、Server 等与语义无关的头上,通常不影响规范化判断。
页面内容相同不代表响应等价。可以把响应头分成三组来看:
Location 与状态码。若一个地址返回 301 并带 Location,另一个地址直接返回 200,那么它们不是“同一个页面的两种写法”,而是“跳转关系”与“终点”的区别。此时内容相同只是巧合,判断应围绕终点是否可访问、跳转链是否唯一展开。Content-Type 中的字符集、Content-Language、Vary。当 Vary 包含 Accept-Language 或 User-Agent 时,同一地址会因请求头不同返回不同版本;如果两个地址的 Vary 不一致,缓存和中间层可能把它们视为不同资源,即使正文此刻看起来一样。Cache-Control、Expires、ETag、Last-Modified。这些头不改变页面语义,但会影响下一次请求拿到的是缓存副本还是回源结果。若一个地址允许长缓存、另一个禁止缓存,改版后你观察到的“内容已更新”可能只发生在其中一个地址上。反过来,Date、Server、X-Request-Id 这类头几乎总在变化,不应作为判断两个地址是否同一资源的依据。把它们纳入比较,只会制造噪音。
上面的分组有一个重要前提:你比较的是同一时刻、同一请求条件下的响应。如果两个地址的差异来自不同的请求条件,结论就会失效。
假设站点对移动端和桌面端返回同一套正文,但 Vary: User-Agent 只在其中一个地址上存在。你用桌面 UA 请求 A、用移动 UA 请求 B,看到正文相同、响应头不同,于是判断“B 缺少 Vary 是配置遗漏”。但这个差异可能只是请求条件不同造成的,而非两个地址本身的配置差异。要排除这种可能,必须固定请求方法、UA、Accept-Language 和 Cookie,再分别请求两个地址,否则任何响应头对比都不可靠。
另一个反例是中间层改写。CDN、反向代理或应用防火墙可能对其中一个地址追加或剥离响应头。此时源站配置可能完全一致,差异只出现在边缘节点。判断时应至少对比一次回源响应与一次边缘响应,确认差异发生在哪一层,而不是直接改源站配置。
与其猜测,不如收集能互相区分的证据。以下动作按顺序执行,每一步的结果都会决定下一步:
Date、Server,停止排查,这不影响规范化。Location 或状态码,追踪跳转链,确认终点唯一且返回 200。若出现 A→B→A 的循环,先解决循环,再谈内容重复。Vary 或 Content-Type,检查两个地址是否由同一应用路由处理。若路由不同,差异通常来自配置而非内容。假设某站点改版后,旧地址 301 到新地址,两者正文相同,但旧地址响应带 Cache-Control: max-age=3600,新地址带 no-store。固定条件请求后会发现:旧地址的 301 被缓存,新地址每次回源。此时下一步不是改内容,而是统一跳转响应的缓存策略,否则部分用户会在缓存过期前一直停在旧地址的跳转上。这个例子只用于说明比较方法,不代表任何真实站点数据。
判断的落点是一个动作:确定唯一规范地址,并让另一个地址的响应头服务于这个目标。
Location 指向规范地址,同时避免跳转链超过一层。Vary、Content-Language 和页面内的规范声明区分版本。完成修改后,重新用固定条件请求两个地址,确认状态码、Location、Vary 和缓存头已按预期收敛。只有当这些头不再互相矛盾时,内容相同才不会继续干扰后续的规范化与抓取判断。