hreflang 常见失效原因:缺 self-reference 和 x-default 指错

0 阅读3分钟

hreflang 常见失效原因:缺 self-reference 和 x-default 指错

hreflang 出问题,先别急着改内容。先查两件事:当前页有没有指向自己的 hreflang;x-default 指向的 URL 是不是真正的默认页或语言选择页。这两个错误会让整组 hreflang 被忽略或选错页面。

1. self-reference 是什么

每个语言版本都必须包含指向自己的 hreflang。英文页要列英文页自己,中文页要列中文页自己。缺了 self-reference,搜索引擎可能不把这组链接当成完整集群。

错误示例:英文页只列中文和日文,不列英文。

<link rel='alternate' hreflang='zh-CN' href='https://example.com/zh/post/' />
<link rel='alternate' hreflang='ja' href='https://example.com/ja/post/' />

正确示例:

<link rel='alternate' hreflang='en-US' href='https://example.com/en/post/' />
<link rel='alternate' hreflang='zh-CN' href='https://example.com/zh/post/' />
<link rel='alternate' hreflang='ja' href='https://example.com/ja/post/' />
<link rel='alternate' hreflang='x-default' href='https://example.com/' />

注意:x-default 不要随手写成当前英文页,除非它真的是默认选择页。

2. x-default 指错的三种情况

  • 指到 404 或重定向链:爬虫跟过去拿不到 200,整组信号变弱。
  • 指到某个语言版本,但那个版本不是默认页,也不是语言选择页。
  • 每个语言页都把自己的 URL 写成 x-default,导致同一组 hreflang 里出现多个不一致的 x-default。

正确做法:同一组 hreflang 里 x-default 只出现一次,并且所有语言页都指向同一个默认 URL。没有可靠的默认 URL 时,宁可不输出 x-default,也不要乱指。

3. 用 Python 脚本查当前页

保存为 check_hreflang.py,传入一个或多个 URL。脚本只检查当前页,不递归。

import sys
import urllib.request
from html.parser import HTMLParser
from urllib.parse import urljoin


class HreflangParser(HTMLParser):
    def __init__(self):
        super().__init__()
        self.links = []

    def handle_starttag(self, tag, attrs):
        if tag.lower() != 'link':
            return
        d = dict(attrs)
        if d.get('rel', '').lower() == 'alternate' and d.get('hreflang'):
            self.links.append((d['hreflang'].lower(), d.get('href', '')))


def fetch(url):
    req = urllib.request.Request(url, headers={'User-Agent': 'hreflang-check/1.0'})
    with urllib.request.urlopen(req, timeout=15) as r:
        return r.geturl(), r.read().decode('utf-8', 'replace')


def check(url):
    final, html = fetch(url)
    p = HreflangParser()
    p.feed(html)
    links = [(code, urljoin(final, href)) for code, href in p.links]
    codes = [code for code, _ in links]
    norm = lambda u: u.rstrip('/')

    print('URL:', final)
    for code, href in links:
        print(' ', code, href)

    if not links:
        print('FAIL: 没有 hreflang')
        return

    if any(norm(href) == norm(final) for _, href in links):
        print('OK: self-reference')
    else:
        print('FAIL: 缺 self-reference')

    if 'x-default' not in codes:
        print('FAIL: 缺 x-default')
    elif codes.count('x-default') > 1:
        print('FAIL: x-default 重复')
    else:
        print('OK: 有 x-default')


if __name__ == '__main__':
    for u in sys.argv[1:]:
        check(u)

运行:

python3 check_hreflang.py https://example.com/en/post/

4. WordPress 里输出带 self-reference 的 hreflang

在 wp_head 输出。当前页 URL 一定要进 $urls,这就是 self-reference。x-default 没有可靠来源时不输出。

add_action('wp_head', function () {
    if (!is_singular()) {
        return;
    }

    $post_id = get_queried_object_id();
    $current = get_permalink($post_id);
    if (!$current) {
        return;
    }

    $current_lang = get_post_meta($post_id, '_aum_lang', true);
    if (!$current_lang) {
        $current_lang = 'zh-CN';
    }

    $urls = [];
    $urls[$current_lang] = $current;

    foreach (['zh-CN', 'en-US', 'ja'] as $code) {
        if ($code === $current_lang) {
            continue;
        }
        $url = get_post_meta($post_id, '_aum_translation_url_' . $code, true);
        if ($url) {
            $urls[$code] = $url;
        }
    }

    $x_default = get_post_meta($post_id, '_aum_translation_url_x-default', true);
    if (!$x_default) {
        $default_lang = 'zh-CN';
        if (isset($urls[$default_lang])) {
            $x_default = $urls[$default_lang];
        }
    }

    foreach ($urls as $code => $url) {
        printf(
            "<link rel='alternate' hreflang='%s' href='%s' />" . PHP_EOL,
            esc_attr($code),
            esc_url($url)
        );
    }

    if ($x_default) {
        printf(
            "<link rel='alternate' hreflang='x-default' href='%s' />" . PHP_EOL,
            esc_url($x_default)
        );
    }
});

这里有两个点:

  1. $urls[$current_lang] = $current 保证当前页包含自己。
  2. $x_default 只在有可靠来源时输出;没有就跳过,避免把 x-default 指错。

5. 修复后的验收清单

  • 每个语言页都包含指向自己的 hreflang。
  • 同一组页面里的 hreflang 集合一致,不要有的页多一个、有的页少一个。
  • x-default 只有一个,且所有语言页指向同一个 URL。
  • 所有 hreflang URL 都是绝对地址,返回 200,不经过重定向链。
  • self-reference 的 URL 要和 canonical 一致。
  • 不要用 JavaScript 动态插入 hreflang,爬虫可能看不到。
  • 改完用上面的脚本逐页跑一遍,不要只查首页。

如果不想手写这套输出逻辑,AumLang 是免费插件,做 AI 翻译、hreflang 和 WooCommerce 商品页翻译。插件页:aumcreate.com/plugins/aum…