同一个内容有多个地址时,canonical 链接是站点主动告诉你“哪个是正主”。可是站点会写错,还可能有链、环、冲突。这篇在一个故意写错 canonical 的本机站点上,对比只规范化 URL、信任 canonical、canonical 加内容校验和内容哈希。
每种地址怎么声明 canonical:HTML → 250、Link 头 → 60 相对路径的 canonical(/product/i?ref=mail 页面里写的是 "/product/i")需要用页面 URL 解析;例:<站点>/product/3
商品页有 5 种地址,canonical 的声明方式不一:HTML 里的绝对地址、相对地址(要用页面 URL 解析)、只有 Link 响应头。分页列表页的 canonical 全写成了 /list。
合并方式 页面组数 错并 被拆分 不合并(每个 URL 一组) 310 0 60 只做 URL 规范化(去斜杠、ref、sort) 130 0 60 信任 canonical(没有则规范化 URL) 61 1 0 canonical + 内容校验(目标内容一致才信任) 70 0 0 只看正文哈希(去掉了 footer 时间戳) 70 0 0 整页字节哈希(含 footer 时间戳) 310 0 60 ← 页面里每次变化的时间戳让哈希永远不同,什么都合并不了
只做 URL 规范化合并不了 /p/i 这种别名(130 组);信任 canonical 能合并别名,但会把内容不同的分页合成一个(61 组,错并 1);先检查 canonical 目标的内容是否与自己一致再信任,得到 70 组;只看正文哈希(去掉页脚时间戳)也是 70 组。整页字节哈希因为页脚里每次变化的时间戳完全失效。
信任 canonical 之后,这 10 个页面合成 1 组,内容各不相同的页面有 10 个——被丢掉的页面 9 个 加了内容校验(canonical 目标 /list 的内容与自己不一致 → 不信任):9 / 10 个页面保留了自己的身份
分页页面把 canonical 指向第 1 页是常见写法。直接信任会丢掉第 2 到第 10 页(9 页)。内容校验的代价是每个 canonical 目标要多抓一次(这里用了缓存)。
canonical 链 a → b → c(c 指向自己):/chain/a → /chain/b → /chain/c canonical 环 a → b → a:/loop/a → /loop/b → /loop/a(又回到已访问过的) canonical 指向 404:/bad → /gone → /gone(HTTP 404)
追 canonical 要限制跳数并记录已访问的地址:链最终自指,环会回到起点,指向 404 的目标要放弃。
HTML 里的 <link rel="canonical">(按出现顺序):['/html-says', '/html-second'] Link 响应头里的 canonical:['/header-says'] og:url:['/og-says'] → 三处给出 3 个不同的 URL(HTML 里还有 2 个)。取哪个由爬虫自己的策略决定;上面第二节的"信任 canonical"取的是 HTML 里的第一个,没有再取 Link 头
HTML 里可以有多个 rel="canonical",还有 Link 响应头和 og:url,它们可以互相矛盾。爬虫需要明确的优先级策略并记录来源。