Python, Technical SEO
hreflang XML-Sitemaps mit Python selbst generieren
Eine automatisierte hreflang-Integration im html-head gilt als Standard unter den möglichen Implementierungsformen und bietet auch für SEO Verantwortliche die allerbesten Auswertungs- und Kontrollmöglichkeiten. Dennoch gibt es häufig Situationen in denen eine manuelle Erstellung die effizienteste und schnellste (Überbrückungs-)Lösung sein kann. Insbesondere dann, wenn wenig IT-Ressourcen zur Verfügung stehen. Nachfolgend möchte ich zeigen, wie man sich hreflang-Sitemaps mit Python einfach selbst erstellen kann.

Python Script zur Generierung der hreflang XML-Sitemap
import csv
from xml.etree.ElementTree import Element, SubElement, ElementTree, tostring
from xml.dom import minidom
def prettify(elem):
rough_string = tostring(elem, 'utf-8')
reparsed = minidom.parseString(rough_string)
return reparsed.toprettyxml(indent=" ")
def split_large_sitemaps(root, threshold):
count = len(root.findall('url'))
sitemaps = []
if count > threshold:
while len(root):
new_root = Element('urlset', {
'xmlns': 'https://www.sitemaps.org/schemas/sitemap/0.9',
'xmlns:xhtml': 'https://www.w3.org/1999/xhtml'
})
for _ in range(threshold):
if len(root):
url_element = root[0]
root.remove(url_element)
new_root.append(url_element)
sitemaps.append(new_root)
return sitemaps
else:
return [root]
def generate_sitemap_from_csv(csv_file, threshold=20000):
urlset = Element('urlset', {
'xmlns': 'https://www.sitemaps.org/schemas/sitemap/0.9',
'xmlns:xhtml': 'https://www.w3.org/1999/xhtml'
})
with open(csv_file, mode='r', encoding='utf-8') as file:
reader = csv.reader(file)
headers = next(reader)
for row in reader:
for idx, url in enumerate(row):
if url:
url_element = SubElement(urlset, 'url')
loc_element = SubElement(url_element, 'loc')
loc_element.text = url
alternate_count = 0
for sub_idx, alternate_url in enumerate(row):
if alternate_url and idx != sub_idx:
xhtml_element = SubElement(url_element, 'xhtml:link', {
'rel': 'alternate',
'hreflang': headers[sub_idx],
'href': alternate_url
})
alternate_count += 1
if alternate_count:
xhtml_element = SubElement(url_element, 'xhtml:link', {
'rel': 'alternate',
'hreflang': headers[idx],
'href': url
})
else:
urlset.remove(url_element)
sitemaps = split_large_sitemaps(urlset, threshold)
return sitemaps
sitemaps = generate_sitemap_from_csv('hreflang-data.csv')
for idx, sitemap in enumerate(sitemaps):
sitemap_xml = prettify(sitemap)
with open(f'output/hreflang_sitemap_{idx + 1}.xml', 'w', encoding='utf-8') as file:
file.write(sitemap_xml)
print(f"{len(sitemaps)} Sitemaps wurden generiert!")
Wesentliche Funktionalitäten des Skripts
Das Herzstück des Skripts ist die Funktion generate_sitemap_from_csv.
Diese Funktion durchläuft im wesentlichen folgende Schritte:
- Sie öffnet und liest die angegebene CSV-Datei.
- Sie erstellt für jede URL in der CSV ein url-Element in der XML-Sitemap.
- Sie fügt zu jedem url-Element xhtml:link-Elemente hinzu, welche die hreflang-Zuordnungen für die jeweilige URL repräsentieren. Extrahiert wird der jeweilige Sprach- bzw. Ländercode aus der Überschrift der jeweiligen Spalte.
- Wenn die Gesamtzahl der URLs einen bestimmten Schwellenwert überschreiten, teilt das Skript die Sitemap in mehrere kleinere Sitemaps auf und nummeriert diese durch. Der Schwellwert kann beliebig angepasst werden. Als Höchstwert gibt Google 50.000 URL-Einträge an. Standard im Skript sind 20.000.
- Die Sitemaps werden korrekt formatiert und mit der entsprechenden XML-Deklaration
xml version="1.0" encoding="UTF-8"und dem XML-Namespace-Attributxmlns:xhtml="http://www.w3.org/1999/xhtmlversehen. - Abschließend wird jede generierte Sitemap als separate XML-Datei im
output-Ordner gespeichert.
Struktur der CSV Datei und URL-Mapping
Zur Erstellung der CSV Datei ist ein wenig manuelle Vorarbeit nötig: dafür werden sämtliche Länderäquivalente aus einem Crawl auf Zeilenbasis gemappt. Die jeweilige Spaltenüberschrift gibt den entsprechenden Länder- und/oder Sprachcode an, welcher direkt für die Angabe im hreflang-Attribut verwendet wird. Sollte es für spezifische Länder keine URL geben, wird das Feld einfach leer gelassen. Generiert wird der hreflang-Verbund nur, wenn mindestens zwei URLs für einen bidirektionalen Verweis vorhanden sind. Eine einzelne URL wird übersprungen.
de-de,de-at,de-ch
https://example.de,https://example.at,https://example.ch
https://example.de/url-1,https://example.at/url-1,https://example.ch/url-1
https://example.de/url-2,https://example.at/url-2,https://example.ch/url-2
https://example.de/url-3,https://example.at/url-3,https://example.ch/url-3
https://example.de/url-4,,https://example.ch/url-4
https://example.de/url-5,,
XML-Output
<?xml version="1.0" ?>
<urlset xmlns="https://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="https://www.w3.org/1999/xhtml">
<url>
<loc>https://example.de</loc>
<xhtml:link rel="alternate" hreflang="de-at" href="https://example.at"/>
<xhtml:link rel="alternate" hreflang="de-ch" href="https://example.ch"/>
<xhtml:link rel="alternate" hreflang="de-de" href="https://example.de"/>
</url>
<url>
<loc>https://example.at</loc>
<xhtml:link rel="alternate" hreflang="de-de" href="https://example.de"/>
<xhtml:link rel="alternate" hreflang="de-ch" href="https://example.ch"/>
<xhtml:link rel="alternate" hreflang="de-at" href="https://example.at"/>
</url>
<url>
<loc>https://example.ch</loc>
<xhtml:link rel="alternate" hreflang="de-de" href="https://example.de"/>
<xhtml:link rel="alternate" hreflang="de-at" href="https://example.at"/>
<xhtml:link rel="alternate" hreflang="de-ch" href="https://example.ch"/>
</url>
<url>
<loc>https://example.de/url-1</loc>
<xhtml:link rel="alternate" hreflang="de-at" href="https://example.at/url-1"/>
<xhtml:link rel="alternate" hreflang="de-ch" href="https://example.ch/url-1"/>
<xhtml:link rel="alternate" hreflang="de-de" href="https://example.de/url-1"/>
</url>
<url>
<loc>https://example.at/url-1</loc>
<xhtml:link rel="alternate" hreflang="de-de" href="https://example.de/url-1"/>
<xhtml:link rel="alternate" hreflang="de-ch" href="https://example.ch/url-1"/>
<xhtml:link rel="alternate" hreflang="de-at" href="https://example.at/url-1"/>
</url>
<url>
<loc>https://example.ch/url-1</loc>
<xhtml:link rel="alternate" hreflang="de-de" href="https://example.de/url-1"/>
<xhtml:link rel="alternate" hreflang="de-at" href="https://example.at/url-1"/>
<xhtml:link rel="alternate" hreflang="de-ch" href="https://example.ch/url-1"/>
</url>
<url>
<loc>https://example.de/url-2</loc>
<xhtml:link rel="alternate" hreflang="de-at" href="https://example.at/url-2"/>
<xhtml:link rel="alternate" hreflang="de-ch" href="https://example.ch/url-2"/>
<xhtml:link rel="alternate" hreflang="de-de" href="https://example.de/url-2"/>
</url>
<url>
<loc>https://example.at/url-2</loc>
<xhtml:link rel="alternate" hreflang="de-de" href="https://example.de/url-2"/>
<xhtml:link rel="alternate" hreflang="de-ch" href="https://example.ch/url-2"/>
<xhtml:link rel="alternate" hreflang="de-at" href="https://example.at/url-2"/>
</url>
<url>
<loc>https://example.ch/url-2</loc>
<xhtml:link rel="alternate" hreflang="de-de" href="https://example.de/url-2"/>
<xhtml:link rel="alternate" hreflang="de-at" href="https://example.at/url-2"/>
<xhtml:link rel="alternate" hreflang="de-ch" href="https://example.ch/url-2"/>
</url>
<url>
<loc>https://example.de/url-3</loc>
<xhtml:link rel="alternate" hreflang="de-at" href="https://example.at/url-3"/>
<xhtml:link rel="alternate" hreflang="de-ch" href="https://example.ch/url-3"/>
<xhtml:link rel="alternate" hreflang="de-de" href="https://example.de/url-3"/>
</url>
<url>
<loc>https://example.at/url-3</loc>
<xhtml:link rel="alternate" hreflang="de-de" href="https://example.de/url-3"/>
<xhtml:link rel="alternate" hreflang="de-ch" href="https://example.ch/url-3"/>
<xhtml:link rel="alternate" hreflang="de-at" href="https://example.at/url-3"/>
</url>
<url>
<loc>https://example.ch/url-3</loc>
<xhtml:link rel="alternate" hreflang="de-de" href="https://example.de/url-3"/>
<xhtml:link rel="alternate" hreflang="de-at" href="https://example.at/url-3"/>
<xhtml:link rel="alternate" hreflang="de-ch" href="https://example.ch/url-3"/>
</url>
<url>
<loc>https://example.de/url-4</loc>
<xhtml:link rel="alternate" hreflang="de-ch" href="https://example.ch/url-4"/>
<xhtml:link rel="alternate" hreflang="de-de" href="https://example.de/url-4"/>
</url>
<url>
<loc>https://example.ch/url-4</loc>
<xhtml:link rel="alternate" hreflang="de-de" href="https://example.de/url-4"/>
<xhtml:link rel="alternate" hreflang="de-ch" href="https://example.ch/url-4"/>
</url>
</urlset>