侧边栏壁纸
  • 累计撰写 207 篇文章
  • 累计收到 2 条评论

给网站做了多语言国际化,搜索收录却全军覆没?排查 IP 自动重定向阻断爬虫、hreflang 互引闭环缺失与 Canonical 冲突我踩过的几个坑

2026-10-16 / 0 评论 / 6 阅读
AI 摘要由 AI 生成

网站进行多语言国际化后,因IP自动重定向、hreflang标记缺失与canonical冲突等问题,导致搜索收录受损。文章指出,通过识别爬虫并采用非侵入式语言提示,而非服务器端强制跳转,可解决这些问题。

去年团队把一个以中文为主的内容站改造成面向出海用户的多语言站点。架构上很规整,主域名按目录划分,/zh/ 放简体中文,/en/ 放英文版,根路径 / 留作语言分流。

为了给海外访客更好的体验,前端和运维在 Nginx 层加了地理位置与浏览器语言识别:检测到海外 IP 或者请求头带着英文偏好,就自动 302 临时重定向到 /en/;国内访问则跳到 /zh/。上线前在办公室测试了一圈,用代理节点访问能无缝切到英文版,国内直接访问跳中文版,大家觉得体验顺畅。

上线后第三周,业务负责人发来一张 Google Search Console 的索引覆盖截图:中文站原本收录的好几千个页面,在新周期里全线变灰,未编入索引原因清一色写着“网页包含重定向”或“备用网页(有适当的规范标记)”。更严重的是,百度那边由于抓取根目录时偶尔遭遇分流抖动,抓取频次也砍了一半。

整个多语言改造不仅没带来海外增量,还差点把老站原有的自然搜索流量送走。复盘排查了一周多,发现整套配置从 Nginx 路由、HTML 标签到 HTTP 响应头,踩了一连串极其隐蔽的坑。

按 IP 或 Accept-Language 强制 302 重定向,把 Googlebot 锁死在单语种

这是最致命也是最普遍的一个错。很多工程师会下意识认为:浏览器有 Accept-Language,IP 能查 GeoIP,在入口处做个判断自动跳转省心省力。

在 Nginx 里随手写个规则很容易变成这样:

# 踩坑配置:根据 Accept-Language 或海外 IP 自动 302
map $http_accept_language $lang_prefix {
    default zh;
    ~*^en en;
}

server {
    server_name example.com;

    location /zh/ {
        # 如果是海外 IP 访问中文目录,强行跳英文
        if ($geoip_country_code != "CN") {
            return 302 /en$request_uri;
        }
        try_files $uri $uri/ /index.php?$args;
    }
}

在真实浏览器测试时表现正常,但搜索引擎爬虫根本不是普通用户。

Googlebot 绝大多数抓取节点物理机房都在美国,出网 IP 全部属于美国地区,而且爬虫发起的 HTTP 请求通常不会携带特定语言偏好的 Accept-Language 请求头。

当 Googlebot 尝试抓取 https://example.com/zh/post-1024 时,Nginx 一看来源 IP 是美国,立刻返回了一个 302 Found,把 Googlebot 踢到了 https://example.com/en/post-1024。

Googlebot 遵循重定向规则,在英文页面得到了 200,但它永远拿不到中文页面的完整内容。在 Googlebot 的视角里,https://example.com/zh/post-1024 只是一个指向英文页的跳转跳板,它判定中文页面缺乏独立内容,于是将中文 URL 从索引库彻底清除。

即使是在根路径做分流,强制重定向同样危险。如果访问 https://example.com/ 总是 302 到 /en/ 或 /zh/,百度蜘蛛从国内 IP 抓取根域名拿到的是 302 到 /zh/,而 Googlebot 抓取根域名拿到的是 302 到 /en/。一旦配置或 CDN 缓存把这个 302 响应缓存住,蜘蛛就会在不同的语言路径之间陷入死循环或者混乱抓取。

解决这个问题的方法是:永远不要对抓取爬虫执行强制性的多语言重定向,对普通用户也尽量采用非侵入式的浮层提示,而不是服务端硬跳。

如果产品一定要保留新访客自动匹配语言的逻辑,至少要在 Nginx 层把常见搜索引擎爬虫识别出来放行,并借助 Cookie 做跳转记录:

# 优化方案:爬虫放行与 Cookie 记忆
map $http_user_agent $is_search_bot {
    default 0;
    ~*(Googlebot|Baiduspider|bingbot|YandexBot|DuckDuckBot|Applebot) 1;
}

server {
    server_name example.com;

    location / {
        # 爬虫一律不跳转,直接返回当前请求页面的正规内容
        if ($is_search_bot = 1) {
            break;
        }

        # 普通用户只有在根路径且未设置语言偏好 Cookie 时才引导一次
        if ($cookie_user_lang = "") {
            # 这里可以设置 Set-Cookie 并引导到首选语种,但千万别对具体文章页强行跨语种 302
        }

        try_files $uri $uri/ /index.php?$args;
    }
}

页面内容应当由 URL 自身明确决定,/zh/ 输出中文,/en/ 输出英文,爬虫抓取哪个 URL 就给出哪个 URL 的完整 HTTP 200 内容,绝不应该依据客户端 IP 或请求头去篡改目标页面的返回逻辑。

hreflang 双向互引缺少闭环,Google 直接全盘丢弃

多语言站点的核心信号是 hreflang。它的作用是告诉搜索引擎:这几个不同 URL 上的内容其实是同一篇内容的不同语言翻译版,你在美国搜索时展示英文版 URL,在香港或台湾搜索时展示中文繁体版,在大陆搜索时展示中文简体版。

很多开发以为只要在中文页写上“我的英文版是某某 URL”就行了。比如在 /zh/article-1 的 HTML 头部加上:

<link rel="alternate" hreflang="en" href="https://example.com/en/article-1" />

结果 GSC 里直接报红:“未指定回引标记(No return tags)”。

Google 的规则是严格对等的验证闭环:页面 A 声明页面 B 是它的外语版本,页面 B 的 HTML 里必须同时也包含声明页面 A 的标签。只要英文版页面漏掉了指向中文版的标签,Google 就会认为这种关联未经双方确认,为了防止第三方恶意关联,整组 hreflang 声明都会被废弃。

另一个漏掉的细节是自引标记(Self-referencing hreflang)。当前页面本身也必须出现在自己的 hreflang 列表中。

规范的完整结构长这样:

<!-- 在 https://example.com/zh/article-1 页面中必须包含全部语种及自身 -->
<link rel="alternate" hreflang="zh-Hans" href="https://example.com/zh/article-1" />
<link rel="alternate" hreflang="en" href="https://example.com/en/article-1" />
<link rel="alternate" hreflang="x-default" href="https://example.com/article-1" />

<!-- 同时在 https://example.com/en/article-1 页面中也必须一字不差包含同样的对应关系 -->
<link rel="alternate" hreflang="zh-Hans" href="https://example.com/zh/article-1" />
<link rel="alternate" hreflang="en" href="https://example.com/en/article-1" />
<link rel="alternate" hreflang="x-default" href="https://example.com/article-1" />

这里还要注意 x-default 的语义。它不是指英语,而是指当访客的语言没有匹配到列表中的任何一项时,搜索引擎应该展示的默认兜底页(通常是没有语种前缀的主页面或语言选择引导页)。如果全站没有做通用兜底页,可以把最主要的国际版本作为 x-default,但不能把 x-default 指向一个带强制跳转的动态逻辑页。

Canonical 标签指向主站中文版,把多语言版本全杀了

排查时发现的第三个重灾区是 Canonical 标签踩踏。

许多站点的公共模板直接写死了 Canonical 生成逻辑:
<link rel="canonical" href="https://example.com/zh/post-1" />

当生成英文版 /en/post-1 时,负责渲染模板的代码直接把中文页面的 Canonical 原封不动复制了过去。

这就制造了严重的逻辑冲突:

  1. hreflang 告诉爬虫:/en/post-1 是独立的英文翻译内容,请针对英语国家展示。
  2. canonical 却告诉爬虫:/en/post-1 只是 /zh/post-1 的一份重复副本,请忽略这个英文 URL,一切权重以中文 URL 为准。

搜索引擎遇到这种相互掐架的指令,通常会选择相信 Canonical。爬虫会认为英文页面没有独立的索引价值,将其作为重复内容去重,英文版在 Google 搜索结果中要么消失,要么即使展示出来,链接也跳到了中文 URL,海外用户点进来看到一屏幕中文,跳出率高达 90% 以上。

正确的处理规则:每一个语言版本的页面,其 Canonical 标签必须指向它自己当前的绝对规范 URL,绝对不能跨语种互指:

在 /zh/post-1 上:
<link rel="canonical" href="https://example.com/zh/post-1" />

在 /en/post-1 上:
<link rel="canonical" href="https://example.com/en/post-1" />

Canonical 负责语种内部的 URL 规范化(去除多余的追踪参数、SessionID 等),而 hreflang 负责语种之间的平行映射,两者的职责范围不能交叉混淆。

语言代码格式乱写,把英国英语配置成了乌克兰语

排查 hreflang 错误时,还抓出了一堆离谱的语言代码错误。

标准规范要求语言代码遵循 ISO 639-1(两位小写字母),区域代码遵循 ISO 3166-1 Alpha 2(两位大写字母)或者 ISO 15924 文字代码。

当时在代码仓库里搜出好几种违规写法:

  1. 下划线连接:写成了 en_US 或 zh_CN。HTML 标准里属性值必须使用连字符连接,写下划线会导致解析器直接报错跳过。
  2. 误用国别代码当语言代码:最典型的错误是给英国版页面标注 hreflang="uk"。英国的国别域名是 .uk,但在 ISO 639-1 中,uk 代表的是乌克兰语。这直接导致英国本地搜索搜不出英文页面,乌克兰语搜索里却冒出了英文版。面向英国的英语页面应当写成 en-GB。
  3. 中文简繁体的代码混乱:很多站长习惯写 zh-CN 和 zh-TW。如果主要区分的是简体与繁体中文,使用 zh-Hans(简体中文)与 zh-Hant(繁体中文)在覆盖面上更清晰,能同时兼顾新加坡、马来西亚等同样使用简体中文的地区,以及港澳台地区。

整理一份常见的多语言标准代码对照:

  • 英语(全球通用):en
  • 英语(美国):en-US
  • 英语(英国):en-GB
  • 简体中文(全球):zh-Hans
  • 繁体中文(全球):zh-Hant
  • 日语:ja
  • 德语:de
  • 西班牙语:es

代码中的任何拼写和大小写不严谨,都会让爬虫在解析语种矩阵时丢弃整条映射记录。

非 HTML 资源与性能优化时,漏掉了 HTTP 响应头的 Link 声明

很多人只知道在 HTML 的 <head> 里放 <link> 标签,但在实际业务中,多语言站点往往包含大量的跨语种 PDF 白皮书、使用手册、技术规格书,或者通过 REST API 分发的文档内容。

这些非 HTML 格式的静态资源根本没有 <head> 节点,爬虫抓取英文版手册和中文版手册时,完全无法获知两者之间的多语言对照关系。

排查过程中我们把这一层逻辑直接移到了 Nginx 的 HTTP 响应头上。通过在响应头中输出 Link 标头,既可以支持 PDF 等二进制静态文件,还能省去在长 HTML 页面头部塞入几十行标签的开销。

在 Nginx 中针对多语言静态下载目录的配置示例:

location ~* ^/(zh|en)/docs/(.+\.pdf)$ {
    set $doc_path $2;
    add_header Link '<https://example.com/zh/docs/$doc_path>; rel="alternate"; hreflang="zh-Hans", <https://example.com/en/docs/$doc_path>; rel="alternate"; hreflang="en"' always;
}

这样当爬虫下载 https://example.com/en/docs/manual.pdf 时,响应头里就明确带有指向中文版对应文件的双向标记:

HTTP/1.1 200 OK
Content-Type: application/pdf
Link: <https://example.com/zh/docs/manual.pdf>; rel="alternate"; hreflang="zh-Hans", <https://example.com/en/docs/manual.pdf>; rel="alternate"; hreflang="en"

排查与上线前验证命令

改造完成后,不能只用肉眼看页面,必须用命令行模拟各种爬虫环境验证。

  1. 验证 Googlebot 抓取中文版是否保持 200 OK,严防被 302 弹走:
curl -I -A "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.71 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" https://example.com/zh/article-1

必须确认第一行返回 HTTP/1.1 200 OK,若出现 HTTP/1.1 302 Found 且 Location 指向英文版,说明分流逻辑依然在误杀爬虫。

  1. 检查 HTML 中的 hreflang 闭环与 Canonical 自身对应:
curl -sL https://example.com/en/article-1 | grep -E '(hreflang|canonical)'

检查输出里是否包含指向自身及所有翻译版的绝对路径,且 Canonical 属性与当前访问的 URL 完全一致。

把这几处 Nginx 规则、标签互引以及响应头修正上线后,大概过了两周,Google Search Console 里的未编入索引曲线逐步平缓,中文和英文页面开始各就各位,收录量和展示量也恢复了正常的爬升通道。多语言站点在做分流设计时,技术细节上的克制与规范,往往比盲目追求全自动化的花哨功能更加稳妥。

评论一下?

OωO
取消