Wenn Sie Preise, Inhalte oder Verfügbarkeit über mehrere Märkte hinweg vergleichen, besteht das erste Problem darin, dieselbe Seite in jedem Markt zu finden. Das Erraten von URL-Mustern funktioniert bei manchen Sites, scheitert aber bei den meisten: Eine Site nutzt /de/, eine andere de.example.com, eine weitere einen Query-Parameter und eine vierte eine eigene Domain pro Land.
Viele Sites veröffentlichen die Antwort bereits. Die hreflang-Annotation, die Sites für Suchmaschinen hinzufügen, listet jede Sprach- und Länderversion einer Seite samt Fundort auf. Liest man sie aus, liefert ein einziger Abruf die gesamte Menge. Dieser Leitfaden erklärt, wie das funktioniert, was wir bei der Prüfung der Nutzung durch Top-Sites festgestellt haben und warum der Besitz der richtigen URL nicht immer ausreicht, um die richtige Seite zu sehen.
Die wichtigsten Erkenntnisse
- Am 1. Oktober 2026 gaben 82 von 253 abgerufenen Top-Site-Startseiten (32 %) hreflang-Alternates an, mit einem Median von 17,5 Versionen pro Seite und bis zu 157.
- Ein einziger Abruf einer annotierten Seite liefert jede lokalisierte URL dieser Seite, ohne dass URL-Muster erraten werden müssen.
- Die Annotationen sind nicht immer sauber: 12 der 82 Sites verwendeten mindestens einen Code, den Google nicht unterstützt, etwa
en-uk,es-419,en_GBmit Unterstrich oder das Land vor der Sprache. - 86 % der von uns geprüften Alternates verlinkten direkt zurück, wie von Google gefordert. Die meisten übrigen verwiesen auf eine andere URL für dieselbe Seite oder leiteten um.
- Manche Versionen öffnen sich nur vom richtigen Ort aus. Die US-Startseite einer Nachrichtenseite und die deutsche Storefront eines Marktplatzes leiteten unsere Verbindung aus Rumänien anderswohin um und luden normal über Exits in den Vereinigten Staaten und in Deutschland.
Wie hreflang funktioniert
Eine Seite listet ihre Alternates in ihrem <head>:
<link rel="alternate" hreflang="en" href="https://example.com/" />
<link rel="alternate" hreflang="de" href="https://example.com/de/" />
<link rel="alternate" hreflang="pt-BR" href="https://example.com/pt/" />
<link rel="alternate" hreflang="x-default" href="https://example.com/" />
Jeder Wert ist ein Sprachcode, gegebenenfalls gefolgt von einer Schrift oder einer Region: de für Deutsch überall, en-GB für Englisch im Vereinigten Königreich, zh-Hant für traditionelles Chinesisch. x-default benennt den Fallback für Besucher, die zu keinem der Codes passen. Googles Dokumentation legt die Regeln fest, an die sich die meisten Sites halten:
- Sprachen nutzen ISO-639-1-Codes, Schriften ISO 15924 und Regionen ISO 3166-1 Alpha-2. Eine Region allein ist nicht gültig, und Codes außerhalb dieser Standards, etwa
es-419für lateinamerikanisches Spanisch, werden nicht unterstützt. - URLs müssen vollständig qualifiziert sein, einschließlich
https://. - Jede Version muss sich selbst und jede andere Version auflisten, und wenn zwei Seiten nicht aufeinander verweisen, können die Annotationen ignoriert werden.
- Dieselbe Information kann im HTML, in einem HTTP-
Link-Header (nützlich für PDFs) oder in einer Sitemap angegeben werden.
Für die Datenerhebung ist die dritte Regel die nützliche. Da jede Version jede andere auflisten soll, ist jede einzelne von ihnen eine vollständige Karte der Menge.
Was wir bei Top-Sites gefunden haben
Wir haben die Startseiten der 500 führenden Domains im Tranco-Ranking populärer Sites abgerufen. 253 verschiedene Sites lieferten eine HTML-Startseite; der Rest waren Content-Netzwerke, API-Hosts und andere Domains ohne eigene Startseite, oder Duplikate einer bereits gezählten Site.
| Maßzahl | Ergebnis |
|---|---|
| Startseiten mit hreflang-Annotationen | 82 von 253 (32 %) |
| Alternates pro annotierter Seite | Median 17,5, Maximum 157 |
Seiten mit x-default | 63 von 82 |
Startseiten mit hreflang in einem HTTP-Link-Header | 0 |
| Sites mit mindestens einem nicht unterstützten Code | 12 von 82 |
| Sites mit relativen URLs | 2 |
| Sites, die denselben Code doppelt aufführen | 6 |
Die nicht unterstützten Codes folgten einigen Mustern:
| Muster | Beispiel | Sites |
|---|---|---|
| Land vor Sprache, plus regionale Bezeichnungen | mx-es, cz-cs, emea_africa-en | 1 (48 Codes) |
| Unterstrich statt Bindestrich | en_GB, de_DE | 1 (25 Codes) |
| Regionscode, der nicht ISO 3166-1 ist | en-uk, en-eu, sq-xk | 3 |
| Lateinamerikanisches Spanisch | es-419 | 2 |
| Sprachcode, der nicht ISO 639-1 ist | ceb, skr, das veraltete iw für Hebräisch | 3 |
| Erfundene Codes | tc, zh-FT, ms-en | 3 |
Einige Sites erscheinen in mehr als einer Zeile. Das Muster „Land zuerst“ ist für einen Parser am trügerischsten, da mehrere dieser Codes zufällig in umgekehrter Reihenfolge gültig sind: ca ist der Code für Katalanisch, id für Indonesisch und th für Thailändisch, sodass ca-en als Katalanisch statt als Englisch für Kanada gelesen wird. Leiten Sie niemals einen Markt allein aus dem Code ab, ohne zu prüfen, ob er Sinn ergibt.
Rücklinks
Wir haben aus jeder annotierten Startseite bis zu zwei Alternates entnommen, insgesamt 155, sie abgerufen und geprüft, ob jede die Startseite zurück verlinkt.
| Ergebnis | Alternates |
|---|---|
| Direkt zurückverlinkt | 133 (86 %) |
| Zurückverlinkt auf eine andere URL für dieselbe Seite | 9 |
| Woanders hin umgeleitet | 9 |
| Hat die Seite tatsächlich nicht aufgeführt | 4, bei zwei Sites |
Die zweite Zeile ist die subtile. Bei mehreren Sites lag die Startseite, auf der wir landeten, unter einer URL, während die Menge eine andere benannte: / gegenüber /en/, /homepage oder /home.html, oder eine bloße URL gegenüber einer mit Sprachparameter. Die Annotationen waren konsistent; unser Einstiegspunkt war einfach nicht die URL, die die Site als kanonisch betrachtet. Beim Sammeln sollten Sie jede Version nach der URL indexieren, die die Menge selbst verwendet, nicht nach der, bei der Sie zufällig gelandet sind.
Die richtige URL reicht nicht immer aus
Die Umleitungen waren der aufschlussreichste Teil. Wir haben jeden umleitenden Alternate erneut abgerufen, direkt und über Shifter-Exits im Zielland, mit Englisch und mit lokalen Accept-Language-Headern:
| Sitetyp | Alternate | Aus Rumänien | Aus dem Zielland | Entschieden durch |
|---|---|---|---|---|
| Nachrichtenseite | US-Startseite | Umgeleitet auf die internationale Ausgabe | Geladen, über einen US-Exit | Standort |
| Marktplatz | Deutsche Storefront | Umgeleitet auf die globale Site | Geladen, über einen deutschen Exit | Standort |
| Videotelefonie-Anbieter | Japanische Startseite | Lud nur mit japanischem Accept-Language | Dasselbe, über einen japanischen Exit | Sprache |
| Spieleverlag | Arabische Startseite | Umgeleitet | Umgeleitet, über einen saudischen Exit | Weder noch: gelistet, aber nicht erreichbar |
Zwei Sites entschieden nach dem Standort des Besuchers, der Marktplatz unabhängig von der Sprache, sodass deren eigene Annotationen auf Seiten verwiesen, die ein Besucher von anderswo nicht öffnen konnte. Eine entschied nach Sprache, unabhängig vom Standort. Und eine listete eine Version, die in jeder von uns getesteten Kombination umleitete, was man wissen sollte, bevor man darauf einen Marktvergleich aufbaut.
Hier treffen sich hreflang und Exit-Standort. Die Annotationen sagen Ihnen, welche Versionen existieren und wo; sie getreu zu erfassen bedeutet, jede Version so anzufragen, wie es ein lokaler Besucher täte, aus diesem Land und mit dieser Sprache, wie in Abgleich von Proxy-Geo, Zeitzone und Locale behandelt. Andernfalls riskieren Sie, die internationale Version unter einem deutschen Label zu erfassen.
Der Code
Das folgende Modul liest die HTML-Alternates einer Seite aus, markiert nicht unterstützte Codes und relative URLs und prüft Rücklinks, wobei es für jeden fehlschlagenden Alternate meldet, warum er fehlschlägt:
from html.parser import HTMLParser
from urllib.parse import urljoin, urlsplit, urlunsplit
import requests
from babel import Locale
# Google akzeptiert ISO-639-1-Sprachen, ISO-15924-Schriften und ISO-3166-1-Alpha-2-Regionen.
LANGUAGES = {code for code in Locale("en").languages if len(code) == 2}
NOT_ISO_3166 = {"AC", "CP", "CQ", "DG", "EA", "EU", "EZ", "IC", "QO", "TA", "UN", "XA", "XB", "XK", "ZZ"}
REGIONS = {code for code in Locale("en").territories if code.isalpha()} - NOT_ISO_3166
SCRIPTS = set(Locale("en").scripts)
class AlternateLinks(HTMLParser):
"""Collect <link rel="alternate" hreflang="..."> elements from a page's markup."""
def __init__(self):
super().__init__()
self.links = []
def handle_starttag(self, tag, attrs):
a = dict(attrs)
if tag == "link" and "alternate" in (a.get("rel") or "").lower().split() and a.get("hreflang") and a.get("href"):
self.links.append((a["hreflang"].strip(), a["href"].strip()))
def valid_hreflang(code):
"""True for x-default, a language, or language-region / language-script(-region) codes."""
if code.lower() == "x-default":
return True
parts = code.split("-")
if parts[0].lower() not in LANGUAGES:
return False
rest = parts[1:]
if rest and rest[0].title() in SCRIPTS:
rest = rest[1:]
if rest and rest[0].upper() in REGIONS:
rest = rest[1:]
return not rest
def hreflang_map(url, session=None, timeout=20):
"""Fetch a page and return its declared alternates as {hreflang: absolute URL}, plus any problems found."""
session = session or requests.Session()
response = session.get(url, timeout=timeout)
parser = AlternateLinks()
parser.feed(response.text) # link tags are normally ASCII, so the decoding choice rarely matters here
alternates, problems = {}, []
for code, href in parser.links:
if not href.startswith(("http://", "https://")):
problems.append(f"relative URL for {code}: {href}")
if not valid_hreflang(code):
problems.append(f"invalid code: {code}")
alternates[code] = urljoin(response.url, href)
return response.url, alternates, problems
def normalise(url):
"""Compare URLs without default ports, host case or a trailing slash getting in the way."""
parts = urlsplit(url)
port = f":{parts.port}" if parts.port and parts.port not in (80, 443) else ""
return urlunsplit((parts.scheme.lower(), (parts.hostname or "") + port, parts.path.rstrip("/") or "/", parts.query, ""))
def check_return_links(url, alternates, session=None, timeout=20):
"""Fetch each alternate and report why it breaks the return-link rule. An empty result means all link back."""
session = session or requests.Session()
target = normalise(url)
problems = {}
for code, alternate in alternates.items():
if normalise(alternate) == target:
continue
final, back, _ = hreflang_map(alternate, session, timeout)
if normalise(final) != normalise(alternate):
problems[code] = f"redirects to {final}"
elif not back:
problems[code] = "has no hreflang annotations"
elif target not in {normalise(u) for u in back.values()}:
problems[code] = "does not link back"
return problems
Ausführung gegen unsere eigene Startseite:
import requests
from hreflang import hreflang_map, check_return_links
session = requests.Session()
session.headers["User-Agent"] = "ExampleCollector/1.0 (+https://example.com/bot)"
url, alternates, problems = hreflang_map("https://shifter.io/", session)
for code, alternate in sorted(alternates.items()):
print(code, alternate)
print("problems:", problems)
print("return-link problems:", check_return_links(url, alternates, session))
de https://shifter.io/de
en https://shifter.io/
es https://shifter.io/es
fr https://shifter.io/fr
ja https://shifter.io/ja
ko https://shifter.io/ko
pt-BR https://shifter.io/pt
x-default https://shifter.io/
zh-Hans https://shifter.io/cn
problems: []
return-link problems: {}
Zwei Grenzen sollte man kennen. Der Code liest Annotationen nur im HTML; Sites, die sie in einem HTTP-Header oder einer Sitemap veröffentlichen, erfordern, dass auch diese ausgelesen werden, und Sitemaps als Erkennungsquelle behandelt die Sitemap-Seite davon. Und check_return_links ruft jeden ihm übergebenen Alternate ab, also sollten Sie bei einer Seite mit 150 Versionen eine Stichprobe nehmen oder die Anfragen zeitlich strecken.
In der Praxis einsetzen
- Beginnen Sie mit einer Seite pro Vorlage. Ein Produkt, eine Kategorie und eine Artikelseite teilen sich meist ein Annotationsmuster, sodass ein Abruf jeder dieser Seiten zeigt, wie die gesamte Site ihre Märkte abbildet.
- Indexieren Sie Versionen nach den eigenen URLs der Menge. Speichern Sie die URL, die jeder Alternate angibt, und behandeln Sie Umleitungen als Befunde statt als Rauschen.
- Rufen Sie jede Version wie ein lokaler Besucher ab. Nutzen Sie einen Exit im jeweiligen Markt und dessen Sprache, und prüfen Sie dann, ob Sie nicht umgeleitet wurden. Der Standort entschied in zwei unserer vier Umleitungsfälle, wie auch in welche Länder am häufigsten geoblockt werden.
- Validieren Sie Codes, bevor Sie ihnen vertrauen. Ein vertauschter oder erfundener Code kann eine Seite unbemerkt dem falschen Markt zuordnen.
- Normalisieren Sie, was Sie erfassen. Sobald Sie jede Version haben, unterscheiden sich Preise, Daten und Zahlen weiterhin im Format, wie in Normalisierung von Preisen, Zahlen und Daten über Locales hinweg behandelt, und der Vergleich selbst ist Thema von Erkennung geo-personalisierter Preisgestaltung.
Fazit
hreflang kommt dem nahesten, was das Web an einem veröffentlichten Index der Märkte einer Seite besitzt. Ein Drittel der Top-Sites stellt es bereit, ein einziger Abruf offenbart die gesamte Menge, und der größte Teil der Menge verlinkt sich wie vorgesehen untereinander.
Es ist eine Karte, keine Garantie. Codes können falsch sein, die URL, auf der Sie landen, ist möglicherweise nicht die, die die Menge verwendet, und manche Versionen öffnen sich nur für Besucher am richtigen Ort oder mit der richtigen Sprache. Lesen Sie die Annotationen, validieren Sie sie, und rufen Sie jede Version so ab, wie es ein lokaler Besucher täte.
Quellen und Referenzen
- Google Search Central, Tell Google about localized versions of your page.
- Tranco, Liste Q2K34, die Domain-Rankings vom 1. bis 30. September 2026 aggregiert.
- Babel, dessen CLDR-Daten die Sprach-, Schrift- und Regionslisten im Code liefern.
- Startseiten und Alternates, abgerufen von Shifter am 1. Oktober 2026, direkt und über Shifter-Exits, mit dem oben genannten Code.