HaoWordTool

多语言 SEO · Case 02

30 个多语言工具页的 canonical 全指向英文版:等于主动放弃收录

2026-10-25 实测 · 对象 haowordtool.com · 366 个页面全量扫描

这事的起点是一个看起来很简单的判断:「多语言页做得还不够好,先留着 noindex,等内容真正本地化了再放开。」判断是对的。但它底下压着更硬的一层——那批页面的 canonical 全部指向英文版。在这种情况下,放开 noindex 完全无效,两个信号互相抵消。

现象:全站 171 个 error,四类,没有玄学

写了个扫描器把每个页面的 canonical、hreflang、robots 全提出来对照,输出按目录和错误码聚合:

错误码数量成因
HL_BROKEN111hreflang 指向已删语种 /ar/ /ru/,或指向不存在的同语种页
CANON_FOREIGN30ja/es 全部工具页 canonical 指向英文版
HL_DUP26同一语言重复声明,全在 tools/
CANON_MISSING4examples/ 下演示页缺 canonical

171 条里没有一条是「玄学」,四类各有明确成因。这决定了修复方式:不需要逐页手改,可以写确定性修复器。

核心问题:canonical 是「指向自己」还是「指向别人」

ja/tools/age-calculator.html  → canonical: /tools/age-calculator.html    ← 英文版
es/tools/calculadora.html     → canonical: /tools/calculator.html        ← 英文版
de/tools/calculator.html      → canonical: /de/tools/calculator.html     ← 自指,正确

canonical 是一个「告诉搜索引擎哪个版本是正本」的指令。指向英文版,等于明确说「别收我,收那篇英文的」。这与 noindex 的效果方向一致:两者都在阻止这个页面被单独收录。所以对 ja/es 这 30 个页面来说,放开 noindex 只是拆掉了一道门,另一道门还锁着。

de 的写法是对的,这也解释了为什么同样是「内容较薄的语言页」,de 的 11 个工具页能过闸门,ja/es 一个都过不了。

hreflang 的问题更隐蔽

111 条 hreflang 指向 /ar/ 和 /ru/——这两个目录早就不存在了。hreflang 指向 404,等于向搜索引擎声明「这个语言版本在这儿」,实际打开是错误页。搜索引擎处理这种不一致的方式通常是:整组 hreflang 都不再被信任,包括那些本身写对的条目。

另外 tools/ 下 26 处同一语言重复声明(en/de/ja 各出现两次),这属于格式错误,同样会削弱整组信号。

修复:只动 head,不动正文

修复器做了三件事,都是确定性的、可重跑的:

  1. canonical 非自指的页面,改为自指。
  2. hreflang 中语言前缀不在现存语种列表里的条目,删除。
  3. 同一语言重复的 hreflang,保留第一条。

一共涉及 87 个页面、165 处。落盘前留了两层回滚:git,以及一个 .head-backup/ 目录。

dry-run 救了一次误删

第一版修复器把 hreflang="zh-cn" 当成弃用语种删掉了。zh-cn 是合法标签,与 zh 同属中文,只是带地区子标签。判断逻辑写成了「完整字符串是否在列表里」,正确写法是「取主语言前缀再比较」。这类错误如果不做 dry-run 直接落盘,会在几百个页面上同时生效。

复验:error 归零,然后看闸门

指标修前修后
error1710
es 可放开页114
ja 可放开页011
sitemap 收录163189

关键在第二、三行:同一批内容,一个字没改,只是把信号修正了,可放开页从 1 变成 14、从 0 变成 11。这说明之前的「内容不够本地化」只是部分原因,更早生效的阻断在 head 里。

我自己写错判据的两次

一、拿目标语言虚词率当门槛

第一版闸门用「德语虚词占比」判断一页是不是真德语,结果把 de/tools/calculator.html 判成 FAIL:lang:28% < 35%。而这页是真德语、零英文残留。原因是工具页术语多、正文短、按钮文字多,德语虚词天然占比低。改用「英文残留率」后,这页 0.0% 直接通过。

二、功能检测只认 addEventListener

德语 word-counter 被判成空壳,理由是「没有交互逻辑」。实际它写的是 t.oninput = upd,内联属性绑定,功能完整。判据补上 oninput / onchange / fetch / Intl / setInterval 之后才正确。

两次都是同一个方向:判据把「能用但写法不同」误判成「不行」。这类错误的代价不是漏报,而是把好页面当成坏页面处理——加厚、封索引,做的都是无用功,还会掩盖真正的问题。

顺带发现的一条平台规则

静态托管上「同名文件 + 同名目录」会怎么解析?用站内 tools/text-diff 实测(两种形态同时存在):

/tools/text-diff    → text-diff.html          (无斜杠:文件优先)
/tools/text-diff/   → text-diff/index.html    (带斜杠:目录优先)

这意味着新开一个栏目目录时,如果根目录正好有一个同名 .html,无斜杠访问会打到那个文件而不是栏目。新建栏目页里的内链必须全部带斜杠,否则用户点进去看到的是另一个页面。

可复用的检查顺序

  1. 先扫全站 canonical,统计「非自指」比例。比例高说明是系统性问题,不要逐页修。
  2. 检查 hreflang 指向的 URL 是否真实存在——指向 404 比缺失更糟。
  3. 检查同一语言是否重复声明。
  4. 以上三项清零后,再去看内容质量。顺序反了会误判内容。
  5. 任何批量改写前先 dry-run,并人工看一遍 diff 里被删掉的条目。