Zum Inhalt springen
Doctor SEO

SEO mit KI: KI als Arbeitswerkzeug

Claude und dein CMS: WordPress, Rank Math und Schreibrechte

Lesezugang und Schreibzugang sind zwei Erlaubnisse, und nur die zweite kann dich URLs kosten. Mit dem offiziellen MCP von Rank Math, dem belegten Dublettenfall und einer Stapelprüfung, die vor dem ersten Schreibvorgang über den ganzen Stapel urteilt.

Aktualisiert am 8 Oktober 2026 28 Min. Lesezeit

Zwei Zugänge sehen in einem Konnektor gleich aus und kosten im Fehlerfall völlig Verschiedenes: ein Lesezugang gibt eine Kopie heraus, ein Schreibzugang entscheidet, welche Adressen deine Website hat. Am Ende dieser Lektion läuft bei dir eine Stapelprüfung: sie nimmt einen geplanten Stapel von CMS-Schreibvorgängen und urteilt über ihn als Ganzes, bevor die erste Seite entsteht.

Den belegten Fehlschlag hat die Erlaubnis möglich gemacht, nicht das Modell: erteilt war das Recht, neue Seiten anzulegen, und angelegt wurden zwei Kopien der Startseite mit verändertem Title-Tag, deren Impressionen und Klicks bei null blieben (Search Engine Land, Will Scott, 28. August 2026, ohne Link).

Anzuschließen gibt es inzwischen etwas Offizielles: Rank Math veröffentlichte am 27. Juli 2026 einen MCP-Server für WordPress. Ein solcher Zugang ändert nicht die Formulierung eines Entwurfs, sondern die Größe des Rückwegs dahinter.

Zur Einordnung: dieses Level benutzt die Modelle als Werkzeug. Ob und wie du selbst in einer KI-Antwort erscheinst, gehört zu Level 4, GEO und AIO. Das lesende Gegenstück zu dieser Lektion ist Claude mit deinen eigenen Daten verbinden: Search Console und GA4 per MCP.

Was du lernst

  • Lese- und Schreibzugang nach dem Rückweg bewerten, nicht nach dem Versprechen.
  • Den belegten Dublettenfall als Rechtefall lesen und nicht als Fähigkeitsproblem.
  • Eine Stapelprüfung über einen geplanten Stapel laufen lassen und ihr Urteil in fünf Blöcken lesen.
  • Eine Vorlagenspur erkennen, die kein Eintrag für sich trägt und kein Paarvergleich findet.
  • Zwölf Rückgabewerte so belegen, dass eine Verweigerung nie 0 zurückgibt.
  • Sagen, was der offizielle MCP von Rank Math nach eigener Ankündigung liest und schreibt, welcher Teil PRO verlangt und was die Ankündigung offenlässt.

Der Rückweg kostet, nicht der Zugriff

Teuer ist an einem Schreibzugang nicht der Vorgang, sondern seine Rücknahme – darin unterscheiden sich die beiden Erlaubnisse grundsätzlich, nicht graduell. MCP steht für Model Context Protocol: ein Protokoll, über das ein Modell wie Claude Aufrufe in fremde Software setzt, etwa in ein WordPress-Plugin. Angeschlossen wird keine neue Fähigkeit, sondern eine Tür zu Fähigkeiten, die ein anderes Programm längst hatte. Türen haben Breiten.

Was sich nach einem Fehlgriff unterscheidet, ist nicht der Augenblick, sondern die Reparatur: wer sie ausführt, woran sie hängt und wie lange etwas darauf wartet. Die Tabelle stellt vier Fehlgriffe nebeneinander, vom falsch gelesenen Wert bis zur angelegten Seite.

Fehlgriff Die Reparatur Wer sie ausführt Was darauf wartet
Eine Zahl falsch gelesen noch einmal abfragen du allein nichts
Einen Bericht darauf gebaut den Bericht neu bauen du, vor seinen Lesern die Entscheidung darauf
Einen Title-Tag überschrieben den alten Wert zurückschreiben du, falls der Vorzustand existiert der nächste Crawl
Eine Seite angelegt löschen, weiterleiten oder beides du und jeder, der die Adresse verlinkt hat der Index

Die oberen zwei Zeilen kosten Minuten. Die unterste geht nicht zurück: eine gecrawlte Adresse wird stillgelegt, und das ist Relaunch-Arbeit in der Größe des Stapels, der sie erzeugt hat.

Was Praktiker zurückhalten lässt, ist in einer Umfrage mit Stichprobenangabe erhoben – und es ist nicht die Breite der Erlaubnis: Keyword.com, State of AI in SEO 2026, 1. Januar 2026, n = 97 auswertbare Antworten, selbst gewählte Teilnahme. Eine Person von 97, also 1 %, beschreibt ihre Arbeit als vollständig automatisiert. Als Grund, nicht zu automatisieren, nennen 57 % „Quality not good enough“ – eine Zahl innerhalb der Gruppe, die zurückhält, nicht 57 von 97.

Was die Erlaubnis zuließ: zwei neue Adressen

Zugelassen war das Anlegen von Seiten, und genau das ist geschehen – zweimal. Will Scott beschrieb den Fall am 28. August 2026 in Search Engine Land: Claude legte unter /seo-grader und /content-grader zwei Kopien der Startseite an, unterschieden allein durch den Title-Tag. Für beide neuen Adressen stehen Impressionen und Klicks bei null, und die Ausgangsseite stand danach wie vorher auf Platz 9 oder schlechter.

Dieselbe Duplizierung trat unabhängig auf einer zweiten Website auf, ScryPrice. Will Scott hält dieses Verhalten für systematisch und nicht für einmalig; zwei Websites eines Autors belegen deshalb noch keine Häufigkeit. searchengineland.com lässt sich nicht abrufen, deshalb steht die Quelle mit Publikation, Autor und Datum und ohne Link.

Halluziniert wurde nichts, und der Auftrag blieb im erteilten Recht. Was fehlte, war keine Fähigkeit des Modells, sondern eine Bedingung an der Erlaubnis: dass zwei neue Adressen nicht dieselbe Seite sein dürfen.

Die weiteren Grenzen der Quelle gehören dazu: keine Stichprobengröße, kein Trafficvolumen, kein Beobachtungszeitraum, keine Beschreibung, wie der Fehler auffiel. Damit lässt sich ein Mechanismus beschreiben, dem man eine Bedingung entgegensetzt – eine Häufigkeit nicht, und diese Lektion rechnet keine.

Der tragende Satz ist ein anderer: der Fehler hat keinen Ort. Wer /seo-grader öffnet, findet eine Seite, die hält, was ihr Titel ankündigt, und für /content-grader gilt dasselbe. An jeder einzelnen Seite ist nichts falsch, am Paar ist alles falsch. Eine Prüfung Seite für Seite findet das nicht – deshalb liegt das Prüfobjekt unten beim Stapel.

Was der offizielle MCP von Rank Math nach eigener Ankündigung liest und schreibt

Nach eigener Ankündigung liest und schreibt der offizielle MCP-Server von Rank Math Metadaten und strukturierte Daten, führt Audits aus und holt Keyword-Daten aus der Search Console; autorisiert wird er über OAuth, und ein Teil der Funktionen ist an die PRO-Version gebunden. Angekündigt hat ihn Bhanu Ahluwalia in „Rank Math MCP: AI-Powered SEO Automation for WordPress“, veröffentlicht am 27. Juli 2026, zuletzt aktualisiert am 16. September 2026.

Nach dieser Ankündigung liest und schreibt der Konnektor also Metadaten und Schema – welche Beiträge eine einzelne Autorisierung umfasst, steht in keiner hier zitierten Quelle. Das liest du an deinem eigenen Einwilligungsdialog ab. Beschrieben ist damit, was das Produkt zu leisten angibt, und das ist kein Vorwurf.

Keine der hier zitierten Quellen veröffentlicht dazu eine Messung: keine Fehlerquote, keine Stichprobengröße, kein Zählwerk zurückgenommener Schreibvorgänge – nicht für diesen Konnektor und nicht für einen anderen Weg in ein CMS. Eine Produktankündigung ist eine zulässige Gattung und keine Untersuchung. Die einzige verfügbare Zahl erhebst du deshalb selbst, an einem kleinen Stapel. Metadaten und Inhaltsformate als Handwerk behandelt On-Page-SEO und Content.

Ob die Regeln deines WordPress-Bedienfelds auch greifen, wenn ein Schreibvorgang über einen Konnektor eintrifft – wer veröffentlichen darf, was Entwurf bleibt, welcher Benutzer in der Revision landet –, misst keine Quelle hier.

Einen datierten Nachbarfall gibt es, und er betrifft einen Crawler. Rich Voller hielt in „Screaming Frog v24 MCP: The Complete Agency Guide“ vom 26. Mai 2026, aktualisiert am 12. Juni 2026, einen über MCP angestoßenen Lauf fest: eingestellt war in der Oberfläche eine URL pro Sekunde, abgerufen wurden 500 URLs in 20 Sekunden. Eine Stichprobengröße nennt der Text nicht. Ein Crawler ist kein CMS, also entscheidet dieser Fall über deine Installation nichts.

Warum das Prüfobjekt der ganze Stapel ist

Prüfobjekt ist der ganze Stapel, weil die Eigenschaft, die den belegten Fehlschlag ausmacht, zwischen zwei Einträgen liegt und in keinem von beiden. Fünf einzeln saubere Einträge können ein Paar enthalten, das dieselbe Seite zweimal anlegt; die Freigabe jedes einzelnen erzeugt dann das Ergebnis, das Will Scott beschrieben hat.

Die andere Hälfte gehört Claude Code für technisches SEO: dort bindet das Schreibtor eine Freigabe an die Signatur eines Plans und hält jede Änderung zurück, deren Suchtext nicht genau einmal in der Zieldatei steht. Diese Lektion setzt das voraus und stellt die Frage, die eine Prüfung je Eintrag nicht stellen kann.

Acht Befunde fallen in diese Frage; die dritte Spalte sagt, was eine Prüfung je Eintrag davon sieht.

Marke Was verglichen wird Was eine Prüfung je Eintrag davon sieht
TITELKLON zwei Rümpfe im Stapel, Titel verschieden nichts
RUMPFKLON zwei Rümpfe im Stapel, Titel gleich nichts
BESTANDSDUBLETTE ein Rumpf gegen eine veröffentlichte Seite nichts ohne den Bestand
SLUG-DOPPELT zwei Adressen im Stapel nichts
SLUG-BELEGT eine Adresse gegen den Bestand nichts ohne den Bestand
NICHT-VERGLEICHBAR ein Rumpf gegen die Breite der Wortgruppe den kurzen Rumpf, nicht das Hindernis
OBERGRENZE die Zahl der Einträge gegen deine Zahl nichts
VORLAGENSPUR alle Rümpfe gegeneinander, nicht paarweise nichts

VORLAGENSPUR ist der Befund, für den es diese Bauform braucht: er zählt Wortgruppen, die in mindestens drei Einträgen stehen, und findet damit ein Gerüst unterhalb jeder Paarschwelle. Drei Seiten auf derselben Vorlage mit getauschtem Anlass überschneiden sich paarweise zu wenig und als Menge zu viel.

Die Stapelprüfung: zwei Dateien, fünf Ausgabeblöcke, eine Zahl

Die Stapelprüfung liest zwei Dateien von der Platte – den geplanten Stapel und einen Abzug des Bestands – und gibt eine Zahl zurück, die für den ganzen Stapel gilt. Sie öffnet keine Verbindung und braucht kein Recht an deinem CMS. Knapp 280 Zeilen Python 3, nur Standardbibliothek, kein Netzzugriff; gespeichert ist sie als vorflug.py, und so heißt sie in jedem Aufruf unten.

Verglichen werden Fünfergruppen: Mengen von je fünf aufeinanderfolgenden Wörtern eines Rumpftextes. Das Maß ist der Anteil der eigenen Fünfergruppen, die auch im anderen Text vorkommen, gemessen in beide Richtungen, wobei der größere Wert zählt – sonst rutscht ein kurzer Rumpf durch, der vollständig in einem langen steckt. Als Schwelle steht 0,60 im Kopf des Skripts. Diese Zahl ist meine Wahl und keine veröffentlichte Größe. Begründung: fünf aufeinanderfolgende Wörter wiederholen sich selten zufällig, und teilen zwei Rümpfe drei Fünftel ihrer Gruppen, sind sie dieselbe Seite mit zwei Titeln. Das Nachjustieren einer solchen Schwelle gehört in diesem Level an eine andere Stelle.

Ein fehlendes Feld ist eine Verweigerung und nie ein stilles Bestehen. get(feld, "") kommt im Skript darum an keiner Stelle vor: wer einen Standardwert einsetzt, verwandelt ein fehlendes Feld in ein leeres und lässt einen beschädigten Export mit sauberem Urteil passieren. Zur Schreibweise: im Skript steht der Dezimalpunkt, weil Python so formatiert, im Text das Komma – und das Skript schreibt seine eigenen Meldungen ohne Umlaute, damit die Läufe unten in jeder Terminalkodierung gleich aussehen.

#!/usr/bin/env python3
"""Stapelpruefung: prueft einen geplanten Stapel von CMS-Schreibvorgaengen,
bevor irgendetwas das CMS erreicht. Zwei Dateien von der Platte, kein
Netzzugriff, nur Standardbibliothek.

    python3 vorflug.py stapel.json bestand.json

stapel.json  {"format": "stapel/1", "obergrenze": <int>,
              "eintraege": [{"slug": .., "titel": .., "rumpf": ..}, ..]}
bestand.json {"format": "bestand/1", "seiten": [{"pfad": .., "text": ..}, ..]}

Geprueft wird der Stapel als Ganzes: gesucht sind Beziehungen zwischen
zwei Eintraegen und zwischen einem Eintrag und einer schon
veroeffentlichten Seite. Ein einzelner Eintrag kann fuer sich
tadellos sein und trotzdem einen Befund tragen.

Zwoelf belegte Rueckgabewerte, 1 bleibt unbelegt:
   0  kein Befund, der Stapel darf einem Menschen vorgelegt werden
   2  Befehlszeile: es braucht genau zwei Pfade
   3  eine der beiden Dateien fehlt oder ist keine regulaere Datei
   4  regulaere Datei, deren Bytes nicht zu lesen waren
   5  die Bytes sind nicht UTF-8
   6  UTF-8, aber kein gueltiges JSON
   7  stapel.json hat nicht die Form stapel/1, oder eintraege ist leer
   8  ein Eintrag im Stapel ist unbrauchbar: Feld fehlt, falscher Typ
      oder leer
   9  bestand.json hat nicht die Form bestand/1, oder ein Bestandseintrag
      ist unbrauchbar
  10  obergrenze ist keine ganze Zahl ab 1
  11  Befund: der Stapel wird verweigert, nichts wird geschrieben
  12  unerwartete Ausnahme, es ergeht kein Urteil
   1  unbelegt

Was dieses Tor nicht sieht: zwei Texte, die um dieselbe leere Aussage
herum verschieden formuliert sind, teilen keine Fuenfergruppen und
bestehen beide. Ein Rumpf, der mitten im Satz abgeschnitten wurde,
ist gueltiges JSON und faellt nicht auf. Doppelte Schluessel in einem
Objekt loest der Parser der Standardbibliothek stillschweigend zum
letzten Wert auf, ohne ein Wort darueber.
"""

import json
import os
import re
import sys

DECKUNG = 0.60          # eigene Wahl, keine veroeffentlichte Zahl
BREITE = 5              # Woerter je Schindel
VORLAGEN_MINDEST = 3    # in so vielen Eintraegen muss eine Schindel stehen
VORLAGEN_SPUREN = 3     # so viele solcher Schindeln gelten als Geruest

FELDER = ("slug", "titel", "rumpf")
FEHLT = object()        # Platzhalter: nirgends steht get(feld, "")


def melde(zeile):
    sys.stdout.write(zeile + "\n")


def ende(code, text):
    sys.stderr.write(text + "\n")
    raise SystemExit(code)


def schindeln(text):
    """Menge der Fuenfergruppen aus aufeinanderfolgenden Woertern."""
    woerter = re.findall(r"[\w]+", text.lower(), re.UNICODE)
    if len(woerter) < BREITE:
        return frozenset()
    return frozenset(" ".join(woerter[i:i + BREITE])
                     for i in range(len(woerter) - BREITE + 1))


def deckung(eigen, fremd):
    """Anteil der eigenen Schindeln, die auch im fremden Text stehen."""
    if not eigen:
        return 0.0
    return len(eigen & fremd) / len(eigen)


def lies_text(pfad):
    if not os.path.exists(pfad) or not os.path.isfile(pfad):
        ende(3, "nicht gefunden oder keine regulaere Datei: %s" % pfad)
    try:
        with open(pfad, "rb") as datei:
            rohbytes = datei.read()
    except OSError as fehler:
        ende(4, "Bytes nicht lesbar: %s (%s)" % (pfad, fehler))
    try:
        return rohbytes.decode("utf-8")
    except UnicodeDecodeError as fehler:
        # UnicodeDecodeError erbt von ValueError, nicht von OSError, und
        # braucht deshalb einen eigenen Zweig vor dem JSON-Zweig.
        ende(5, "nicht UTF-8: %s (%s)" % (pfad, fehler))


def als_objekt(text, pfad):
    try:
        return json.loads(text)
    except ValueError as fehler:
        ende(6, "kein gueltiges JSON: %s (%s)" % (pfad, fehler))


def ganze_zahl_ab_eins(wert):
    """int ab 1 oder None. bool ist eine Unterklasse von int und wird
    ausdruecklich abgewiesen, damit true nicht als 1 durchgeht."""
    if isinstance(wert, bool) or not isinstance(wert, int):
        return None
    return wert if wert >= 1 else None


def nimm_stapel(pfad):
    """Gibt (eintraege, obergrenze) zurueck oder beendet mit 7, 8 oder 10."""
    doc = als_objekt(lies_text(pfad), pfad)
    if not isinstance(doc, dict) or doc.get("format", FEHLT) != "stapel/1":
        ende(7, "%s ist kein Dokument der Form stapel/1" % pfad)
    eintraege = doc.get("eintraege", FEHLT)
    if not isinstance(eintraege, list) or not eintraege:
        ende(7, "%s: eintraege fehlt, ist kein Array oder ist leer" % pfad)
    for nummer, eintrag in enumerate(eintraege, 1):
        if not isinstance(eintrag, dict):
            ende(8, "Eintrag %d in %s ist kein Objekt" % (nummer, pfad))
        for feld in FELDER:
            wert = eintrag.get(feld, FEHLT)
            if wert is FEHLT:
                ende(8, "Eintrag %d in %s hat kein Feld %s"
                        % (nummer, pfad, feld))
            if not isinstance(wert, str):
                ende(8, "Eintrag %d in %s: %s ist keine Zeichenkette"
                        % (nummer, pfad, feld))
            if not wert.strip():
                ende(8, "Eintrag %d in %s: %s ist leer" % (nummer, pfad, feld))
    rohgrenze = doc.get("obergrenze", FEHLT)
    if rohgrenze is FEHLT:
        obergrenze = len(eintraege)
    else:
        obergrenze = ganze_zahl_ab_eins(rohgrenze)
        if obergrenze is None:
            ende(10, "obergrenze in %s ist keine ganze Zahl ab 1" % pfad)
    return eintraege, obergrenze


def nimm_bestand(pfad):
    """Gibt die veroeffentlichten Seiten zurueck oder beendet mit 9."""
    doc = als_objekt(lies_text(pfad), pfad)
    if not isinstance(doc, dict) or doc.get("format", FEHLT) != "bestand/1":
        ende(9, "%s ist kein Dokument der Form bestand/1" % pfad)
    seiten = doc.get("seiten", FEHLT)
    if not isinstance(seiten, list):
        ende(9, "%s: seiten fehlt oder ist kein Array" % pfad)
    geprueft = []
    for nummer, seite in enumerate(seiten, 1):
        if not isinstance(seite, dict):
            ende(9, "Seite %d in %s ist kein Objekt" % (nummer, pfad))
        for feld in ("pfad", "text"):
            wert = seite.get(feld, FEHLT)
            if wert is FEHLT or not isinstance(wert, str) or not wert.strip():
                ende(9, "Seite %d in %s: %s fehlt, hat den falschen Typ "
                        "oder ist leer" % (nummer, pfad, feld))
        geprueft.append((seite["pfad"].strip(), seite["text"]))
    return geprueft


def finde_dubletten(eintraege, bestand):
    """Befunde aus dem Vergleich von Rumpftexten. Liste von Tripeln."""
    befunde = []
    schindelmengen = [schindeln(e["rumpf"]) for e in eintraege]
    for nummer, menge in enumerate(schindelmengen):
        if not menge:
            befunde.append(("NICHT-VERGLEICHBAR", eintraege[nummer]["slug"],
                            "Rumpf unter %d Woertern, kein Vergleich moeglich"
                            % BREITE))
    for links in range(len(eintraege)):
        for rechts in range(links + 1, len(eintraege)):
            wert = max(deckung(schindelmengen[links], schindelmengen[rechts]),
                       deckung(schindelmengen[rechts], schindelmengen[links]))
            if wert < DECKUNG:
                continue
            gleich = eintraege[links]["titel"] == eintraege[rechts]["titel"]
            befunde.append((
                "RUMPFKLON" if gleich else "TITELKLON",
                "%s + %s" % (eintraege[links]["slug"],
                             eintraege[rechts]["slug"]),
                "Deckung %.2f" % wert))
    for nummer, menge in enumerate(schindelmengen):
        for pfad, text in bestand:
            wert = deckung(menge, schindeln(text))
            if wert >= DECKUNG:
                befunde.append(("BESTANDSDUBLETTE",
                                eintraege[nummer]["slug"],
                                "deckt %s mit %.2f" % (pfad, wert)))
    return befunde


def finde_kollisionen(eintraege, bestand):
    """Befunde aus den Adressen, nicht aus den Texten."""
    befunde = []
    zaehler = {}
    for eintrag in eintraege:
        schluessel = eintrag["slug"].strip("/")
        zaehler[schluessel] = zaehler.get(schluessel, 0) + 1
    for schluessel in sorted(zaehler):
        if zaehler[schluessel] > 1:
            befunde.append(("SLUG-DOPPELT", schluessel,
                            "%d Eintraege derselben Adresse"
                            % zaehler[schluessel]))
    belegt = {pfad.strip("/") for pfad, _ in bestand}
    for schluessel in sorted(set(zaehler) & belegt):
        befunde.append(("SLUG-BELEGT", schluessel,
                        "/%s ist veroeffentlicht" % schluessel))
    return befunde


def finde_stapelmass(eintraege, obergrenze):
    """Befunde ueber den Stapel, die keinem Eintrag gehoeren."""
    befunde = []
    if len(eintraege) > obergrenze:
        befunde.append(("OBERGRENZE", "stapel",
                        "%d Eintraege gegen eine Obergrenze von %d"
                        % (len(eintraege), obergrenze)))
    verteilung = {}
    for eintrag in eintraege:
        for schindel in schindeln(eintrag["rumpf"]):
            verteilung[schindel] = verteilung.get(schindel, 0) + 1
    spuren = sum(1 for anzahl in verteilung.values()
                 if anzahl >= VORLAGEN_MINDEST)
    if spuren >= VORLAGEN_SPUREN:
        befunde.append(("VORLAGENSPUR", "stapel",
                        "%d Fuenfergruppen stehen in mindestens %d Eintraegen"
                        % (spuren, VORLAGEN_MINDEST)))
    return befunde


def schreib_block(titel, befunde):
    melde("== %s ==" % titel)
    if not befunde:
        melde("  ohne Befund")
        return 0
    for marke, wo, text in befunde:
        melde("  %-18s %-28s %s" % (marke, wo, text))
    return len(befunde)


def main(argv):
    if len(argv) != 3:
        melde("Aufruf: vorflug.py stapel.json bestand.json")
        return 2
    eintraege, obergrenze = nimm_stapel(argv[1])
    bestand = nimm_bestand(argv[2])

    melde("== Eingabe ==")
    melde("  Stapel   %d Eintraege, Obergrenze %d" % (len(eintraege),
                                                      obergrenze))
    melde("  Bestand  %d veroeffentlichte Seiten" % len(bestand))
    melde("  Schwelle Deckung %.2f auf Fuenfergruppen" % DECKUNG)

    gesamt = schreib_block("Dubletten", finde_dubletten(eintraege, bestand))
    gesamt += schreib_block("Kollisionen", finde_kollisionen(eintraege,
                                                             bestand))
    gesamt += schreib_block("Stapelmass", finde_stapelmass(eintraege,
                                                           obergrenze))

    melde("== Urteil ==")
    if gesamt:
        melde("  %d Befunde. Es wird nichts geschrieben." % gesamt)
        return 11
    melde("  Kein Befund. Der Stapel darf einem Menschen vorgelegt werden.")
    return 0


if __name__ == "__main__":
    try:
        sys.exit(main(sys.argv))
    except SystemExit:
        raise
    except Exception as fehler:          # 1 bleibt dem Absturz vorbehalten
        sys.stderr.write("unerwartete Ausnahme: %s: %s\n"
                         % (type(fehler).__name__, fehler))
        sys.exit(12)

Vier Eingabedateien des Notenversands Kirnbach und drei Läufe

Vier Eingabedateien tragen die Läufe unten, und alle vier sind erfunden. Der Beispielstapel des Notenversands Kirnbach stammt aus keinem Kundenprojekt. Kirnbach ist ein erfundener Notenhandel; Adressen, Titel und Rumpftexte sind für diese Lektion geschrieben und beziffern nichts. Alle Dateien sind UTF-8 mit LF-Zeilenenden und enden mit einem Zeilenumbruch; mit CRLF fallen die Urteile gleich aus, nur die Zeichenpositionen in der Meldung zu Code 6 verschieben sich.

Zuerst der Bestand: drei veröffentlichte Seiten, je mit Pfad und Text.

{
  "format": "bestand/1",
  "seiten": [
    {"pfad": "/klavierauszuege",
     "text": "Klavierauszüge liegen bei uns in der Urtext-Fassung und in der bezifferten Spielfassung vor. Welche Ausgabe für eine Probe taugt, entscheidet der Dirigent und nicht der Preis."},
    {"pfad": "/notenpulte",
     "text": "Notenpulte aus Stahlblech halten eine Partitur über drei Stunden Probe, Leichtpulte aus Aluminium kippen ab zwei Ordnern. Diese Seite vergleicht Standfläche, Gewicht und Höhenverstellung."},
    {"pfad": "/chornoten-advent",
     "text": "Chornoten für den Advent sortieren wir nach Besetzung und Schwierigkeit, von vierstimmig a cappella bis zur Orgelbegleitung. Lieferzeit und Mindestabnahme stehen bei jedem Titel."}
  ]
}

Dann der Stapel, den ein Agent vorschlägt. Fünf Einträge gegen eine selbst gesetzte Obergrenze von drei, und zwei davon tragen denselben Rumpf mit verschiedenen Titeln – die Bauform des belegten Falls.

{
  "format": "stapel/1",
  "obergrenze": 3,
  "eintraege": [
    {"slug": "klavierschule-anfaenger",
     "titel": "Klavierschule für Anfänger",
     "rumpf": "Eine Klavierschule führt vom ersten Ton bis zur zweistimmigen Begleitung. Wir führen sechs Reihen mit Begleit-CD und vier ohne, dazu Hefte mit Fingersatz über jeder Note."},
    {"slug": "klavierschule-erwachsene",
     "titel": "Klavierschule für Erwachsene",
     "rumpf": "Eine Klavierschule führt vom ersten Ton bis zur zweistimmigen Begleitung. Wir führen sechs Reihen mit Begleit-CD und vier ohne, dazu Hefte mit Fingersatz über jeder Note."},
    {"slug": "notenpulte",
     "titel": "Notenpulte im Vergleich",
     "rumpf": "Notenpulte aus Stahlblech halten eine Partitur über drei Stunden Probe, Leichtpulte aus Aluminium kippen ab zwei Ordnern. Diese Seite vergleicht Standfläche, Gewicht und Höhenverstellung im Überblick."},
    {"slug": "saitenwechsel",
     "titel": "Saiten wechseln",
     "rumpf": "Zwei Sätze Saiten."},
    {"slug": "chornoten-kammerchor",
     "titel": "Chornoten für den Kammerchor",
     "rumpf": "Ein Kammerchor mit zwölf Stimmen braucht andere Sätze als ein großer Verein. Diese Liste nennt Werke, die mit einer Stimme pro Partie tragfähig bleiben, und sagt bei jedem, wo es dünn wird."}
  ]
}

Der Lauf an diesen beiden Dateien:

$ python3 vorflug.py stapel.json bestand.json
== Eingabe ==
  Stapel   5 Eintraege, Obergrenze 3
  Bestand  3 veroeffentlichte Seiten
  Schwelle Deckung 0.60 auf Fuenfergruppen
== Dubletten ==
  NICHT-VERGLEICHBAR saitenwechsel                Rumpf unter 5 Woertern, kein Vergleich moeglich
  TITELKLON          klavierschule-anfaenger + klavierschule-erwachsene Deckung 1.00
  BESTANDSDUBLETTE   notenpulte                   deckt /notenpulte mit 0.91
== Kollisionen ==
  SLUG-BELEGT        notenpulte                   /notenpulte ist veroeffentlicht
== Stapelmass ==
  OBERGRENZE         stapel                       5 Eintraege gegen eine Obergrenze von 3
== Urteil ==
  5 Befunde. Es wird nichts geschrieben.
$ echo $?
11

Fünf Befunde aus drei Blöcken und vier verschiedenen Vergleichen. TITELKLON kommt aus dem Paar im Stapel, Deckung 1,00. BESTANDSDUBLETTE kommt aus notenpulte gegen die veröffentlichte Seite /notenpulte, 0,91; dieselbe Adresse liefert zusätzlich SLUG-BELEGT – zwei Vergleiche, nicht einer doppelt. saitenwechsel hat drei Wörter im Rumpf und ist nicht vergleichbar, OBERGRENZE gehört keinem Eintrag, und chornoten-kammerchor trägt nichts: einer von fünf war nie das Problem, und die Prüfung sagt das.

Nach drei Änderungen – das Paar auf einen Eintrag reduziert, notenpulte gestrichen, saitenwechsel mit eigenem Rumpf – liegt die zweite Stapeldatei vor:

{
  "format": "stapel/1",
  "obergrenze": 3,
  "eintraege": [
    {"slug": "klavierschule-anfaenger",
     "titel": "Klavierschule für Anfänger",
     "rumpf": "Eine Klavierschule führt vom ersten Ton bis zur zweistimmigen Begleitung. Wir führen sechs Reihen mit Begleit-CD und vier ohne, dazu Hefte mit Fingersatz über jeder Note."},
    {"slug": "chornoten-kammerchor",
     "titel": "Chornoten für den Kammerchor",
     "rumpf": "Ein Kammerchor mit zwölf Stimmen braucht andere Sätze als ein großer Verein. Diese Liste nennt Werke, die mit einer Stimme pro Partie tragfähig bleiben, und sagt bei jedem, wo es dünn wird."},
    {"slug": "saitenwechsel",
     "titel": "Saiten wechseln am Kontrabass",
     "rumpf": "Ein Satz Darmsaiten braucht nach dem Aufziehen zwei Tage, bis er die Stimmung hält. Diese Anleitung geht den Wechsel Wirbel für Wirbel durch und nennt die Zugkraft je Lage."}
  ]
}
$ python3 vorflug.py stapel-bereinigt.json bestand.json
== Eingabe ==
  Stapel   3 Eintraege, Obergrenze 3
  Bestand  3 veroeffentlichte Seiten
  Schwelle Deckung 0.60 auf Fuenfergruppen
== Dubletten ==
  ohne Befund
== Kollisionen ==
  ohne Befund
== Stapelmass ==
  ohne Befund
== Urteil ==
  Kein Befund. Der Stapel darf einem Menschen vorgelegt werden.
$ echo $?
0

Drei Marken feuern in diesen beiden Läufen nie: RUMPFKLON braucht zwei gleiche Titel, SLUG-DOPPELT zwei Einträge auf einer Adresse, VORLAGENSPUR drei Einträge mit gemeinsamen Wortgruppen. Die dritte Stapeldatei liefert alle drei.

{
  "format": "stapel/1",
  "obergrenze": 10,
  "eintraege": [
    {"slug": "chornoten-advent-versand",
     "titel": "Chornoten für den Advent: Versand",
     "rumpf": "Noten für den Advent versenden wir am Tag der Bestellung. Advent-Programme stellen wir auf Wunsch zusammen, und die Mindestabnahme liegt bei zwanzig Heften."},
    {"slug": "chornoten-advent-versand",
     "titel": "Chornoten für den Advent: Versand",
     "rumpf": "Noten für den Advent versenden wir am Tag der Bestellung. Advent-Programme stellen wir auf Wunsch zusammen, und die Mindestabnahme liegt bei zwanzig Heften."},
    {"slug": "chornoten-ostern-versand",
     "titel": "Chornoten für Ostern: Versand",
     "rumpf": "Noten für Ostern versenden wir am Tag der Bestellung. Ostern-Programme stellen wir auf Wunsch zusammen, und die Mindestabnahme liegt bei zwanzig Heften. Dazu kommen Passionsmotetten in zwei Besetzungen und eine Liste der Orgelstimmen, die wir vorrätig halten."}
  ]
}
$ python3 vorflug.py stapel-vorlage.json bestand.json
== Eingabe ==
  Stapel   3 Eintraege, Obergrenze 10
  Bestand  3 veroeffentlichte Seiten
  Schwelle Deckung 0.60 auf Fuenfergruppen
== Dubletten ==
  RUMPFKLON          chornoten-advent-versand + chornoten-advent-versand Deckung 1.00
== Kollisionen ==
  SLUG-DOPPELT       chornoten-advent-versand     2 Eintraege derselben Adresse
== Stapelmass ==
  VORLAGENSPUR       stapel                       11 Fuenfergruppen stehen in mindestens 3 Eintraegen
== Urteil ==
  3 Befunde. Es wird nichts geschrieben.
$ echo $?
11

Der dritte Eintrag ist hier der interessante. Seine Deckung gegen den ersten liegt bei 0,55, unter der Schwelle von 0,60 und nachrechenbar an den abgedruckten Rümpfen, also trägt er keinen TITELKLON. Auffällig wird er trotzdem: elf Fünfergruppen stehen in allen drei Einträgen, und das meldet VORLAGENSPUR. Ein Paarvergleich hätte ihn durchgelassen. Noch einmal, weil es für diese Zahlen zählt: 1,00, 0,91 und 0,55 sind Eigenschaften erfundener Texte und keine Messung an einer Website.

Zwölf belegte Rückgabewerte, und die 1 bleibt frei

Zwölf Rückgabewerte sind belegt, jeder mit genau einer Ursache, und die Verweigerung trägt die 11. Die 1 bleibt frei, weil ein Python-Prozess mit ungefangener Ausnahme selbst mit 1 endet – wer sie vergibt, verwechselt einen Absturz der Prüfung mit einer Entscheidung der Prüfung.

Code Was eingetreten ist Unten vorgeführt
0 kein Befund; der Stapel darf einem Menschen vorgelegt werden ja
2 Befehlszeile: es braucht genau zwei Pfade ja
3 eine Datei fehlt oder ist keine reguläre Datei ja
4 reguläre Datei, deren Bytes nicht zu lesen waren nein
5 die Datei ist nicht als UTF-8 dekodierbar ja
6 dekodierbar, aber syntaktisch kein JSON ja
7 nicht die Form stapel/1, oder eintraege ist leer ja, zweimal
8 ein Stapeleintrag ist unbrauchbar: Feld fehlt, falscher Typ oder leer ja
9 nicht die Form bestand/1, oder ein Bestandseintrag ist unbrauchbar ja
10 obergrenze ist keine ganze Zahl ab 1 ja
11 Befund: der Stapel wird verweigert ja, zweimal
12 unerwartete Ausnahme, es ergeht kein Urteil nein
1 unbelegt –

Aus dieser Tabelle folgt die Regel, die das Skript einhält: eine Verweigerung gibt nie 0 zurück.

Drei Typfallen sitzen in dieser Prüfreihe. Die Oberklasse von UnicodeDecodeError ist ValueError und nicht OSError – deshalb steht der Dekodierzweig vor dem JSON-Zweig und trägt mit 5 einen eigenen Wert. int hat bool als Unterklasse, weshalb die Prüfung von obergrenze ein true gesondert abweist. Und get(feld, "") steht in keiner Zeile.

Zwei Werte stehen als nicht vorgeführt in der Tabelle, weil sie sich aus keiner abgedruckten Datei herstellen lassen: Code 4 braucht eine Datei ohne Leserecht, Code 12 eine unvorhergesehene Ausnahme. Die Sitzung unten erzeugt die fehlerhaften Eingaben mit printf; eine davon trägt ein Byte, das in UTF-8 nicht vorkommen darf, und ist nicht als Text abdruckbar – die Oktalfolge \366 stellt sie her. Was in Klammern steht, ist die Wortwahl des Interpreters.

$ printf '{"format": "plan/1", "eintraege": []}\n' > stapel-form.json
$ printf '{"format": "stapel/1", "eintraege": []}\n' > stapel-ohne.json
$ printf '{"format": "stapel/1", "eintraege": [{"slug": "notenpulte-leicht", "rumpf": "Leichtpulte aus Aluminium wiegen unter einem Kilogramm."}]}\n' > stapel-eintrag.json
$ printf '{"format": "stapel/1", "obergrenze": true, "eintraege": [{"slug": "notenpulte-leicht", "titel": "Leichte Notenpulte", "rumpf": "Leichtpulte aus Aluminium wiegen unter einem Kilogramm."}]}\n' > stapel-grenze.json
$ printf '{"format": "bestand/1", "seiten": [{"pfad": "/notenpulte"}]}\n' > bestand-form.json
$ printf '{"format": "stapel/1", "eintraege": [\n' > stapel-kaputt.json
$ printf '{"format": "stapel/1", "eintraege": [{"slug": "fl\366te", "titel": "Fl\366te", "rumpf": "Querfl\366ten in zwei Bauformen."}]}\n' > stapel-latin1.json
$ python3 vorflug.py stapel-form.json bestand.json
stapel-form.json ist kein Dokument der Form stapel/1
$ echo $?
7
$ python3 vorflug.py stapel-ohne.json bestand.json
stapel-ohne.json: eintraege fehlt, ist kein Array oder ist leer
$ echo $?
7
$ python3 vorflug.py stapel-eintrag.json bestand.json
Eintrag 1 in stapel-eintrag.json hat kein Feld titel
$ echo $?
8
$ python3 vorflug.py stapel-grenze.json bestand.json
obergrenze in stapel-grenze.json ist keine ganze Zahl ab 1
$ echo $?
10
$ python3 vorflug.py stapel.json bestand-form.json
Seite 1 in bestand-form.json: text fehlt, hat den falschen Typ oder ist leer
$ echo $?
9
$ python3 vorflug.py stapel-kaputt.json bestand.json
kein gueltiges JSON: stapel-kaputt.json (Expecting value: line 2 column 1 (char 38))
$ echo $?
6
$ python3 vorflug.py stapel-latin1.json bestand.json
nicht UTF-8: stapel-latin1.json ('utf-8' codec can't decode byte 0xf6 in position 49: invalid start byte)
$ echo $?
5
$ python3 vorflug.py stapel-fehlt.json bestand.json
nicht gefunden oder keine regulaere Datei: stapel-fehlt.json
$ echo $?
3
$ python3 vorflug.py stapel.json
Aufruf: vorflug.py stapel.json bestand.json
$ echo $?
2

Bevor die Prüfung an irgendetwas gehängt wird, lohnt eine Zeile: python3 vorflug.py stapel.json bestand.json; echo $? muss als letzte Zeile 11 zeigen. Eine gedruckte Meldung ist kein Rückgabewert, und nur einer von beiden erreicht den nächsten Schritt.

Was diese Prüfung nicht sieht, steht auch im Kopf des Skripts: zwei Texte, die um dieselbe leere Aussage herum verschieden formuliert sind, teilen keine Fünfergruppe und bestehen beide; ein mitten im Satz abgeschnittener Rumpf ist gültiges JSON und fällt nicht auf; und doppelte Schlüssel löst der Parser der Standardbibliothek stillschweigend zum letzten Wert auf, sodass eine zweite obergrenze die erste lautlos überschreibt.

Wo die Branche über Seiten in Menge streitet

Gestritten wird darüber, wo scaled content abuse anfängt, und diese Seite entscheidet es nicht. Maßgeblich ist der Zweck einer Seitenmenge, und so steht es in Googles Spam-Richtlinien mit Stand vom 28. August 2026: „when many pages are generated for the primary purpose of manipulating search rankings and not helping users“. Wie die Seiten hergestellt wurden, spielt nach diesem Text keine Rolle, und beziffert ist dort gar nichts – weder eine Seitenzahl noch eine Rate noch ein Anteil an der Website. Die Richtlinie lässt sich nicht abrufen und steht deshalb ohne Link.

Für das eine Lager folgt daraus, dass die Prüfung vollständig am Zweck hängt: eine belegte, eigenständige, nützliche Seite ist dann bei keiner Stückzahl ein Verstoß. Sein bestes Argument ist der Wortlaut, der auf das wofür abstellt. Das andere Lager hält dagegen, dass derselbe Satz von vielen Seiten spricht; sein bestes Argument ist eine Beobachtungslage, denn ein Zweck ist von außen nicht feststellbar, eine Seitenzahl dagegen immer.

Entscheiden lässt sich das mit den hier zitierten Quellen nicht: eine Schwelle ist nicht veröffentlicht, und keine Untersuchung trennt die Wirkung der Herstellungsweise von der Wirkung der Menge. Gewählt wird hier deshalb kein Lager. Angeboten wird nur eine Obergrenze, die du selbst setzt und die im Urteil erscheint. Wie weit andere diese Grenze dehnen, zeigt Grey Hat, Black Hat und Parasite SEO.

Häufige Fehler

  1. Den Bestandsabzug weglassen oder veralten lassen. Ohne die zweite Datei vergleicht die Prüfung den Stapel nur mit sich selbst, und eine Dublette gegen eine veröffentlichte Seite rutscht durch. Die Korrektur: den Abzug vor jedem Lauf frisch ziehen.
  2. Lesen und Schreiben in einem Schritt erlauben. Beide kommen im selben Dialog, und nur die zweite kann Adressen kosten. Die Korrektur: Metadaten vor Inhalt, Entwurf vor Veröffentlichung, ein Eintrag vor einem Stapel.
  3. Den Stapel genehmigen, indem man jeden Eintrag genehmigt. Das ist die Prüfung, die den belegten Fall nicht findet, weil kein Eintrag für sich etwas trägt. Die Korrektur: erst das Urteil über den Stapel lesen, dann über Einträge reden.
  4. Eine Deckungsschwelle übernehmen, die für eine andere Aufgabe gedacht war. Werte aus der automatischen internen Verlinkung entscheiden, ob zwei Seiten einen Link verdienen, nicht ob sie dieselbe Seite sind. Die Korrektur: die Zahl als eigene Wahl behandeln und festhalten.
  5. Eine Vorlagenspur für ein Ergebnis der Paarschwelle halten. Sie zählt Wortgruppen über alle Einträge; ein Stapel kann unter jeder Paarschwelle bleiben und trotzdem ein Gerüst tragen. Die Korrektur: beide Blöcke lesen, Dubletten und Stapelmass.

Kurzfassung

  • Prüfobjekt ist der ganze Stapel. TITELKLON, BESTANDSDUBLETTE und VORLAGENSPUR sind Beziehungen, die kein Eintrag für sich trägt – und die Deckungsschwelle 0,60 stammt von mir, nicht aus einer Veröffentlichung.
  • Der belegte Fehlschlag ist ein Rechtefall. Erlaubt war das Anlegen neuer Adressen; angelegt wurden zwei Kopien der Startseite mit verändertem Title-Tag. Weder die eine noch die andere neue Adresse erreichte eine Impression oder einen Klick; die Ausgangsseite blieb bei Platz 9 oder schlechter; dieselbe Duplizierung wiederholte sich unabhängig auf ScryPrice (Search Engine Land, Will Scott, 28. August 2026, ohne Link). Zwei Websites, ein Autor, keine Stichprobengröße.
  • Rank Math veröffentlichte am 27. Juli 2026 einen offiziellen MCP, aktualisiert am 16. September 2026. Nach dieser Ankündigung: OAuth, Audits, Metadaten und Schema lesend und schreibend, Keyword-Daten aus der Search Console, ein Teil nur in PRO. Welche Beiträge eine Autorisierung umfasst und wie oft ein automatischer Schreibvorgang danebengeht, nennt keine der hier zitierten Quellen.
  • Der einzige datierte Nachbarfall unter den in dieser Lektion zitierten Quellen zur Frage, ob eine Einstellung in der Oberfläche einen Konnektor bindet, betrifft einen Crawler: eine URL pro Sekunde eingestellt, 500 URLs in 20 Sekunden abgerufen (Rich Voller, 26. Mai 2026, aktualisiert 12. Juni 2026, ohne veröffentlichte Stichprobengröße). Über WordPress entscheidet er nichts.
  • Zwölf Rückgabewerte sind belegt, die 1 bleibt frei, und die Verweigerung ist 11 – nie 0.
  • Googles Spam-Richtlinien (Stand 28. August 2026, ohne Link) bestimmen scaled content abuse über den Zweck und nennen keine Schwelle. Wo die Grenze verläuft, entscheidet diese Seite nicht.

Häufige Fragen

Was mache ich, wenn die Stapelprüfung 11 zurückgibt?

An deiner Website ist nichts passiert: 11 heißt, der Stapel trägt Befunde und nichts wurde geschrieben. Die drei Blöcke sagen, aus welchem Vergleich jeder Befund kommt – Dubletten aus Rumpftexten, Kollisionen aus Adressen, Stapelmass aus Eigenschaften ohne Eintrag. Nur BESTANDSDUBLETTE und SLUG-BELEGT hängen am Abzug des Bestands; ist der veraltet, prüfe ihn zuerst.

Woher nehme ich den Bestandsabzug?

Die Prüfung verlangt je veröffentlichter Seite zwei Felder, pfad und text, und liest sie aus einer Datei; woher die Datei kommt, prüft das Skript nicht. Ein Abzug nur der Seiten in der Nachbarschaft des Stapels reicht aus. Der Preis steht dazu: was nicht im Abzug ist, kann keine BESTANDSDUBLETTE erzeugen, und diese Lücke meldet die Prüfung nicht, weil sie sie nicht sehen kann.

Warum fünf Wörter und nicht einzelne Wörter?

Weil einzelne Wörter sich in zwei Texten derselben Branche fast immer überschneiden: zwei ehrlich verschiedene Seiten über Notenpulte teilen Dutzende Wörter und sind trotzdem zwei Seiten. Fünf aufeinanderfolgende Wörter sind dagegen eine Formulierung, und die wiederholt sich selten zufällig. Der Preis ist die andere Richtung: zwei Texte um dieselbe leere Aussage herum bestehen beide.

Quellen

(ohne Link) heißt: Publikation, Autor und Datum stehen da, die URL nicht – und kein Nachdruck tritt an ihre Stelle.

  • Rank Math, Bhanu Ahluwalia, „Rank Math MCP: AI-Powered SEO Automation for WordPress“, 27. Juli 2026, aktualisiert 16. September 2026. OAuth, Audits, Metadaten und Schema lesend und schreibend, Keyword-Daten aus der Search Console, Teile nur in PRO. Produktankündigung ohne Fehlerquote, ohne Stichprobengröße und ohne Angabe, welche Beiträge eine einzelne Autorisierung umfasst.
  • Rich Voller, „Screaming Frog v24 MCP: The Complete Agency Guide“, 26. Mai 2026, aktualisiert 12. Juni 2026. Oberflächeneinstellung: eine URL pro Sekunde. Tatsächlich abgerufen: 500 URLs in 20 Sekunden. Praxisbericht ohne veröffentlichte Stichprobengröße, über einen Crawler und nicht über ein CMS.
  • Keyword.com, State of AI in SEO 2026, 1. Januar 2026, n = 97 auswertbare Antworten, selbst gewählte Teilnahme, ohne veröffentlichte Fehlermarge: eine Person von 97 vollständig automatisiert; 57 % „Quality not good enough“, gezählt innerhalb der zurückhaltenden Gruppe. Die Seite trägt zwei weitere Prozentwerte, die hier nicht gemeint sind: 70 % betreffen Einschränkungen, 57 % die Nutzung von ChatGPT.
  • Search Engine Land, Will Scott, 28. August 2026. Neue Adressen: /seo-grader und /content-grader. Unterschied zur Startseite: der Title-Tag. Impressionen je neuer Adresse: 0. Klicks je neuer Adresse: 0. Rangplatz der Ausgangsseite danach: 9 oder schlechter. Zweite betroffene Website: ScryPrice. Stichprobengröße: nicht veröffentlicht. (ohne Link)
  • Google Search Central, Richtlinien zu Spam in der Suche, Abschnitt scaled content abuse, Stand 28. August 2026: maßgeblich ist der Zweck; zur Herstellungsweise und zu einer Obergrenze steht dort nichts. (ohne Link)
  • Skript, Eingabedateien und Läufe sind original für diese Lektion. Die Dateien des Notenversands Kirnbach sind erfunden, die Urteile hängen allein von den hier abgedruckten Dateien ab, und die Schwelle 0,60 ist eine eigene Wahl.

Weiter im Kurs

Die Prüfung je Eintrag, die diese Stapelprüfung voraussetzt, steht in Claude Code für technisches SEO. Was Claude überhaupt leistet, bevor es etwas anfassen darf, steht in Was Claude im SEO leisten kann – und was nicht.