← Alle Artikel

HTTP 200, aber blockiert: So erkennst du Soft-Blocks beim Web Scraping

Dein Scraper beendet einen Durchlauf. Jede Anfrage lieferte HTTP 200. Keine Ausnahmen, keine Wiederholungen, keine Fehler im Log. Dann öffnest du das Ergebnis und die Hälfte der Datensätze fehlt, Preise sind leer und einige Zeilen enthalten den Satz “Bitte bestätige, dass du ein Mensch bist”. Der Statuscode hat dich angelogen.

Das ist einer der teuersten Fehlerfälle beim Web Scraping, weil er lautlos ist. Ein harter Block (403, 429, ein zurückgesetzter Verbindungsaufbau) ist laut: Du siehst ihn, du behandelst ihn. Ein Soft-Block liefert 200 mit einem Body, der nicht die angeforderte Seite ist, also läuft dein Parser, findet nichts und macht weiter. Das Scraping “gelingt”, während dein Datensatz still verrottet.

Dieser Leitfaden behandelt, wie Soft-Blocks von deiner Seite aussehen, warum Websites sie ausliefern und wie du sie mit Open-Source-Werkzeugen zuverlässig erkennst, bevor schlechte Daten deine Datenbank erreichen.

Was ein Soft-Block wirklich ist

Ein Soft-Block ist jede Antwort, die einen Erfolgs-Statuscode trägt, aber nicht den angeforderten Inhalt enthält. Der Server hat entschieden, dass du wie ein Bot aussiehst, aber statt dich rundheraus abzuweisen, gibt er dir etwas anderes zurück und markiert es als 200 OK. Häufige Varianten:

  • Eine Challenge- oder Interstitial-Seite. Der Body ist eine JavaScript-Challenge, eine “Überprüfe deinen Browser”-Warteseite oder eine CAPTCHA-Wand. Status: 200. Inhalt: nicht deine Daten.
  • Eine Zustimmungs- oder Regionswand. Ein Cookie- oder DSGVO-Zustimmungsbildschirm erscheint statt des Artikels. Der echte Inhalt liegt hinter einer Interaktion, die du nie ausgeführt hast.
  • Eine gekürzte oder teilweise Seite. Du bekommst das Gerüst der Seite (Kopf, Fuß, Layout), aber das Produktraster, die Angebotstabelle oder der Artikeltext ist leer oder abgeschnitten. Manchmal bekommst du 3 Einträge, obwohl die Seite eigentlich 60 hat.
  • Eine generische “Etwas ist schiefgelaufen”-Seite. Eine freundliche Fehlerseite, mit 200 ausgeliefert, damit Monitoring-Tools sie nicht markieren.
  • Eine Honeypot- oder Köderantwort. Gefälschte oder zufällige Daten, die plausibel wirken sollen, aber nutzlos sind.

Der gemeinsame Nenner: Die HTTP-Ebene sagt, alles sei in Ordnung, und nur der Inhalt sagt die Wahrheit.

Warum Websites 200 statt 403 ausliefern

Es wirkt widersinnig. Wenn eine Website dich nicht bedienen will, warum nicht einfach nein sagen? Drei praktische Gründe:

  1. Es legt naive Scraper lautlos lahm. Viele Scraper prüfen nur response.status_code. Ein 200 mit Müll bedeutet, dass diese Scraper ihre Wiederholungs- oder Alarmlogik nie auslösen. Deine Pipeline verschlechtert sich, statt zu scheitern, was schwerer zu bemerken und zu debuggen ist.
  2. Es schützt echte Nutzer. Anti-Bot-Systeme sind nicht perfekt und markieren manchmal echte Besucher. Eine sanfte Challenge (löse dies, klick hier) ist ein freundlicherer Rückfall als ein harter 403, der einen echten Kunden aussperren würde.
  3. Es verwischt dein Signal. Kämen Blocks immer als 403 zurück, könntest du deine Blockrate genau messen und dich anpassen. Blocks in 200er zu mischen lässt deine Erfolgsmetriken gesünder aussehen, als sie sind, was deine Reaktion verlangsamt.

Das Motiv zu verstehen ist wichtig, weil es dir sagt, wo du hinschauen musst: Vertraue nie allein dem Statuscode und validiere immer den Body.

Wie du Soft-Blocks erkennst

Erkennung läuft auf ein Prinzip hinaus: Validiere den Inhalt, nicht nur den Transport. Hier ein mehrschichtiger Ansatz, die günstigsten Prüfungen zuerst.

1. Prüfe die Antwortgröße

Das schnellste Signal. Eine echte Produktseite hat vielleicht 200-500 KB HTML. Eine Challenge-Seite oder ein gekürztes Gerüst ist oft ein Bruchteil davon. Erfasse einen Basiswert für jeden Seitentyp und markiere alles, was deutlich darunter liegt.

import requests

resp = requests.get(url, headers=headers, timeout=20)
size = len(resp.content)

if size < 15_000:  # pro Website anpassen
    raise ValueError(f"Verdaechtig kleine Antwort: {size} Bytes")

Die Größe allein erzeugt Fehlalarme, behandle sie also als ersten Filter, nicht als Urteil.

2. Suche nach Block-Signaturen

Suche nach Phrasen und Markern, die nur auf Challenge- oder Fehlerseiten vorkommen. Führe eine Liste pro Website, denn der Wortlaut variiert.

BLOCK_MARKERS = [
    "verify you are human",
    "checking your browser",
    "enable javascript and cookies",
    "access denied",
    "unusual traffic",
    "captcha",
    "cf-challenge",
    "px-captcha",
]

body = resp.text.lower()
if any(marker in body for marker in BLOCK_MARKERS):
    raise ValueError("Block-Signatur im Body gefunden")

Das fängt die Mehrheit der Interstitial- und Challenge-Seiten ab. Aktualisiere die Liste, wann immer du eine neue Wand siehst.

3. Prüfe zusichernd auf den erwarteten Inhalt

Die zuverlässigste Prüfung ist positiv, nicht negativ. Suche nicht nur nach Anzeichen eines Blocks, sondern bestätige, dass das, wofür du gekommen bist, tatsächlich da ist. Wenn du eine Produktseite scrapest, muss der Preis-Selektor auflösen. Wenn du eine Angebotsseite scrapest, muss die Anzahl der Einträge in einem sinnvollen Bereich liegen.

from parsel import Selector

sel = Selector(resp.text)
items = sel.css("div.product-card")

if len(items) < 5:
    raise ValueError(f"Nur {len(items)} Eintraege - volle Seite erwartet")

price = sel.css("span.price::text").get()
if not price:
    raise ValueError("Preis-Selektor leer - Seite wahrscheinlich unvollstaendig")

Das fängt die fieseste Variante ab: die Teilseite. Eine gekürzte Antwort hat keine Block-Marker und kann eine normale Größe haben, aber die Daten sind schlicht nicht da. Nur eine positive Zusicherung erkennt sie.

4. Erkenne teilweise und inkonsistente Seiten

Manche Websites liefern die volle Seite meistens und zufällig eine gekürzte Version, besonders unter Last oder bei Verdacht auf Automatisierung. Dieselbe URL gibt dir bei einer Anfrage 60 Einträge und bei der nächsten 3, beide mit Status 200.

Wehre dich mit einer Vollständigkeits-Heuristik und einem begrenzten Wiederholungsversuch:

def fetch_complete(url, min_items=20, attempts=3):
    best = None
    for _ in range(attempts):
        resp = requests.get(url, headers=headers, timeout=20)
        sel = Selector(resp.text)
        items = sel.css("div.product-card")
        if best is None or len(items) > len(best):
            best = items
        if len(items) >= min_items:
            return items  # gut genug, frueh aufhoeren
    return best  # gib das vollstaendigste Rendering zurueck, das wir sahen

Den vollständigsten von mehreren Versuchen zu behalten, statt des ersten, macht aus instabilen Teilantworten nutzbare Daten.

5. Vergleiche mit einem bekannten Referenzwert

Speichere für hochwertige Seiten eine Momentaufnahme einer gesunden Antwort: ihre Größe, ihre Eintragszahl, das Vorhandensein wichtiger Selektoren. Vergleiche bei jedem Durchlauf mit dem Referenzwert und schlage Alarm, wenn die Form abweicht. Ein plötzlicher Rückgang der Eintragszahl um 80% über viele Seiten hinweg ist ein weit besseres Block-Signal als jede einzelne Anfrage für sich.

Mach aus der Erkennung einen wiederverwendbaren Wächter

Verstreue diese Prüfungen nicht über deine Spider. Verpacke sie in eine einzige Validierungsfunktion, die jede Antwort vor dem Parsen durchläuft. Scheitere laut (wirf eine Ausnahme, logge die URL und leite in eine Wiederholungs-Queue), damit ein Soft-Block zu einem sichtbaren Ereignis statt zu stillem Datenverlust wird.

def validate(resp, min_items=5):
    if len(resp.content) < 15_000:
        raise ValueError("Antwort zu klein")
    body = resp.text.lower()
    if any(m in body for m in BLOCK_MARKERS):
        raise ValueError("Block-Signatur")
    sel = Selector(resp.text)
    if len(sel.css("div.product-card")) < min_items:
        raise ValueError("Inhalt fehlt")
    return sel

Ein einziger Engpass bedeutet einen einzigen Ort, um Schwellen anzupassen und neue Signaturen hinzuzufügen, während sich Websites ändern.

Wenn Erkennung nicht ausreicht

Erkennung sagt dir, dass eine Anfrage gescheitert ist. Sie behebt nicht den zugrunde liegenden Grund, warum du markiert wurdest. Wenn deine validierte Blockrate stetig steigt (etwa von 1% auf 8% innerhalb einer Woche über viele Ziele), ist das meist die Website, die ihre Anti-Bot-Haltung verschärft, kein Fehler in deinem Parser. An diesem Punkt sind die Hebel auf Leserseite die üblichen: IPs rotieren, realistische Browser-Fingerabdrücke mit Werkzeugen wie curl_cffi oder einem nicht erkennbaren Browser nutzen, menschenähnliche Verzögerungen einbauen und JavaScript mit Playwright oder Selenium rendern, wenn der Inhalt es erfordert.

Ab einer gewissen Größe wird es zu einem eigenen Projekt, all das selbst zu pflegen. Ein verwalteter Unblocking-Dienst wie ScrapeUnblocker übernimmt Fingerprinting, Rotation und Rendering hinter einem einzigen API-Aufruf, sodass du die echte Seite zurückbekommst und deine Validierungslogik auf Datenqualität statt auf Blockvermeidung ausrichten kannst. Mehr dazu, wie er in einen Scraping-Stack passt, findest du in der Entwicklerdokumentation.

FAQ

Bedeutet ein Statuscode 200, dass mein Scraping funktioniert hat? Nein. Ein 200 bedeutet nur, dass der Server mit irgendetwas geantwortet hat. Es sagt nichts darüber aus, ob dieses Etwas der gewünschte Inhalt ist. Validiere immer den Body.

Wie unterscheide ich einen Soft-Block von einer wirklich leeren Seite? Nutze eine positive Zusicherung, die an den Seitentyp gebunden ist, plus einen Referenzwert. Wenn eine Kategorieseite normalerweise 50 Einträge hat und 0 zurückgibt, ist das ein Block oder ein Parser-Fehler, keine leere Kategorie. Gleiche das mit der Antwortgröße und den Block-Signaturen ab.

Warum bekomme ich für dieselbe URL unterschiedliche Ergebnisse? Manche Websites liefern unter Last oder bei Verdacht auf Automatisierung teilweise oder zufällige Antworten. Frage mehrmals ab und behalte das vollständigste Rendering, und logge die Schwankung, um zu erkennen, wann es schlimmer wird.

Sollte ich einen Soft-Block sofort wiederholen? Ein begrenzter Wiederholungsversuch (2-3 Versuche) hilft bei instabilen Teilseiten. Bleibt der Block bestehen, verschwendet die gleiche Wiederholung nur Anfragen: Ändere zuerst etwas, etwa deine IP, deine Header oder deinen Rendering-Ansatz.

Kernpunkte

  • Statuscodes beschreiben den Transport, nicht den Inhalt. Vertraue nie allein dem 200.
  • Schichte deine Prüfungen: Antwortgröße, Block-Signaturen und, vor allem, positive Zusicherungen auf die erwarteten Daten.
  • Teilseiten sind der hinterhältigste Soft-Block. Nur eine Vollständigkeitsprüfung des Inhalts erkennt sie.
  • Zentralisiere die Validierung in einem Wächter, damit jede Antwort geprüft wird, bevor sie deinen Parser erreicht.
  • Eine steigende validierte Blockrate ist ein Signal, den Ansatz zu ändern, nicht nur härter zu wiederholen.

Behandle jede Antwort als schuldig, bis ihre Vollständigkeit bewiesen ist, und Soft-Blocks hören auf, lautlos zu sein.

ScrapeUnblocker kostenlos testen

Über 95 % Erfolgsquote · ab 0,55 € pro 1.000 Aufrufe · 500 kostenlose Anfragen bei der Registrierung.

Kostenlos testen → Preise ansehen