bigywater.tech
爬虫工程 · 45

canonical 与重复 URL:同一内容多个地址怎么合并

同一个内容有多个地址时,canonical 链接是站点主动告诉你“哪个是正主”。可是站点会写错,还可能有链、环、冲突。这篇在一个故意写错 canonical 的本机站点上,对比只规范化 URL、信任 canonical、canonical 加内容校验和内容哈希。

9 个
10 个内容不同的分页列表页都声明 canonical 是 /list,信任它时被丢掉的页面数
70 / 70
canonical 加内容校验和正文哈希两种方式得到的页面组数(真实内容数 70)
310
整页字节哈希得到的组数——页脚时间戳让每个响应都不同

站点与声明方式

实测 · 本机执行输出
  每种地址怎么声明 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 写错

实测 · 本机执行输出
  信任 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,它们可以互相矛盾。爬虫需要明确的优先级策略并记录来源。

分步演示

canonical 与重复 URL
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.8 / requests 2.34.2 / aiohttp 3.14.3 / lxml 6.1.1(系统 Python);所有请求只发给本机自己起的测试服务器(127.0.0.1,端口 9960 以上,不访问任何真实网站);服务器可以按实验需要制造延迟、断连、压缩、重定向等行为,并记录收到的原始请求;计时用 time.perf_counter,数字每次运行会有小幅抖动。站点由本机服务器生成;商品页内容含页脚时间戳(time.time_ns),用来模拟动态内容;canonical 从 HTML 、Link 响应头提取并用页面 URL 解析为绝对地址;“错并”=一个组里含多种真实内容,“被拆分”=一种真实内容被分在多个组;正文哈希只对 h1 和 p 文本做 sha1
    • 没有验证:搜索引擎对 canonical 的实际处理(它们把 canonical 当作提示而不是命令,这里没有核对);hreflang、amp 等其它 rel 关系;canonical 与 301 重定向同时存在时的优先级;跨域 canonical;JavaScript 注入的 canonical;内容哈希在轻微差异(近似去重,见 SimHash 一篇)下的失效
    京ICP备2026067502号-1京公网安备11010502063487号