Redirects für den Relaunch automatisiert erstellen

Mit Screaming Frogs SEO Spiders Custom Javascript, ChatGPT und Python — So gelingt es wirklich

Seit dem Screaming Frog die Version 20 des SEO Spiders veröffentlicht hat häufen sich die Artikel darüber, wie man mit Hilfe von Custom JavaScript so wie der ChatGPT API Vector- Embeddings erzeugen kann. Dadurch lässt sich die Cosinus-Ähnlichkeit berechnen. Das ist wiederum die Grundlage, um URL Mappings und Weiterleitungslisten sogar teilweise automatisiert zu erstellen.

Eins haben diese Anleitungen für mich allerdings gemeinsam. In der Praxis funktionieren diese für mich nicht zufriedenstellend. Und warum das so ist, gucken wir uns jetzt an

Das Problem: Warum bestehende Ansätze scheitern

Ich bin zum ersten Mal auf die Möglichkeit gestoßen, das Erstellen von Weiterleitungen zu automatisieren, als ich auf LinkedIn einen Beitrag von Daniel Emery gelesen habe. In seinem Beitrag beschrieb er, wie er ein Python-Skript verwendet, um URLs basierend auf ähnlichen Inhalten zu matchen. Diese Erkenntnis war ein Aha-Erlebnis für mich und lieferte den ersten Impuls, an einer passenden Lösung zu arbeiten. Endlich gab es eine Möglichkeit, diese lästige und mühsame SEO-Aufgabe zu automatisieren. Leider gab es einen Haken und der sah so aus:

In der Theorie funktioniert es so: Ich crawle mit Hilfe des Screaming Frog SEO Spiders die Testumgebung der neuen Website und die alte Website. Beim Crawl wird dann der Inhalt der Seite über die OpenAI Embeddings API übermittelt, aus dem Inhalt werden Vector Embeddings erzeugt und zurück an den Frog übermittelt. Ich exportiere aus beiden Crawls jeweils die URLs und die Embeddings, wahlweise als Excel-Datei oder als CSV, dann nutze ich ein Python Skript in Google Colab, das jemand externes mithilfe von ChatGPT erstellt hat. Ich erhalte eine Liste mit passenden alten und neuen URLs, die ich manuell überprüfe. Nur um dann festzustellen, dass die meisten der “passenden” URLs gar nicht zu gebrauchen ist, weil sich der Großteil des Inhalts der Seiten doch gar nicht so sehr unterscheidet und ich es auch hätte gleich manuell machen können.

Vielleicht klappt es bei Blogs oder Websites mit einzelnen Seiten, die viel einzigartigen Inhalt haben. Bei großen Online-Shops mit mehreren hunderttausend Produkten und Produktvariationen, die alle über einzigartige URLs erreichbar sind und der einzigartige Inhalt, nämlich die Produktbeschreibung, nur einen kleinen, bzw. einen zu geringen Anteil des Gesamttextinhalts einer Seite ausmacht, oder sich manchmal auch nur Kleinigkeiten unterscheiden, reicht es eben nicht aus, einfach nur das Standart-Skript des Frogs zum Erstellen von Vector Embeddings zu nutzen. Wenn 50% des Inhalts einer Seite der Produkttext ist und die anderen 50% aus Navigations-, Footer- und Trust-Elementen und anderen Produktempfelungen besteht, funktioniert der Vergleich dieser Vector Embeddings nicht.

Weil mich das wahnsinnig gemacht hat und ich Weiterleitungslisten auch wirklich nicht mehr händisch erstellen wollte, bin ich der Sache nachgegangen und habe einen für mich passenden Weg gefunden, wie sich dieser Prozess automatisieren ließ.

Was sind Vector Embeddings

Weil das vielleicht eine Frage ist, die bis hier hin aufgekommen sein könnte, ein kurzer Abstecher zu dem Thema “Was sind Vector Embeddings?”. Ich kann dir dafür keine wissenschaftlich lupenreine Antwort geben, aber diese gibt es ja auch schon zu genüge, wie z. B. in diesem Artikel von Mike King.

Meine vereinfachte Antwort darauf ist: Vector Embeddings sind eine numerische Darstellung des Inhalts. Mein Lieblingsbeispiel dafür ist das Wort “umfahren”. Ein Wort mit zwei Bedeutungen. Entweder kann ich die Omi umfahren, oder ich kann die Omi umfahren. Nur eins davon geht gut aus.

Würden wir nur die Wörter mit einander vergleichen, würde es hier zu keinem passgenauen Ergebnis kommen. Beim Erstellen von Vector Embeddings berücksichtigen die LLMs (Large Language Models) allerdings auch den Kontext des Wortes zu den umliegenden Worten und dessen, was der Text aussagt.

Meine Helferlein

Ich habe zur Lösung des Problems ein paar Tools genutzt, auf die ich nur kurz einmal eingehen möchte:

Der Screaming Frog SEO Spider ist das Tool mit dem ich die Website gecrawlt habe und welches mir ermöglicht Inhalte zu extrahieren und auch APIs anzuschließen, wie in diesem Beispiel hier die OpenAI API, um aus den Inhalten einer Webseite Vector Embeddings zu erstellen.

Da ich kein Programmierer bin und nur ein grundlegendes Verständnis von Programmiersprachen habe, habe ich auf ChatGPT zurückgegriffen, um mir die Skripte, die ich brauchte, zu erstellen, herumzuprobieren und auszupfeilen. Um das Custom JavaScript für den Screaming Frog SEO Spider zu erstellen, gibt es den Custom GPT Kermit, dem die gesamte Dokumentation dieses Features bereits zur Verfügung steht. Wenn du eigene Custom JavaScript erstellen möchtest, probier’s selber mal aus.

Um Python Skripte ausführen zu können, vor allem die, die besonders rechenintensiv sind und wenn man keine eigene lokale Umgebung auf dem Rechner hat, bietet sich Google Colab an. Hier kann man einfach im eigenen Google Drive ein neues Colab Notebook erstellen und eigene Projekte ausprobieren. Für den Start kann man auch erstmal Notebooks anderer, wie z. B. meins weiter unten, ins eigene Google Drive kopieren und dort laufen lassen.

Der Weg zur Lösung

Das Ziel: So soll die Lösung am Ende aussehen

Da für mich die bereits existierenden Lösungen keine ausreichenden Ergebnisse geliefert haben, habe ich mir überlegt, was ich mir wünschen würde, wie das Ergebnis aussieht. Ganz am Ende möchte ich eine Excel-Liste haben, in der die URL der alten Seite drin steht mit einer zugeordneten Ziel-URL. Aber auch nur, wenn der Inhalt wirklich übereinstimmt. Reicht die Übereinstimmung nicht aus, möchte ich ein zweites mögliches Ergebnis in einer weiteren Spalte haben. In diesem Fall kann ich dann manuell nachprüfen und mich für eins der beiden passenden Ergebnisse entscheiden. Sollte es kein passendes Ergebnis geben, möchte ich auch kein Eintrag in der Liste. So erspare ich mir das mühsame Löschen von URLs der Startseite, das gerne, in meinen Tests, als Fallback mit einem Similarity Score von 10% als Ergebnis eingetragen wurde.
Der Geschichte etwas vorgegriffen, habe ich mich durch Ausprobieren darauf festgelegt, wenn es ein Ergebnis mit einem Similarity Score von über 90% gibt, brauche ich nur eine passende URL in der Liste. Sollte die Ähnlichkeit der Inhalte nicht groß genug sein, aber immer noch hoch, also für mich zwischen 80% und 90%, dann möchte ich die zwei Ergebnisse zur Auswahl haben, die die größte Übereinstimmung haben.

Hürde: An die passenden Similarity Cores kommen

Jetzt stand ich vor der Frage, wie erhalte ich so hohe Similarity Scores, wenn es, wie oben in meiner Problemstellung beschrieben, so viele generische Inhalte auf den Produktdetailseiten gibt? Ich muss die Auswahl des Inhalts auf die einzigartigen Elemente beschränken, bevor daraus Vector Embeddings erzeugt werden. Das Custom JavaScript zur Erstellung dieser Vector Embeddings im SF SEO Spider muss also um weiteren Code ergänzt werden, damit nur wesentliche Teile des Inhalts an die OpenAI API übermittelt werden.

Um mir das Custom JavaScript so anzupassen, dass es das tut, was ich möchte, habe ich mir mit dem Custom GPT Kermit das JavaScript dahingehend angepasst. Ich habe hier selber erstmal viel rumexperimentiert. Vorher hatte ich bereits über die JavaScript Library Readability.js von Mozilla gelesen, welches eine Standalone-Version der Readability-Library ist, die für den Firefox Reader View verwendet wird. Ich hatte gehofft, damit den Inhalt der Seite auf das wesentliche reduzieren zu können. Was jedoch nicht geklappt hat. ChatGPT und ich haben dann eine eigene Lösung gebaut, um in dem Skript irrelevante Inhalte, wie die der Navigation, des Footers, etc. mit Hilfe der HTML-Elemente auszuschließen. Diese Liste der auszuschließenden Elemente allerdings, wäre für jede Website elendig lang und jedes Mal anders und müsste angepasst werden. Erstens wäre diese Liste zu erstellen äußerst zeitintensiv und zweitens sind mir immer wieder Elemente durchgerutscht, die dann doch mit von der Website extrahiert wurden. Es umzudrehen, nämlich die Elemente einzuschließen, die von der Website extrahiert werden sollen, machte gleich viel mehr Sinn. Außerdem, um genauer auswählen zu können (auch auf älteren Webseites), wurde die Liste der einzuschließenden Elemente um HTML-IDs, -Klassen und Role-Attribute erweitert. Das sieht dann so aus:

// Select elements that typically contain relevant content
    const elementsToInclude = document.querySelectorAll(`
        main, article, section, 
        div.content, div.main, div.article, 
        div.post, div.entry, div.body, div#product-name,
        p, h1, h2, h3, h4, 
        [role="main"], [role="article"]

Dabei sind durch Punkt getrennte Elemente Klassen-Selektoren HTML-Element.klasse und durch Rauten getrennte Elemente ID-Selektoren HTML-Element#id.

Zielgerade: Der letzte Feinschliff

An dieser Stelle sei angemerkt, dass ich das alles erst einmal ausprobiert habe mit einer kleinen Liste an URLs und ich mir auch erst nur den Inhalt extrahiert habe, ohne die Embeddings zu erstellen. So war ich in der Lage die Ergebnisse der Extraktion beurteilen zu können, ob die korrekten Elemente extrahiert wurden.

Nachdem dieser Ansatz funktionierte, habe ich den extrahierten Text noch minifiziert, um Zeilen- und Seitenumbrüche, doppelte Leerzeichen und optional auch Bindestriche aus dem Text zu entfernen. So habe ich einen reinen Textblock erhalten, in dem ich nachvollziehen konnte, welche Elemente extrahiert wurden.

// Minify the extracted content by removing unnecessary characters and whitespace
    content = content
        .replace(/(\r\n|\n|\r)/g, ' ')    // Remove all line breaks and replace them with a space
        .replace(/\s+/g, ' ')             // Replace multiple spaces with a single space
        .replace(/—/g, '')                // Optional: Remove dashes (if needed)
        .replace(/\u000C/g, '');          // Remove any page breaks (form feed character)

Dann war es wichtig, weil die extrahierten Texte teilweise recht lang sein konnten und das Token-Limit der API überschreiten konnten, dass der Text in kleinere Abschnitte unterteilt wird, wenn der Inhalt zu lang ist.

// Function to handle the API request
function chatGptRequest(content) {
    if (new TextEncoder().encode(content).length > 8191) { // Checking byte length approximation for tokens
        // Function to break the string into chunks
        function chunkString(str, size) {
            const numChunks = Math.ceil(str.length / size);
            const chunks = new Array(numChunks);
            for (let i = 0, o = 0; i < numChunks; ++i, o += size) {
                chunks[i] = str.substring(o, o + size);
            }
            return chunks;
        }
        // Divide content into manageable chunks
        const chunks = chunkString(content, 8191);

Ziel und Treppchen: Aus 2 Excel-Dateien mach 1

Nachdem das Custom Javascript jetzt genau die Daten geliefert hat, die ich haben wollte, konnte ich in einem neuen Chat mit ChatGPT das Python erstellen, um aus den zwei Excel-Dateien, die ich aus den Screaming Frog Crawls habe, mir eine Excel-Datei zu liefern, in der die URLs der alten Website mit den URLs der neuen Website gematcht werden, sofern die Ähnlichkeiten groß genug sind.

Wichtig war mir hier, wie oben auch schon beschrieben, dass ich bei großer Übererstimmung von 90% oder größer das beste Ergebnis zugewiesen bekomme und sollte die Übereinstimmung zwischen 80% und 90% liegen soll in einer weiteren Spalte das zweitbeste Ergebnis zugewiesen werden. Wenn die Übereinstimmung unter 80% liegt, soll kein Ergebnis in die Spalten geschrieben werden, da diese meiner Erfahrung nach dann nicht mehr passend genug sind und in aller Regel sowieso überschrieben werden müssen. Das vereinfacht am Ende des Korrekturprozess für mich.

Außerdem wollte ich vor allem mit Excel-Dateien hantieren, da ich das Abspeichern und Importieren von CSV-Dateien vermeiden wollte und je nach verwendetem Tool die Separatoren auch unterschiedliche sind. Und ich wollte beim Similarity Score nur mit zwei Dezimalstellen arbeiten, weil auch das die Übersicht am Ende verbessert. Dafür wird im Python der Similarity Score auf zwei Dezimalstellen gerundet. Wichtig war hier ChatGPT darauf hinzuweisen, dass der Similarity Score erst nach dem Zuweisen gerundet wird, damit die gesamte Zahl im Zuweisungs-Prozess genutzt wird.

Um das Skript ausführen zu können, nutze ich ein Notebook in Google Colab.
Nach dem Ausführen des Skripts, wird am Ende eine Excel Tabelle heruntergeladen, die in etwa so aussieht:

Old URLNew URLSimilarity ScoreSecond Best MatchSimilarity Score
old.example.com/product_1new.example.com/product-199,56No match0
old.example.com/product_2new.example.com/product-2a89,99new.example.com/product-2b78,37

Dein Trainingsplan zum Nachmachen

Step 1

Crawl der alten und neuen Website

Für den Start ist es erstmal wichtig die alte Website und die Testumgebung der neuen Website in einem möglichst späten Stadium zu crawlen, wenn schon möglichst viele Daten und Texte gepflegt sind. Ziel ist es, ein möglichst umfassendes Bild der Websites zu erhalten. Wir erstellen jetzt noch keine Vector Embeddings, sondern finden erstmal nur die URLs, für die wir Embeddings erstellen müssen. Für die alte Website bietet es sich an, die Google Search Console und auch Google Analytics anzuschließen. Dadurch werden auch URLs gefunden, die zwar indexiert sind oder Traffic erhalten, die aber intern für den Crawler nicht auffindbar sind. Dabei musst du sicherstellen, dass in den Einstellungen der APIs die jeweiligen Haken gesetzt sind, um in den Tools aufkommende URLs auch zu crawlen. Wie das genau geht, findest du auch in der Screaming Frog Dokumentation.

Step 2

Listen erstellen

Wenn wir einen Überblick darüber haben, welche URLs es auf der neuen und der alten Website gibt, dann bietet es sich an, je nach Umfang und Anzahl der URLs, diese URLs in kleinere Listen zu kategorisieren. Für mich erscheint am sinnvollsten, vor allem die URLs der Produktdetailseiten in eine eigene Liste zu schreiben. Den Blog, wenn es einen gibt, kann man auch noch separieren und je nach Anzahl der Kategorieseiten auch diese und dann noch eine für den Rest, wie Impressum, Datenschutzseiten, und “Über uns”-Bereiche, etc.

Wir beschränken uns hier auf die Erstellung von Weiterleitungen von HTML-Dokumenten und verwenden daher auch nur URLs, hinter denen sich solche verbergen. Man kann aber auch Vector-Embeddings für Bilder erstellen, auf dessen Basis man dann Weiterleitungen für Bilder-URLs erstellen kann.

Die Verteilung in unterschiedliche Listen hat den Vorteil, dass wir Produktdetailseiten mit Produktdetailseiten vergleichen können und so nicht in das Problem laufen, dass der Produktdetailseite zu weißen Sneakern irrtümlicherweise die Kategorieseite zu weißen Sneakern zugewiesen werden.

Ich erstelle dafür pro Teilbereich eine Text-Datei, in die ich die URLs eine pro Zeile kopiere. Du kannst aber genau so in dem Excel Export aus Screaming Frog SEO Spider eine Spalte erzeugen und die URLs clustern.

Step 3

Erstellen von Vector Embeddings

Jetzt gehen wir ans Eingemachte und erstellen die Vector Embeddings. Stelle zunächst den Screaming Frog SEO Spider auf den List Mode um. Dann brauchst du noch die folgenden Dinge um loslegen zu können:

  1. Das Custom JavaScript

  2. CSS-Selektoren für die wichtigen Teile deiner Seite

  3. OpenAI API Key

Step 3.1

Das Custom JavaScript herunterladen und importieren

Das Custom Javascript habe ich als Template abgespeichert, welches du hier herunterladen kannst. Nach dem Download öffnest du deinen Screaming Frog SEO Spider, gehst in die Konfiguration und wählst dort den Punkt Custom Javascript aus. Dort kannst du dann die JSON-Datei hochladen.

Ich habe noch ein weiteres Custom Javascript zur Überprüfung der Selektoren erstellt, ohne die Anbindung an die OpenAI API, um vor der Erstellung der Embeddings verifizieren zu können, dass der extrahierte Content vollständig ist. Das reduziere Custom JavaScript kannst hier herunterladen.

Step 3.2

Beschaffen der Selektoren

Als nächstes brauchen wir die Selektoren der Elemente, aus denen die Texte extrahiert werden müssen. Dafür öffnest du eine Seite von dem Typen, für den du jetzt die Vector Embeddings erstellen wirst. Öffne die DevTools, in dem du das gewünschte Element markierst , und per Rechtsklick auf “Untersuchen” gehst. In diesem Beispiel ist der Produktname die H1 mit der Klasse product-main-info__name. In der Zeile ganz unten sehen wir auch die korrekte Schreibweise, die wir in dem Include-Part des Skripts ergänzen, nämlich h1.product-main-info__name

Das machen wir jetzt auch nochmal mit der Produktbeschreibung. Hier habe ich einmal das Overlay geöffnet, in dem die gesamte Beschreibung drin steht. Ich habe auch hier wieder das Element gesucht und bin dann unten in den DevTools auf die obere DIV-Ebene gewechselt, die um all die Textelemente liegt. Ein anderer Weg, um an den Selektor zu kommen ist, auf das HTML-Element mit Rechtsklick zu klicken und in dem Dialog auf “Copy > Copy selector” zu klicken. Damit wird der gesamte Weg zu dem Element kopiert, wir brauchen davon dann aber nur das letzte div.pdpPanelSlide__content.

Das kannst du jetzt mit beliebig vielen Elementen machen, von den du denkst, sie reichern die Datengrundlage weiter mit einzigartigen Inhalten an. Da die Selektoren sich auf der alten und der neuen Seite höchstwahrscheinlich unterscheiden, suchst du die Selektoren für beide Websites heraus.

In unserem Beispiel reicht das und der Teil im Skript sähe jetzt so aus:

// Select elements that typically contain relevant content
const elementsToInclude = document.querySelectorAll(`
h1.product-main-info__name, div.pdpPanelSlide__content

Step 3.3

OpenAI API Key

Damit du die OpenAI API nutzen kannst, benötigst du einen OpenAI Account. Das ist etwas anderes als einen ChatGPT Account. Hier ist eine Anleitung, wie du den API Key erhältst, sofern du noch keinen hast.

Deinen API Key fügst du im oberen Abschnitt des Custom JavaScript in den dafür vorgesehenen Platzhalter ein.

Step 3.4

Auf die Warteplätze, Kaffee-fertig, los

Jetzt kannst du den Crawl starten, dir einen Kaffee oder ein Getränk deiner Wahl holen, es dir in deinem Lieblingsessel gemütlich machen und warten bis der Crawl durchgelaufen ist.

Wenn der Crawl durchgelaufen ist, überprüfe einmal, ob alle URLs einen HTTP-Status Code 200 ergeben haben und du für alle URLs Embeddings erhalten hast. Wechsel im Screaming Frog SEO Spider dafür in den Custom JavaScript Tab. Filtere hier über das Dropdown links oben nach den Embeddings und überprüfe die Zahl unten rechts der Liste mit der Anzahl der von dir eingegebenen URLs. Wenn diese übereinstimmen, exportiere die Liste als Excel-Datei. Wenn die Liste nicht vollständig ist, finde die URLs heraus und crawle die fehlerhaften URLs ggf. neu oder finde heraus, was der Fehler ist.

Step 4

Let the real magic begin

Wenn du jetzt für die alten und die neuen Seiten die Embeddings erzeugt hast, öffne einmal beide Excel Tabellen, achte darauf, dass nur zwei Spalten mit den URLs und den Embeddings bestehen, lösche ansonsten die übrigen. Nenne die Spalten URL und Embedding, das ist wichtig für das Python Skript. Wenn beide Excel-Dateien bereinigt sind, dann öffne das Google Colab Notebook. Wenn du es zum ersten Mal öffnest, kannst du es dir auch in dein eigenes Google Drive kopieren und wenn du möchtest, die Thresholds und Ranges für das “Best match” und das “Second best match” ändern.

Klicke links oben auf das Play-Icon, um das Skript auszuführen. Du wirst jetzt zuerst aufgefordert die Excel-Datei der alten URLs hochzuladen, danach die der neuen URLs. Das nimmt jetzt wieder etwas Zeit in Anspruch. Also Zeit für den nächsten Kaffee.

Nach dem Upload läuft das Skript durch, analysiert die Inhalte der Dateien, erstellt die Kosinus-Ähnlichkeit der Embeddings, matcht die passenden URLs und lädt dann im Anschluss die erstellte Excel-Datei herunter.

Step 5

It’s a match

Herzlichen Glückwunsch, du hast dir soeben Stunden an lästiger Arbeit gespart oder dich um wertvolle Zeit des Podcasthörens gebracht. Jedenfalls hast du jetzt eine Liste mit passenden URLs der alten und neuen Website erhalten. Überprüfe unbedingt die Ergebnisse und wähle in den Fällen, in denen der Similarity Score nicht hoch genug ist und es zwei Ergebnisse gibt, das passende aus.

Abschlussgedanken

Es ist zwar nicht der einfachste Weg, erfordert auf jeden Fall einige manuelle Handgriffe und wird es das erste Mal vermutlich nicht so schnell gehen, aber schon beim zweiten Mal lag bei mir die Zeitersparnis gegenüber dem manuellen Anlegen der Weiterleitungen bei 80%. Ich freue mich, einen für mich passenden Weg gefunden zu haben mir diese Arbeit vom Hals zu schaffen und ihn mit dir teilen zu können. Lass mich gerne wissen, was du davon hältst oder was du noch daran ändern würdest.

Checkliste

Hier nochmal eine kurze Checkliste zum Hinhängen:

  1. Crawle die alte Website und wähle die URLs aus, für die du die Embeddings erstellen möchtest
  2. Crawle die neue Website und wähle die URLs aus, für die du die Embeddings erstellen möchtest
  3. Crawle die ausgewählten URLs der alten Website, wähle die CSS-Selektoren für die relevanten Inhalte und erstelle die Embeddings
  4. Crawle die ausgewählten URLs der neuen Website, wähle die CSS-Selektoren für die relevanten Inhalte und erstelle die Embeddings
  5. Exportiere die URL Listen inkl. Embeddings als Excel-Dateien und bereinige diese (Achte auf die Spaltentitel = URL & Embedding)
  6. Führe das Python Notebook in Google Colab aus und lade die Excel-Dateien hoch
  7. Trinke einen Kaffee
  8. Lade das die Excel-Datei mit den gematchten Weiterleitungszielen herunter und überprüfe die Ergebnisse
  9. Freue dich über die gesparte Zeit

Möchtest du mehr von uns oder unseren SEO-Leistungen erfahren?

  • This field is for validation purposes and should be left unchanged.