Zurück zum Blog
Die Wissenschaft des Web-Scrapings
Suciu DanLast updated on Apr 30, 202627 min read

Wie man einen Python Web Crawler erstellt: Vom Start bis zur Skalierung

Wie man einen Python Web Crawler erstellt: Vom Start bis zur Skalierung
Kurzfassung: Ein Python-Webcrawler automatisiert die mühsame Arbeit, Links auf einer Website zu verfolgen, um Inhalte zu entdecken und zu sammeln. Dieser Leitfaden führt Sie durch die Erstellung eines solchen Crawlers von Grund auf mit „requests“ und „BeautifulSoup“ und geht anschließend auf „Scrapy“ über, um paralleles Crawling, Item-Pipelines und den Export strukturierter Daten zu ermöglichen. Außerdem lernen Sie, wie man verantwortungsbewusst crawlt, Proxys wechselt, um Sperren zu vermeiden, und mit JavaScript-gerenderten Seiten umgeht.

Ein Python-Webcrawler ist ein Programm, das automatisch durch Websites navigiert, indem es Hyperlinks folgt, neue Seiten entdeckt und dabei deren Inhalte sammelt. Während es beim Web-Scraping darum geht, bestimmte Datenpunkte von einer einzelnen Seite zu extrahieren, geht es beim Web-Crawling darum, eine gesamte Website (oder sogar mehrere Websites) zu durchlaufen, um diese Seiten überhaupt erst zu finden.

Python ist wohl die beliebteste Sprache für diese Aufgabe. Dank seiner lesbaren Syntax, bewährten HTTP-Bibliotheken und einem Framework, das buchstäblich nach Webspinnen benannt ist, macht das Ökosystem das Crawling zugänglich, ohne dabei an Leistungsfähigkeit einzubüßen. Ganz gleich, ob Sie jede Produktseite einer E-Commerce-Website erfassen, einen Backlink-Index für die SEO-Analyse erstellen oder strukturierte Daten in Machine-Learning-Pipelines einspeisen müssen – ein gut konzipierter Crawler ist der Motor, der den gesamten Prozess antreibt.

Dieses Tutorial behandelt den gesamten Lebenszyklus der Erstellung eines Web-Crawlers in Python: das Abrufen Ihrer ersten Seite mit requests, das Parsen und Extrahieren von Links mit BeautifulSoup und die anschließende Skalierung mit Scrapys Spidern, Selektoren und Item-Pipelines. Dabei lernen Sie, wie Sie mit Sonderfällen wie relativen URLs und JSON-APIs umgehen, robots.txt beachten, Ihre Anfragen drosseln und vermeiden, von Anti-Bot-Systemen blockiert zu werden. Jeder Abschnitt enthält lauffähigen Code, den Sie kopieren, anpassen und für Ihre eigenen Projekte erweitern können. Am Ende haben Sie einen klaren Weg von einem 20-zeiligen Prototyp zu einer produktionsreifen Crawling-Pipeline vor sich.

Was ist ein Python-Webcrawler und warum sollte man einen erstellen?

Im Kern ist ein Python-Webcrawler ein automatisiertes Skript, das von einer oder mehreren Start-URLs ausgeht, den Seiteninhalt abruft, jeden gefundenen Link extrahiert und diesen Zyklus dann für jede neue URL wiederholt. Stellen Sie sich das wie einen methodischen Besucher vor, der das Verzeichnis auf jeder Etage eines Gebäudes liest, bevor er entscheidet, welche Räume er als Nächstes betreten soll.

Die Unterscheidung zwischen Crawling und Scraping verwirrt die Leute ständig. Crawling ist die Entdeckungsphase: das Auffinden von Seiten durch das Durchlaufen des Linkgraphen. Scraping ist die Extraktionsphase: das Extrahieren strukturierter Felder (Titel, Preise, Daten) aus Seiten, die Sie bereits gefunden haben. In der Praxis benötigen die meisten Projekte beides, aber es handelt sich um getrennte Aufgaben mit unterschiedlichen Anforderungen an die Tools. Das Verständnis dieser Unterscheidung hilft Ihnen, die richtigen Tools auszuwählen und Ihr Projekt richtig zu strukturieren.

Warum also eines in Python erstellen? Hier sind einige konkrete Gründe:

  • SEO-Audits und Backlink-Mapping: Crawlen Sie Ihre eigene Website, um defekte Links, verwaisten Seiten oder fehlende Meta-Tags zu finden. Sie können auch Blogs, Partner-Websites und Nachrichtenportale durchsuchen, um herauszufinden, wer auf Sie oder Ihre Konkurrenten verlinkt.
  • Datenerfassung für ML und Analysen: Sammeln Sie Trainingsdaten von Hunderten von Seiten und leiten Sie diese direkt in Pandas-DataFrames, Feature Stores oder LLM-Trainingspipelines weiter. Die strukturierte Ausgabe eines gut konzipierten Crawlers fließt direkt in die nachgelagerte Analyse ein.
  • Preis- und Bestandsüberwachung: Durchsuchen Sie jede Nacht Produktkategorieseiten, um Preisänderungen und Lagerbestände bei Tausenden von Artikelnummern zu verfolgen.
  • Forschung und Archivierung: Wissenschaftliche Forscher crawlen Foren, Regierungsdatenbanken und öffentliche Datensätze, die keine APIs für den Massen-Download anbieten.
  • Inhaltsaggregation: Nachrichtenagenturen und Marktforschungsunternehmen crawlen Branchenwebsites, um kuratierte Feeds und Dashboards für Wettbewerbsinformationen zu erstellen.

Dank des Python-Ökosystems (requests, BeautifulSoup, Scrapy und viele mehr) können Sie einen funktionierenden Crawler in weniger als 30 Zeilen prototypisieren und dieselbe Logik dann auf Millionen von Seiten skalieren, ohne die Sprache zu wechseln. Genau diesen Weg vom Prototyp zur Produktion behandelt dieser Leitfaden.

Wie Web-Crawler unter der Haube funktionieren

Jeder Python-Webcrawler, vom zehnzeiligen Skript bis zum verteilten System, folgt derselben grundlegenden Schleife:

  1. Beginnen Sie mit Start-URLs. Sie geben eine oder mehrere Startadressen an. Diese werden in eine Warteschlange (oft als „Frontier“ bezeichnet) gestellt.
  2. Die Seite abrufen. Der Crawler sendet eine HTTP-GET-Anfrage für die nächste URL in der Warteschlange und empfängt die HTML-Antwort.
  3. Das HTML wird geparst. Ein Parser (BeautifulSoup, lxml, Scrapy-Selektoren) liest das Dokument und stellt dessen Struktur als durchsuchbaren Baum dar.
  4. Extrahieren von Links. Der Parser extrahiert jeden <a href="..."> von der Seite sowie alle anderen auffindbaren URLs.
  5. Filtern und Duplikate entfernen. Nicht jeder Link ist es wert, verfolgt zu werden. Der Crawler gleicht jede URL mit einer Liste bereits bekannter URLs ab, wendet Domain- oder Pfadfilter an und verwirft Duplikate. Dieser Schritt umfasst auch die URL-Normalisierung: Entfernen von Fragmenten, Sortieren von Abfrageparametern und Umwandeln von Pfaden in Kleinbuchstaben, sodass example.com/Page und example.com/page nicht als unterschiedliche URLs behandelt werden.
  6. Neue URLs in die Warteschlange stellen. Die verbleibenden Links werden in die Frontier-Warteschlange aufgenommen.
  7. Wiederholen, bis die Warteschlange leer ist oder eine Stoppbedingung erfüllt ist (maximale Tiefe, maximale Seitenanzahl, Zeitlimit).

Diese Schleife ist trügerisch einfach, aber die eigentliche Technik steckt im Detail. Wie gehen Sie mit Seiten um, die eine 301-Weiterleitung zu einer URL zurückgeben, die Sie bereits besucht haben? Was passiert, wenn der Server langsam ist und 500 URLs in der Warteschlange stehen? Wie vermeiden Sie es, denselben Inhalt unter verschiedenen URL-Mustern (Sitzungs-IDs, Tracking-Parameter, Kalender-Widgets) zu crawlen?

Eine naive Implementierung ruft URLs einzeln ab, was für ein paar Dutzend Seiten in Ordnung ist. Sobald Sie Tausende crawlen müssen, benötigen Sie Parallelität (mehrere gleichzeitig laufende Anfragen), persistente Warteschlangen und eine Wiederholungslogik für vorübergehende Fehler. Einfache Crawler, denen Wiederholungsversuche fehlen und die Seiten nacheinander abrufen, sind für den Einsatz im Produktionsmaßstab wirklich ungeeignet. Genau diese Lücke sollte Scrapy schließen: Es bietet Ihnen eine asynchrone Engine, einen integrierten Scheduler mit Deduplizierung und Middleware-Hooks für jede Phase der Schleife.

Das Verständnis dieser Schleife ist nicht nur theoretisch. Wenn sich Ihr Crawler fehlerhaft verhält (Seiten überspringt, dieselbe URL erneut aufruft oder fast zum Stillstand kommt), lässt sich der Fehler fast immer auf einen dieser sieben Schritte zurückführen. Die Fehlerdiagnose wird wesentlich schneller, wenn Sie genau feststellen können, in welcher Phase der Fehler auftritt.

Die Wahl des richtigen Python-Crawling-Tools

Bevor Sie auch nur eine einzige Zeile Code schreiben, lohnt es sich, die richtige Bibliothek für den Umfang und die Komplexität Ihres Projekts auszuwählen. Hier ist eine praktische Entscheidungsmatrix für die Erstellung eines Web-Crawlers in Python:

Kriterien

requests + BeautifulSoup

Scrapy

Verwalteter API-Dienst

Einrichtungszeit

Minuten

15–30 Min. (Projektaufbau)

Minuten (API-Schlüssel)

Parallelität

Manuell (Threads/Asyncio)

Integrierte Async-Engine

Wird für Sie übernommen

Deduplizierung

Sie erstellen sie

Integrierter Scheduler-Filter

Wird für Sie erledigt

JS-Rendering

Nicht unterstützt

Erfordert ein Plugin (z. B. scrapy-playwright)

Oft enthalten

Datenexport

Manuell (in Datei schreiben)

CLI-Flags für JSON/CSV

Variiert je nach Anbieter

Anti-Bot-Handhabung

DIY (Header, Proxys)

Middleware-Hooks

Integrierte Proxy-Rotation, CAPTCHA-Lösung

Am besten geeignet für

Kleine, einmalige Crawls

Mittlere bis große, wiederkehrende Crawls

Websites mit starker Bot-Abwehr oder JS-Rendering

requests + BeautifulSoup ist die erste Wahl, wenn Sie einen schnellen Prototyp oder einen Crawler benötigen, der nur eine Handvoll Seiten abfragt. Sie kontrollieren jedes Detail, was zum Lernen ideal, für die Skalierung jedoch ungeeignet ist. Auf diese Weise erstellte einfache Crawler besuchen oft dieselben Seiten erneut oder bleiben ohne sorgfältige Deduplizierungslogik beim Verfolgen wiederholter Links hängen.

Scrapy ist ein komplettes Framework, das speziell für das Web-Crawling in großem Maßstab entwickelt wurde. Es bewältigt Parallelität, Wiederholungsversuche, Deduplizierung und Datenpipelines von Haus aus. Der Nachteil ist eine steilere Lernkurve und eine festgelegte Projektstruktur. Aber sobald du das Spider-/Pipeline-Muster verinnerlicht hast, geht das Erstellen neuer Crawler bemerkenswert schnell.

Verwaltete API-Dienste sind sinnvoll, wenn der schwierige Teil Ihres Crawls nicht die Parsing-Logik, sondern die Infrastruktur ist: rotierende Proxys, das Lösen von CAPTCHAs, das Rendern von JavaScript. Anstatt diesen Stack selbst zu verwalten, senden Sie eine Anfrage und erhalten HTML (oder JSON) zurück.

Wählen Sie die einfachste Option, die Ihren Anforderungen entspricht. Sie können später jederzeit upgraden, und dieser Leitfaden zeigt Ihnen, wie Sie die einzelnen Stufen durchlaufen.

Einrichten Ihrer Python-Umgebung

Eine saubere Umgebung verhindert Abhängigkeitskonflikte und sorgt dafür, dass Ihr Projekt reproduzierbar bleibt. Hier ist die minimale Konfiguration für Ihr Python-Webcrawler-Projekt:

# Create and activate a virtual environment
python3 -m venv crawler-env
source crawler-env/bin/activate   # macOS / Linux
crawler-env\\Scripts\\activate      # Windows

# Install core libraries
pip install requests beautifulsoup4 lxml scrapy

Dein Projektordner sollte in etwa so aussehen:

my-crawler/
├── crawler-env/
├── simple_crawler.py      # requests + BS4 version
├── scrapy_project/        # generated by scrapy startproject
│   ├── scrapy_project/
│   │   ├── spiders/
│   │   ├── items.py
│   │   ├── pipelines.py
│   │   └── settings.py
│   └── scrapy.cfg
└── requirements.txt

Fixiere deine Abhängigkeiten mit pip freeze > requirements.txt , damit jeder, der das Repo klont, die gleichen Versionen erhält. Wenn du planst, Scrapy mit einem Headless-Browser für JavaScript-gerenderte Seiten zu verwenden, füge auch scrapy-playwright zur Installationsliste hinzu.

lxml ist als Parser-Backend für BeautifulSoup enthalten. Es ist deutlich schneller als Pythons integriertes html.parser und geht eleganter mit fehlerhaftem HTML um, was wichtig ist, wenn du Seiten crawlen, deren Markup eindeutig nicht von Menschen geschrieben wurde.

Sobald diese Pakete installiert sind, können Sie mit dem Programmieren beginnen. Im nächsten Abschnitt erstellen wir einen vollständig funktionsfähigen Crawler von Grund auf.

Erstellen eines einfachen Python-Web-Crawlers mit Requests und BeautifulSoup

Zeit, den eigentlichen Code zu schreiben. Das Ziel dieses ersten Crawlers ist einfach: Ausgehend von einer Start-URL die Seite abrufen, alle darauf befindlichen Links finden und dann jeden dieser Links aufrufen, während man auf derselben Domain bleibt. Er ist bewusst minimalistisch gehalten, damit Sie die einzelnen Komponenten verstehen, bevor Sie die Komplexität erhöhen.

import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin, urlparse
import time

def crawl(seed_url, max_pages=20, delay=1):
    visited = set()
    queue = [seed_url]
    allowed_domain = urlparse(seed_url).netloc

    while queue and len(visited) < max_pages:
        url = queue.pop(0)
        if url in visited:
            continue

        try:
            response = requests.get(
                url,
                headers={"User-Agent": "MyCrawler/1.0 (contact@example.com)"},
                timeout=10,
            )
            response.raise_for_status()
        except requests.RequestException as e:
            print(f"Failed to fetch {url}: {e}")
            continue

        visited.add(url)
        print(f"Crawled: {url} ({response.status_code})")

        soup = BeautifulSoup(response.text, "lxml")

        for anchor in soup.find_all("a", href=True):
            link = urljoin(url, anchor["href"])
            parsed = urlparse(link)
            # Strip fragments and stay on the same domain
            clean_link = parsed._replace(fragment="").geturl()
            if parsed.netloc == allowed_domain and clean_link not in visited:
                queue.append(clean_link)

        time.sleep(delay)  # Be polite

    print(f"Done. Visited {len(visited)} pages.")
    return visited

if __name__ == "__main__":
    crawl("https://example.com")

Gehen wir die wichtigsten Entscheidungen in diesem Python-Web-Crawler durch:

  • visited set: Dies ist Ihr Mechanismus zur Duplikatsbereinigung. Bevor Sie eine URL abrufen, prüfen Sie, ob sie bereits im Set enthalten ist. Ohne dies würde der Crawler auf Websites mit zirkulären Navigationslinks endlos in einer Schleife laufen. Selbst kleine Websites können Navigationsmenüs haben, die Zyklen erzeugen.
  • urljoin: Konvertiert relative Pfade wie /about in absolute URLs wie example.com/about. Dies ist entscheidend, da die meisten Websites relative hrefs in ihrer Navigation und ihren internen Verlinkungen verwenden.
  • Domänenfilterung: Die urlparse Prüfung hält den Crawler auf einer einzigen Domain. Ohne sie könnte ein einziger externer Link Ihren Crawler auf eine endlose Reise durch das gesamte Internet schicken. Das ist der Unterschied zwischen einem fokussierten und einem unkontrollierten Crawl.
  • Fragment-Stripping: Der _replace(fragment="") Aufruf entfernt #section Anker aus URLs. Diese verweisen auf verschiedene Positionen auf derselben Seite, nicht auf verschiedene Seiten; sie als separate URLs zu behandeln, würde daher zu redundanten Abrufen führen.
  • max_pages cap: Ein Sicherheitsnetz, das besonders während der Entwicklung wichtig ist. Sie möchten nicht versehentlich Tausende von Anfragen auslösen, während Sie Ihren Parser debuggen.
  • time.sleep(delay): Eine grundlegende Höflichkeitsmaßnahme. Selbst eine Sekunde Pause zwischen den Anfragen macht einen erheblichen Unterschied für die Auslastung des Zielservers.
  • Benutzerdefinierter User-Agent: Die Identifizierung Ihres Crawlers mit einem aussagekräftigen Header (einschließlich Kontaktinformationen) ist sowohl ethisch als auch praktisch. Websites blockieren einen Crawler, der sich ehrlich identifiziert, weitaus seltener.
  • Fehlerbehandlung: Der try/except Block fängt Verbindungszeitüberschreitungen, DNS-Fehler und HTTP-Fehler ab (über raise_for_status). Ein Crawler in der Produktion benötigt dies; ein abgestürzter Prozess bedeutet verlorenen Fortschritt und potenziell unvollständige Daten.

Dieser Crawler arbeitet synchron, d. h. er ruft jeweils nur eine Seite ab. Bei 20 Seiten ist das völlig in Ordnung. Bei 2.000 wäre es quälend langsam. Wir werden diese Einschränkung behandeln, wenn wir später in diesem Leitfaden zu Scrapy übergehen.

Die Logik zur Link-Extraktion im Basis-Crawler funktioniert, aber Seiten in der Praxis sind unübersichtlich. Anker-Tags verweisen auf PDFs, Mailto-Adressen, leere JavaScript-Aufrufe, reine Fragment-Links und Varianten derselben Seite mit Query-Strings. Ein intelligenterer Filter bewahrt Sie davor, Anfragen an unbrauchbare URLs zu verschwenden, und sorgt dafür, dass Ihr Crawl zielgerichtet bleibt.

from urllib.parse import urljoin, urlparse

IGNORED_EXTENSIONS = {".pdf", ".jpg", ".png", ".gif", ".zip", ".exe", ".mp4"}

def extract_links(soup, base_url, allowed_domain):
    links = set()
    for anchor in soup.find_all("a", href=True):
        raw = anchor["href"]

        # Skip non-HTTP schemes
        if raw.startswith(("mailto:", "javascript:", "tel:", "#")):
            continue

        full_url = urljoin(base_url, raw)
        parsed = urlparse(full_url)

        # Strip fragments for deduplication
        clean = parsed._replace(fragment="").geturl()

        # Domain filter
        if parsed.netloc != allowed_domain:
            continue

        # Extension filter
        if any(clean.lower().endswith(ext) for ext in IGNORED_EXTENSIONS):
            continue

        links.add(clean)
    return links

Diese Funktion behandelt die häufigsten Sonderfälle beim Crawlen einer Website mit Python: Sie löst relative URLs auf, entfernt Fragment-Identifikatoren (den #section Teil, der die eigentliche Seite nicht verändert), ignoriert Nicht-HTTP-Schemas und überspringt binäre Dateiendungen. Das Ergebnis ist ein sauberer Satz von URLs derselben Domain, die für die Crawl-Warteschlange bereit sind.

Für eine noch stärkere Erkennung doppelter URLs sollten Sie in Erwägung ziehen, Abfrageparameter zu normalisieren, indem Sie sie alphabetisch sortieren. Zwei URLs, die sich nur in der Reihenfolge der Parameter unterscheiden (?a=1&b=2 vs. ?b=2&a=1) liefern in der Regel denselben Inhalt, und sie als unterschiedlich zu behandeln, verschwendet Bandbreite. Du kannst auch die Behandlung kanonischer URLs anwenden, indem du nach <link rel="canonical"> Tags im HTML-Code, die Ihnen die bevorzugte URL für einen Inhalt angeben.

Eine weitere nützliche Technik ist die Erkennung von URL-Mustern. Wenn Sie feststellen, dass der Crawler Tausende von URLs generiert, die einem Muster wie /calendar?date=2024-01-01, /calendar?date=2024-01-02usw., können Sie eine Regex-Sperrliste hinzufügen, um diese Pfade zu unterbinden, bevor sie überhaupt in die Warteschlange gelangen.

Umgang mit relativen URLs und Randfällen

Relative URLs sind die häufigste Fehlerquelle bei einem erstmalig erstellten Python-Webcrawler. Eine Seite unter example.com/blog/ könnte Links wie ../about, ./post-1oder sogar //cdn.example.com/image.png. Python urllib.parse.urljoin behandelt all diese Fälle korrekt, weshalb sie bisher in jedem Code-Beispiel vorkam.

Achten Sie neben relativen Pfaden auch auf diese Sonderfälle:

  • Weiterleitungsketten: Eine 301- oder 302-Weiterleitung bedeutet, dass die endgültige URL von der von Ihnen angeforderten abweicht. Verwenden Sie response.url (nicht die ursprüngliche Anfrage-URL), wenn Sie die Seite zu Ihrem besuchten Set hinzufügen, da Sie sonst dieselbe Seite unter verschiedenen Adressen zweimal crawlen.
  • Soft-404s: Manche Websites geben einen 200-Status zurück, liefern aber einen generischen „Seite nicht gefunden“-Body. Wenn Sie Daten extrahieren, prüfen Sie auf einen Inhaltsmarker (wie einen Produkttitel), bevor Sie die Seite als gültig behandeln.
  • URL-kodierte Zeichen: %20 vs. ein literales Leerzeichen, %2F vs. /. Normalisieren Sie diese vor der Deduplizierung, um zu vermeiden, dass kodierte und unkodierte Varianten als separate URLs behandelt werden.
  • Unendliche URL-Muster: Kalender-Widgets, Session-IDs im Pfad oder Filterkombinationen können eine unbegrenzte Anzahl einzigartig aussehender URLs generieren, die alle ähnliche Inhalte liefern. Legen Sie eine maximale Crawling-Tiefe fest oder nutzen Sie die URL-Mustererkennung, um die Schleife zu durchbrechen.

Werden diese Probleme im Vorfeld gelöst, spart das später stundenlange Fehlerbehebung. Ein Crawler, der Seiten unbemerkt doppelt zählt oder Inhalte wegen eines abschließenden Schrägstrichs übersieht, ist schwieriger zu beheben als einer, der bei einer fehlerhaften URL lautstark abstürzt.

Crawling und Parsing von JSON-APIs

Nicht jede Website stellt ihre Daten als HTML bereit. Viele moderne Webanwendungen laden Inhalte aus internen APIs, die JSON zurückgeben, anstatt Daten direkt in das Seiten-Markup einzubetten. Wenn Sie die Netzwerkanfragen in den Entwicklertools Ihres Browsers überprüfen (Registerkarte „Netzwerk“, gefiltert nach XHR/Fetch), finden Sie oft Endpunkte, die Ihnen strukturierte Daten liefern, ohne dass ein HTML-Parsing erforderlich ist.

Hier ist ein Muster zum Crawlen einer paginierten JSON-API:

import requests
import json
import time

def crawl_json_api(base_url, max_pages=10, delay=1):
    all_items = []
    page = 1

    while page <= max_pages:
        response = requests.get(
            base_url,
            params={"page": page, "per_page": 50},
            headers={"Accept": "application/json"},
            timeout=10,
        )
        response.raise_for_status()
        data = response.json()

        items = data.get("results", [])
        if not items:
            break  # No more data

        all_items.extend(items)
        print(f"Page {page}: fetched {len(items)} items")

        # Check for explicit pagination metadata
        if not data.get("has_next", True):
            break

        page += 1
        time.sleep(delay)

    return all_items

# Example usage
items = crawl_json_api("https://api.example.com/products")
print(f"Total items collected: {len(items)}")

Der Ansatz ist fast identisch mit dem Crawling von HTML, mit zwei wesentlichen Unterschieden. Erstens überspringen Sie den Schritt des HTML-Parsings vollständig, da response.json() Sie ein natives Python-Wörterbuch erhalten. Zweitens ist die Paginierung in der Regel explizit: Die API gibt entweder eine next URL, ein has_next Flag zurück, oder Sie erhöhen einen Seitenparameter, bis das Ergebnis-Array leer zurückkommt.

Wenn die API eine Authentifizierung erfordert (einen API-Schlüssel oder ein Session-Token), übergebe diese über Header statt über Abfrageparameter, um zu vermeiden, dass Anmeldedaten in Server-Logs offengelegt werden. Und überprüfe immer auf Rate-Limit-Header (X-RateLimit-Remaining, Retry-After). Die Beachtung dieser Header ist sowohl höflich als auch praktisch, da der Server Sie sperrt, wenn Sie sie ignorieren.

Dieser Ansatz lässt sich gut mit Tools wie pandas kombinieren. Sobald du eine Liste von Dictionaries aus deinem JSON-Crawl hast, ist das Laden dieser in einen DataFrame zur Analyse oder zum Export nur ein einziger pd.DataFrame(items) Aufruf. Sie können dann pandas verwenden, um die gesammelten Daten zu bereinigen, zu filtern und zu analysieren oder darauf direkt Machine-Learning-Modelle zu trainieren.

Skalierung mit Scrapy

Sobald Ihre Crawling-Anforderungen über einen synchronen requests Schleife, ist Scrapy der logische nächste Schritt. Es handelt sich um ein voll ausgestattetes Python-Framework, das speziell für das Web-Crawling in großem Maßstab entwickelt wurde und die schwierigen Infrastrukturprobleme (Parallelität, Wiederholungsversuche, Deduplizierung und Drosselung) übernimmt, sodass Sie sich auf die Parsing-Logik konzentrieren können.

Die Architektur von Scrapy besteht aus fünf Kernkomponenten, die zusammenarbeiten:

  • Engine: Der zentrale Koordinator. Er leitet Anfragen an den Downloader und Antworten an die Spiders weiter und koordiniert so den gesamten Crawling-Zyklus.
  • Scheduler: Verwaltet die Anforderungswarteschlange und dedupliziert URLs automatisch mithilfe von Fingerprinting. Sie müssen niemals eine visited Satz manuell erstellen.
  • Downloader: Sendet HTTP-Anfragen asynchron mithilfe der Event-Loop von Twisted, was bedeutet, dass Hunderte von Anfragen gleichzeitig ohne Threading-Overhead ausgeführt werden können.
  • Spiders: Ihr Code. Jede Spider-Klasse definiert, mit welchen URLs begonnen wird und wie die einzelnen Antworten geparst werden. Hier befindet sich Ihre domänenspezifische Logik.
  • Item-Pipelines: Nachbearbeitungsstufen, die die von Ihren Spidern extrahierten Daten bereinigen, validieren und speichern. Sie können mehrere Pipelines für unterschiedliche Anforderungen verketten.

Was diese Architektur so leistungsstark macht, ist, dass jede Komponente austauschbar ist. Benötigen Sie benutzerdefinierte Header bei jeder Anfrage? Schreiben Sie eine Downloader-Middleware. Möchten Sie Elemente mit fehlenden Feldern verwerfen? Fügen Sie eine Validierungspipeline hinzu. Müssen die Ergebnisse in eine Datenbank statt in eine JSON-Datei geleitet werden? Tauschen Sie den Standard-Exporter gegen eine benutzerdefinierte Pipeline aus.

Scrapy kann Seiten deutlich schneller verarbeiten als eine Single-Thread-Anforderungsschleife. Das Framework verarbeitet gleichzeitige Anfragen über mehrere Domains hinweg und hält dabei die von Ihnen definierten Drosselungsgrenzen ein. Es berücksichtigt standardmäßig auch robots.txt (über die ROBOTSTXT_OBEY Einstellung), was viele selbst entwickelte Crawler oft vergessen zu implementieren.

Der Nachteil ist die Komplexität. Scrapy hat eine festgelegte Projektstruktur, eine gewisse Lernkurve und ein eigenes Vokabular (Spiders, Items, Pipelines, Middlewares). Aber sobald Sie das Muster verinnerlicht haben, können Sie bemerkenswert schnell Crawler in Produktionsqualität erstellen. Der Rest dieses Leitfadens zeigt Ihnen, wie das geht.

Ein Scrapy-Projekt und Ihren ersten Spider erstellen

Lassen Sie uns ein echtes Scrapy-Projekt aufsetzen. Öffnen Sie ein Terminal in Ihrer virtuellen Umgebung und führen Sie Folgendes aus:

scrapy startproject bookstore
cd bookstore
scrapy genspider books books.toscrape.com

Dadurch wird der vollständige Verzeichnisbaum generiert: settings.py, items.py, pipelines.py, sowie einen spiders/ Ordner mit deinem neuen books.py Spider. Der genspider Befehl füllt den Spider vorab mit den richtigen allowed_domains und einer Starter-Liste start_urls Liste. Öffnen Sie diese Spider-Datei und ersetzen Sie den Standardtext durch einen funktionierenden Parser:

import scrapy

class BooksSpider(scrapy.Spider):
    name = "books"
    allowed_domains = ["books.toscrape.com"]
    start_urls = ["https://books.toscrape.com/"]

    def parse(self, response):
        for book in response.css("article.product_pod"):
            yield {
                "title": book.css("h3 a::attr(title)").get(),
                "price": book.css(".price_color::text").get(),
                "availability": book.css(
                    ".instock.availability::text"
                ).getall()[-1].strip(),
            }

        # Follow the "next" pagination link
        next_page = response.css("li.next a::attr(href)").get()
        if next_page:
            yield response.follow(next_page, callback=self.parse)

Führen Sie den Spider mit folgendem Befehl aus:

scrapy crawl books -o books.json

Dieser einzelne Befehl startet die Engine, ruft jede paginierte Listenseite ab, extrahiert Buchdaten und schreibt die Ergebnisse in eine JSON-Datei. Keine manuelle Schleife, kein Set der besuchten Seiten, kein Boilerplate-Code zum Schreiben von Dateien. Das ist die Stärke eines Python-Webcrawlers, der auf einem geeigneten Framework basiert.

Ein paar Dinge, die bei diesem Spider zu beachten sind:

  • allowed_domains beschränkt den Crawler auf die Zielseite. Alle während des Parsens entdeckten Links außerhalb der Domain werden stillschweigend verworfen, wodurch verhindert wird, dass Ihr Spider auf externe Seiten wandert.
  • response.css() Verwendet CSS-Selektoren im Antworttext. Scrapy parst den HTML-Code einmal und speichert den geparsten Baum im Cache, sodass der Aufruf mehrerer Selektoren für dieselbe Antwort wenig Ressourcen beansprucht.
  • yield statt return: Scrapy-Spider sind Generatoren. Sie geben Elemente (Dictionaries oder Item-Objekte) und Anfragen ab. Die Engine entscheidet, wann und wie diese geplant werden, und verwaltet die Parallelität für Sie.
  • response.follow() behandelt relative URLs intern (keine urljoin erforderlich) und dedupliziert automatisch anhand des Fingerabdruckfilters des Schedulers gegenüber zuvor gesehenen URLs.

Dies ist ein vollständiger, funktionierender Webcrawling-Spider in etwa 20 Zeilen Code. Alles andere (HTTP-Verarbeitung, Planung, parallele Downloads, Export) wird vom Scrapy-Framework verwaltet. Von hier aus kannst du den Spider um tieferes Link-Folgen, zusätzliche Parsing-Callbacks und Pipeline-Verarbeitung erweitern.

Paginierung ist eines der häufigsten Muster beim Erstellen eines Python-Webcrawlers. Die meisten Listing-Seiten verteilen die Ergebnisse auf mehrere Seiten, und Ihr Spider muss diesen „Weiter“-Links folgen, bis keine mehr vorhanden sind. Der obige Scrapy-Spider demonstriert bereits den grundlegenden Ansatz, aber schauen wir uns eine robustere Version an, die tiefere Linkstrukturen mithilfe von Scrapys LinkExtractor.

import scrapy
from scrapy.linkextractors import LinkExtractor

class DeepCrawlSpider(scrapy.Spider):
    name = "deepcrawl"
    start_urls = ["https://example.com/catalog"]
    allowed_domains = ["example.com"]

    link_extractor = LinkExtractor(
        allow=r"/catalog/",
        deny=[r"/login", r"/cart", r"/account"],
    )

    def parse(self, response):
        # Extract data from the current page
        for product in response.css(".product-card"):
            yield {
                "name": product.css("h2::text").get(),
                "url": response.urljoin(product.css("a::attr(href)").get()),
                "price": product.css(".price::text").get(),
            }

        # Follow all matching links found on the page
        for link in self.link_extractor.extract_links(response):
            yield scrapy.Request(link.url, callback=self.parse)

LinkExtractor ist Scrapys Dienstprogramm zum Extrahieren von Links aus einer Seite auf Basis von Regex-Mustern. Der allow behält nur URLs, die /catalog/, während deny Login-, Warenkorb- und Kontoseiten herausfiltert, die unnötige Anfragen verursachen würden. Dies ist weitaus wartungsfreundlicher als die manuelle Programmierung von URL-Prüfungen innerhalb Ihrer parse-Methode, insbesondere wenn die Anzahl der Ausschlussmuster zunimmt.

Bei Websites, die „Load More“-Schaltflächen anstelle herkömmlicher Paginierungslinks verwenden, finden Sie in der Regel einen zugrunde liegenden API-Endpunkt auf der Registerkarte „Netzwerk“. Erstellen Sie die nächste Anfrage manuell, indem Sie einen Seiten- oder Offset-Parameter erhöhen, genau wie beim zuvor in diesem Leitfaden besprochenen Crawling-Muster für JSON-APIs.

Ein häufiger Fehler ist es, zu vergessen, ein Tiefenlimit festzulegen. Scrapys DEPTH_LIMIT -Einstellung begrenzt, wie viele Link-Hops der Crawler von der Start-URL aus verfolgt. Ohne diese Einstellung kann ein Spider auf einer großen Website Millionen von URLs in die Warteschlange stellen, bevor Sie es bemerken. Beginnen Sie während der Entwicklung mit einem konservativen Limit (3–5) und erhöhen Sie es erst, wenn Ihr Spider stabil läuft und Sie Vertrauen in Ihre Filterlogik haben.

Eine weitere nützliche Technik ist die Kombination von CrawlSpider (eine integrierte Scrapy-Klasse) mit Rule Objekten. Dieser Ansatz ermöglicht es Ihnen, Regeln für das Verfolgen von Links deklarativ zu definieren und so die Navigationslogik von der Datenextraktionslogik zu trennen. Dadurch lassen sich komplexe mehrstufige Crawls leichter nachvollziehen.

XPath vs. CSS-Selektoren für die Datenextraktion

Scrapy unterstützt sowohl CSS-Selektoren als auch XPath-Ausdrücke zum Parsen von HTML, und Sie können diese innerhalb desselben Spiders frei kombinieren. Zu wissen, wann Sie welche Variante einsetzen sollten, spart Zeit und sorgt für lesbare Selektoren.

Funktion

CSS-Selektoren

XPath

Syntax

Frontend-Entwicklern vertraut

XML-Abfragesprache

Textextraktion

::text Pseudo-Element

text() Funktion

Zugriff auf Attribute

::attr(href)

@href

Überprüfung der übergeordneten Elemente

Nicht unterstützt

.. oder ancestor:: Achse

Bedingte Logik

Eingeschränkt (:nth-child, :not)

Umfangreich (contains(), starts-with(), Boolesche Operatoren)

Lesbarkeit

Im Allgemeinen prägnanter

Ausführlicher, aber ausdrucksstärker

Verwenden Sie CSS-Selektoren, wenn Sie Elemente anhand von Klasse, ID oder einfacher Hierarchie ansprechen. Sie sind kürzer, leichter zu lesen und für die meisten Extraktionsaufgaben ausreichend:

# CSS: get all product titles
titles = response.css("h3.product-title::text").getall()

# CSS: get the href of every link inside a nav element
nav_links = response.css("nav a::attr(href)").getall()

Verwenden Sie XPath, wenn Sie im DOM-Baum nach oben navigieren, Teiltextinhalte abgleichen oder bedingte Logik anwenden müssen, die mit CSS nicht ausgedrückt werden kann:

# XPath: find links whose visible text contains "Next"
next_link = response.xpath('//a[contains(text(), "Next")]/@href').get()

# XPath: get the parent div of a specific span
parent = response.xpath('//span[@class="price"]/..').get()

# XPath: select items where the price is not empty
priced_items = response.xpath(
    '//div[@class="product"][.//span[@class="price" and text()]]'
).getall()

In der Praxis verwenden die meisten Scrapy-Spider CSS-Selektoren für etwa 80 % ihrer Extraktionsarbeit und wechseln zu XPath für die verbleibenden Randfälle, in denen CSS nicht ausreicht. Scrapy konvertiert CSS-Selektoren intern in XPath, bevor sie ausgeführt werden, sodass es keinen Leistungsunterschied zwischen den beiden Ansätzen gibt. Wählen Sie für jede spezifische Extraktionsaufgabe die Variante, die Ihre Absicht am deutlichsten macht.

Ein praktischer Tipp: Verwenden Sie beim Debuggen von Selektoren scrapy shell "https://target-url.com" , um eine interaktive Sitzung zu öffnen. Sie können sowohl CSS- als auch XPath-Ausdrücke an einer Live-Seite testen, ohne Ihren gesamten Spider ausführen zu müssen, was die Entwicklung erheblich beschleunigt.

Exportieren von gecrawlten Daten in JSON und CSV

Die in Scrapy integrierten Feed-Exporte unterstützen die gängigsten Ausgabeformate ohne jeglichen benutzerdefinierten Code. Den grundlegenden Exportbefehl haben Sie bereits gesehen:

# Export to JSON
scrapy crawl books -o output.json

# Export to CSV
scrapy crawl books -o output.csv

# Export to JSON Lines (one JSON object per line, better for large datasets)
scrapy crawl books -o output.jsonl

Das -o Flag fügt an die Datei an, falls diese bereits existiert, was bei wiederholten Durchläufen zu fehlerhaftem JSON führen kann. Verwenden Sie stattdessen -O (großes O, verfügbar in Scrapy 2.3+) verwenden, um die Datei stattdessen zu überschreiben.

Für mehr Kontrolle über Ihre Datenexport-Pipeline konfigurieren Sie die Exporte in settings.py:

FEEDS = {
    "data/books.json": {
        "format": "json",
        "encoding": "utf-8",
        "indent": 2,
        "overwrite": True,
    },
    "data/books.csv": {
        "format": "csv",
        "fields": ["title", "price", "availability"],
    },
}

Das FEEDS Dictionary können Sie gleichzeitig in mehrere Formate ausgeben, die Feldreihenfolge in CSV steuern und die Kodierung festlegen. Dies ist besonders nützlich, wenn verschiedene Verbraucher unterschiedliche Formate benötigen: Ihr Analyseteam möchte CSV mit bestimmten Spalten, Ihre API-Nutzer wünschen sich JSON Lines für die Streaming-Erfassung und Ihr Archiv benötigt einen übersichtlichen JSON-Snapshot.

Das JSON-Lines-Format (.jsonl) verdient bei größeren Crawls besondere Beachtung. Im Gegensatz zu Standard-JSON, das alles in einem einzigen Array verpackt, schreibt JSON Lines ein vollständiges JSON-Objekt pro Zeile. Das bedeutet, dass Sie die Datei Zeile für Zeile streamen können, neue Datensätze anhängen können, ohne die gesamte Datei neu zu parsen, und Teilergebnisse wiederherstellen können, falls ein Crawl unterwegs abstürzt.

Wenn Sie Daten in eine Datenbank, eine Nachrichtenwarteschlange oder einen Cloud-Speicher-Bucket übertragen müssen, überspringen Sie den Dateiexporter vollständig und schreiben Sie eine benutzerdefinierte Item-Pipeline. Dieser Ansatz gibt Ihnen die volle Kontrolle über Validierung, Transformation und Speicherlogik.

Datenbereinigung mit Scrapy-Item-Pipelines

Rohdaten aus Crawls sind fast nie sauber. Preise enthalten führende Leerzeichen, Titel enthalten vereinzelte Zeilenumbrüche und manche Seiten liefern unvollständige Datensätze. Mit dem Item-Pipeline-System von Scrapy können Sie jedes Item zwischen Extraktion und Export verarbeiten und so sicherstellen, dass Ihre Ausgabe konsistent und gültig ist.

Hier ist eine Pipeline, die die drei häufigsten Bereinigungsaufgaben übernimmt:

from scrapy.exceptions import DropItem

class CleaningPipeline:
    def __init__(self):
        self.seen_titles = set()

    def process_item(self, item, spider):
        # 1. Strip whitespace from all string fields
        for field in item:
            if isinstance(item[field], str):
                item[field] = item[field].strip()

        # 2. Validate required fields
        if not item.get("title"):
            raise DropItem(f"Missing title: {item}")

        # 3. Drop duplicates based on title
        if item["title"] in self.seen_titles:
            raise DropItem(f"Duplicate: {item['title']}")
        self.seen_titles.add(item["title"])

        return item

Um die Pipeline zu aktivieren, registrieren Sie sie in settings.py:

ITEM_PIPELINES = {
    "bookstore.pipelines.CleaningPipeline": 300,
}

Die Ganzzahl (300) ist die Priorität. Niedrigere Zahlen werden zuerst ausgeführt, sodass Sie mehrere Pipelines verketten können: eine Bereinigungspipeline mit 300, eine Validierungspipeline mit 400 und eine Datenbank-Schreibpipeline mit 500. Jede Pipeline empfängt das Item, verarbeitet es und gibt entweder das geänderte Item zurück (wodurch es an die nächste Pipeline weitergeleitet wird) oder löst DropItem , um es vollständig zu verwerfen.

Das Auslösen von DropItem entfernt das Element aus der Ausgabe und protokolliert eine Meldung. Dies ist sauberer als eine nachträgliche Filterung, da verworfene Elemente den Exporter oder die Datenbank nie erreichen. Sie können die Verwerfungsrate Ihres Crawls überwachen, um Parsing-Probleme frühzeitig zu erkennen.

Bei Projekten, die Daten aus Dutzenden verschiedener Seitentypen extrahieren, sollten Sie erwägen, formale Scrapy-Items (oder dataclass-basierte Item-Loader) anstelle von einfachen Wörterbüchern zu definieren. Items erzwingen ein Schema, stellen Standardwerte bereit und arbeiten mit Scrapys Item-Loader-Prozessoren für Transformationen auf Feldebene wie MapCompose(str.strip, str.lower). Dies ist besonders wertvoll bei Teamprojekten, bei denen mehrere Entwickler Spiders für dasselbe Datenmodell schreiben.

Verantwortungsbewusstes Crawling: Robots.txt, Ratenbegrenzungen und Ethik

Ein Python-Webcrawler, der die Regeln einer Website ignoriert, wird irgendwann blockiert, und in manchen Rechtsordnungen könnte dies rechtliche Konsequenzen nach sich ziehen. Verantwortungsbewusstes Crawling ist nicht nur eine Frage der Etikette, sondern eine praktische Notwendigkeit für jeden Crawler, der langfristig zuverlässig laufen soll.

Beachtung von robots.txt

Die robots.txt-Datei befindet sich im Stammverzeichnis jeder Website (z. B. https://example.com/robots.txt) und teilt Crawlern mit, welche Pfade tabu sind und wie schnell sie Anfragen stellen dürfen. So lässt sich die Datei programmgesteuert mit der Python-Standardbibliothek auswerten:

from urllib.robotparser import RobotFileParser

rp = RobotFileParser()
rp.set_url("https://example.com/robots.txt")
rp.read()

if rp.can_fetch("*", "https://example.com/private/data"):
    print("Allowed")
else:
    print("Blocked by robots.txt")

crawl_delay = rp.crawl_delay("*")
print(f"Recommended delay: {crawl_delay} seconds")

Scrapy überprüft robots.txt automatisch, wenn ROBOTSTXT_OBEY = True (Standardeinstellung). Wenn Sie requests und BeautifulSoup verwenden, müssen Sie diese Überprüfung vor jedem Abruf selbst implementieren.

Konfigurieren von Crawl-Verzögerungen und Drosselung

Selbst wenn robots.txt keine Crawl-Verzögerung angibt, führt das Überlasten eines Servers mit Hunderten von gleichzeitigen Anfragen schnell zu einer IP-Sperre. In Scrapy steuern drei Einstellungen Ihr Crawl-Tempo:

# settings.py
DOWNLOAD_DELAY = 1                      # seconds between requests
CONCURRENT_REQUESTS_PER_DOMAIN = 8      # max parallel requests to one domain
AUTOTHROTTLE_ENABLED = True             # dynamically adjusts delay based on load
AUTOTHROTTLE_TARGET_CONCURRENCY = 2.0   # target number of parallel requests

AUTOTHROTTLE ist besonders nützlich, da es sich automatisch an die Antwortzeiten des Servers anpasst. Wenn der Server schnell antwortet, beschleunigt Scrapy. Wenn die Antwortzeiten sprunghaft ansteigen (was auf eine Überlastung des Servers hindeutet), drosselt es die Geschwindigkeit. Dies sorgt für ein Gleichgewicht zwischen Durchsatz und Rücksichtnahme, ohne dass Sie die richtige feste Verzögerung erraten müssen.

Ethische Richtlinien

Über die technischen Einstellungen hinaus sollten Sie folgende Grundsätze befolgen:

  • Identifizieren Sie Ihren Crawler mit einer aussagekräftigen User-Agent-Zeichenkette, die Kontaktinformationen enthält.
  • Crawlen Sie keine Seiten hinter Login- oder Paywalls, es sei denn, Sie haben die ausdrückliche Erlaubnis dazu.
  • Speichern Sie Antworten während der Entwicklung lokal im Cache, damit Sie nicht bei jedem Testlauf Live-Server belasten.
  • Beachten Sie das Robots Exclusion Protocol als Grundregel, auch wenn es in Ihrer Rechtsordnung nicht rechtsverbindlich ist.

Denken Sie bei der Skalierung daran, dass Websites nicht dafür ausgelegt sind, Hunderte von gleichzeitigen Bot-Anfragen zu verarbeiten. Eine Überlastung des Servers beeinträchtigt echte Nutzer, und verantwortungsbewusstes Crawling stellt sicher, dass Sie morgen wieder auf dieselbe Website zugreifen können, ohne dass Ihre IP-Adresse auf einer Sperrliste landet.

Blockierungen vermeiden: User-Agents, Proxys und Anti-Bot-Strategien

Selbst höfliche Crawler werden blockiert. Websites setzen Anti-Bot-Systeme ein, die nach Mustern suchen: wiederholte Anfragen von einer IP-Adresse, fehlende oder generische User-Agent-Header sowie ein Anfrage-Timing, das kein Mensch erzeugen würde. So machen Sie Ihren Python-Webcrawler widerstandsfähiger.

User-Agent-Rotation

Der Standard-User-Agent der meisten HTTP-Bibliotheken identifiziert diese als Bots. Websites, die nach diesem Header filtern, werden Ihre Anfragen sofort ablehnen. Legen Sie einen realistischen, browserähnlichen Header fest:

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                  "AppleWebKit/537.36 (KHTML, like Gecko) "
                  "Chrome/124.0.0.0 Safari/537.36"
}
response = requests.get(url, headers=headers, timeout=10)

Bei längeren Crawling-Sitzungen sollten Sie eine Liste von User-Agent-Strings durchlaufen, um zu vermeiden, dass bei jeder Anfrage derselbe Fingerabdruck präsentiert wird. In Scrapy übernimmt die scrapy-fake-useragent Middleware diese Rotation automatisch.

Proxy-Rotation

IP-basierte Ratenbegrenzungen sind der gängigste Blockierungsmechanismus. Wenn alle Ihre Anfragen von einer einzigen Adresse stammen, erkennt die Website das Muster sofort. Durch die Weiterleitung des Datenverkehrs über rotierende Proxys werden Ihre Anfragen auf viele IP-Adressen verteilt, sodass jede einzelne wie ein unabhängiger Besucher erscheint.

Residential-Proxys sind besonders effektiv, da sie IP-Adressen verwenden, die echten Haushalten zugewiesen sind, wodurch sie vom regulären Nutzerverkehr praktisch nicht zu unterscheiden sind. Rechenzentrums-IPs sind zwar schneller und billiger, lassen sich von Anti-Bot-Systemen jedoch leichter identifizieren und massenhaft blockieren.

Erkennen, wann Sie blockiert sind

Bevor Sie in Gegenmaßnahmen investieren, lernen Sie, die Symptome zu erkennen:

  • HTTP 403 oder 429: Explizite Ablehnung oder Antwort wegen Ratenbegrenzung.
  • Weiterleitung zu einer CAPTCHA-Seite: Der Server verlangt den Nachweis, dass Sie ein Mensch sind.
  • Leerer oder Platzhalter-HTML-Code: Die Seite wird geladen, enthält aber keinen sinnvollen Inhalt, sondern nur ein Gerüst oder eine „Bitte warten“-Meldung.
  • Plötzliche Spitzen bei der Antwortzeit: Der Server bremst Sie absichtlich aus (eine Technik namens „Tarpitting“).

Wenn Sie eine Blockierung feststellen, warten Sie eine Weile, bevor Sie es erneut versuchen. Ein exponentieller Backoff (1 Sekunde warten, dann 2 Sekunden, dann 4 Sekunden, dann 8 Sekunden) ist ein sinnvoller Standard. Scrapys Retry-Middleware behandelt vorübergehende Fehler automatisch, aber anhaltende Blockierungen erfordern in der Regel eine Änderung der Strategie: andere IP-Adressen, langsamere Anfrageraten oder eine Rendering-Schicht für Websites, die Inhalte nur an echte Browser ausliefern.

Anti-Bot-Abwehrmaßnahmen sind ein Wettrüsten. Sobald JavaScript-Herausforderungen, Browser-Fingerprinting und CAPTCHAs ins Spiel kommen, stoßen einfache Crawling-Skripte an ihre Grenzen. Die pragmatische Wahl besteht oft darin, diese Komplexität an einen speziell dafür entwickelten Dienst auszulagern, anstatt einen eigenen Proxy-Pool und eine eigene Infrastruktur für die Browser-Automatisierung zu unterhalten.

Umgang mit JavaScript-gerenderten Seiten

Eine wachsende Zahl von Websites nutzt clientseitiges JavaScript, um ihre Inhalte zu rendern. Wenn Sie eine dieser Seiten mit requestsabrufen, erhalten Sie eine HTML-Hülle mit leeren <div> Containern und einem Bündel JavaScript. Die eigentlichen Daten werden erst geladen, nachdem die Skripte in einer Browserumgebung ausgeführt wurden, was bedeutet, dass herkömmliche HTTP-basierte Crawler nichts Brauchbares sehen.

Beim Erstellen eines Web-Crawlers in Python haben Sie drei Hauptoptionen, um damit umzugehen:

1. Finden Sie die zugrunde liegende API. Bevor Sie zu einem Headless-Browser greifen, öffnen Sie die Entwicklertools Ihres Browsers und überprüfen Sie die Registerkarte „Netzwerk“. Viele Single-Page-Anwendungen rufen Daten von einer JSON-API ab, die Sie direkt aufrufen können, wodurch das Rendering-Problem vollständig umgangen wird. Dies ist der schnellste und ressourcenschonendste Ansatz, wenn er funktioniert.

2. Verwenden Sie einen Headless-Browser. Mit Tools wie Playwright und Puppeteer können Sie eine echte (Headless-)Chrome- oder Firefox-Instanz über Ihren Code steuern. Der Browser führt JavaScript aus, wartet auf das Rendern des Inhalts, und anschließend extrahieren Sie Daten aus dem vollständig geladenen DOM. Scrapy lässt sich über das scrapy-playwright Plugins, mit dem Sie bestimmte Anfragen selektiv für das Browser-Rendering markieren können, während der Rest als schnelle, ressourcenschonende HTTP-Aufrufe bleibt:

# In a Scrapy spider, mark a request for Playwright rendering
yield scrapy.Request(
    url,
    meta={"playwright": True, "playwright_page_methods": [
        {"method": "wait_for_selector", "args": [".product-list"]},
    ]},
    callback=self.parse_products,
)

3. Nutzen Sie einen verwalteten Rendering-Dienst. Wenn Sie keine Headless-Browser-Infrastruktur betreiben und warten möchten (die erheblichen Speicher und CPU-Ressourcen verbraucht), können verwaltete API-Dienste das Rendering für Sie übernehmen. Sie geben das vollständig geladene HTML zurück, sodass Sie es mit Ihren bestehenden BeautifulSoup- oder Scrapy-Selektoren parsen können.

Die richtige Wahl hängt vom Umfang und der Komplexität ab. Für einige hundert JS-lastige Seiten ist ein lokaler Headless-Browser vollkommen ausreichend. Bei Tausenden von Seiten auf Websites mit Anti-Bot-Schutz summiert sich der operative Aufwand für die Verwaltung von Browser-Instanzen, die Behebung von Speicherlecks und die Wiederherstellung nach Abstürzen schnell.

Komplexe Crawls mit einer verwalteten API vereinfachen

Irgendwann ist der schwierigste Teil beim Aufbau eines Python-Webcrawlers nicht mehr die Parsing-Logik, sondern alles andere: die Pflege von Proxy-Pools, das Lösen von CAPTCHAs, die Rotation von Browser-Fingerabdrücken und das Schritthalten mit Anti-Bot-Systemen, die ihre Abwehrmechanismen wöchentlich aktualisieren. Wenn der Aufwand für die Infrastruktur die eigentliche Extraktionsarbeit überwiegt, ist es sinnvoll, diese Ebene auszulagern und sich auf das zu konzentrieren, worum es in Ihrem Code wirklich geht: die Daten.

Ein Managed-API-Dienst sitzt zwischen Ihrem Crawler und der Zielwebsite. Sie senden eine Anfrage mit der Ziel-URL, und der Dienst übernimmt hinter den Kulissen die Proxy-Rotation, das Rendern von JavaScript, Wiederholungsversuche und Anti-Bot-Maßnahmen. Zurück erhalten Sie sauberes HTML (oder strukturiertes JSON), das Sie mit demselben BeautifulSoup- oder Scrapy-Code parsen, den Sie bereits haben. Ihre Crawling-Logik ändert sich nicht; nur die Abrufebene ändert sich.

Dieser Ansatz ist besonders praktisch, wenn:

  • Sie Websites crawlen, die über aggressive Bot-Erkennung verfügen, die IP-Adressen von Rechenzentren innerhalb von Minuten blockiert.
  • Sie JavaScript-Rendering in großem Maßstab benötigen, aber keine Flotte von Headless-Browser-Instanzen und die damit verbundenen Speicher- und CPU-Kosten verwalten möchten.
  • Die Entwicklungszeit Ihres Teams besser für Datenanalyse und Pipeline-Entwicklung genutzt wird als für die Wartung der Proxy-Infrastruktur.
  • Sie viele verschiedene Ziel-Websites crawlen müssen, von denen jede über einen eigenen Anti-Bot-Stack verfügt, was eine einheitliche lokale Lösung unpraktisch macht.

Der Kompromiss sind die Kosten. Sie zahlen pro erfolgreicher Anfrage, anstatt eine eigene Infrastruktur zu betreiben. Bei Crawls mit hohem Volumen und langer Laufzeit, bei denen die Ziele nicht stark geschützt sind, spricht die Wirtschaftlichkeit eher für selbstverwaltete Setups. Bei den meisten Projekten, die botgeschützte Websites betreffen, gleicht die eingesparte Entwicklerzeit die Gebühr pro Anfrage jedoch mehr als aus.

Wichtige Erkenntnisse

  • Fangen Sie einfach an und skalieren Sie dann gezielt. Ein einfacher requests + BeautifulSoup-Crawler reicht für kleine Aufgaben und zum Lernen aus. Wechseln Sie zu Scrapy, wenn Sie Parallelität, automatische Deduplizierung und strukturierte Datenpipelines benötigen.
  • Deduplizierung ist unverzichtbar. Verwenden Sie einen Set von bereits besuchten URLs mit korrekter Normalisierung (oder lassen Sie dies von Scrapys Scheduler übernehmen), um Endlosschleifen und verschwendete Bandbreite zu vermeiden.
  • Crawlen Sie stets verantwortungsbewusst. Beachten Sie robots.txt, konfigurieren Sie Crawling-Verzögerungen, verwenden Sie AUTOTHROTTLE und identifizieren Sie Ihren Bot mit einem aussagekräftigen User-Agent. Dies schützt sowohl die Zielwebsite als auch den Ruf Ihrer eigenen IP-Adresse.
  • Gehen Sie bewusst mit JavaScript um. Prüfen Sie zunächst auf zugrunde liegende APIs, verwenden Sie bei Bedarf Headless-Browser und ziehen Sie Managed Services in Betracht, wenn Sie JS-Rendering in großem Maßstab benötigen.
  • Bereinigen Sie Daten während des Crawls, nicht danach. Mit den Item-Pipelines von Scrapy können Sie Datensätze validieren, deduplizieren und transformieren, bevor sie überhaupt Ihre Exportdatei oder Datenbank erreichen.

FAQ

Was ist der Unterschied zwischen Web-Crawling und Web-Scraping?

Crawling ist der Erkundungsprozess: Ein automatisiertes Programm folgt Hyperlinks über Seiten hinweg, um die Struktur einer Website abzubilden und URLs zu finden. Scraping ist der Extraktionsschritt: das Extrahieren bestimmter Datenfelder (Preise, Titel, Daten) aus bereits gefundenen Seiten. Die meisten Projekte in der Praxis kombinieren beides, aber sie lösen unterschiedliche Probleme und profitieren oft von unterschiedlichen Tools und Strategien.

Das hängt von der Rechtsordnung, den Nutzungsbedingungen der Website und der Art der Daten ab, die Sie sammeln. In den Vereinigten Staaten bestätigte das Urteil „hiQ gegen LinkedIn“ aus dem Jahr 2022, dass der Zugriff auf öffentlich zugängliche Daten keinen Verstoß gegen den Computer Fraud and Abuse Act darstellt. Allerdings können weiterhin Einschränkungen durch Nutzungsbedingungen, das Urheberrecht und Datenschutzbestimmungen wie die DSGVO gelten. Konsultieren Sie immer einen Rechtsbeistand, bevor Sie in großem Umfang crawlen, insbesondere bei kommerzieller Nutzung.

Wie gehe ich beim Crawlen mit JavaScript-lastigen Websites um?

Prüfen Sie zunächst, ob eine zugrunde liegende API vorhanden ist, indem Sie im Netzwerk-Tab des Browsers nach XHR-/Fetch-Anfragen suchen. Wenn die Daten erst nach dem clientseitigen Rendering verfügbar sind, verwenden Sie einen Headless-Browser wie Playwright oder Puppeteer, um JavaScript auszuführen und das vollständig gerenderte DOM zu extrahieren. Für das Crawling großer JS-Mengen kann ein Managed-Rendering-Service die Browser-Orchestrierung übernehmen, sodass Sie diese Infrastruktur nicht selbst warten müssen.

Wie kann ich verhindern, dass mein Python-Crawler blockiert wird?

Wechseln Sie die User-Agent-Strings, verwenden Sie Residential-Proxys, um Anfragen auf mehrere IP-Adressen zu verteilen, fügen Sie zufällige Verzögerungen zwischen den Anfragen ein und beachten Sie die Crawl-Delay-Anweisungen in der robots.txt. Beobachten Sie die Antwortcodes genau: Ein Anstieg der 403- oder 429-Antworten bedeutet, dass die Website Ihr Traffic-Muster erkannt hat. Zurückstecken und die Parallelität zu reduzieren ist fast immer effektiver, als zu versuchen, Blockaden mit Brute-Force zu überwinden.

Wann sollte ich Scrapy anstelle von requests und BeautifulSoup verwenden?

Verwenden Sie Scrapy, wenn Ihr Crawl mehr als ein paar hundert Seiten umfasst, gleichzeitige Anfragen erfordert, eine integrierte Deduplizierung benötigt oder von strukturierten Datenpipelines und dem Export profitiert. Für schnelle, einmalige Skripte, die auf eine kleine Anzahl von Seiten abzielen, sind „requests“ und BeautifulSoup schneller einzurichten und einfacher zu debuggen. Wenn Ihr Projekt über eine einzelne Skriptdatei hinauswächst, erspart Ihnen die Architektur von Scrapy die Neuentwicklung seiner Funktionen.

Fazit

Die Entwicklung eines Python-Webcrawlers ist ein Prozess, kein einzelner Schritt. Sie beginnen mit einer Handvoll Zeilen unter Verwendung von requests und BeautifulSoup, um die Schleife „Abrufen-Analysieren-Extrahieren“ zu verstehen. Von dort aus wechseln Sie zu Scrapy, um Parallelität, automatische Deduplizierung, Flexibilität bei der Selektorenauswahl und pipelinebasierte Datenbereinigung zu erhalten, ohne diese Infrastruktur selbst schreiben zu müssen.

Die Grundlagen bleiben unabhängig vom Umfang dieselben: Behandle die gecrawlten Websites mit Respekt, führe eine konsequente Deduplizierung durch, gehe elegant mit Fehlern um und bereinige deine Daten vor der Speicherung. Wenn die Ziel-Websites mit CAPTCHAs, IP-Sperren oder reinem JavaScript-Rendering zurückschlagen, hast du einen klaren Entscheidungsbaum: Prüfe zuerst auf eine zugrunde liegende API, nutze Headless-Browser für moderate Volumina und greife bei hohen Arbeitslasten auf Managed Services zurück.

Wenn Sie feststellen, dass Sie mehr Zeit mit Proxy-Rotation, CAPTCHA-Lösung und Anti-Bot-Workarounds verbringen als mit der eigentlichen Datenverarbeitung, kann WebScrapingAPI diese Infrastrukturschicht für Sie übernehmen. Es verwaltet Proxys, JavaScript-Rendering und Wiederholungsversuche hinter einem einzigen Endpunkt, sodass Ihre Scrapy-Spider oder BeautifulSoup-Skripte mit minimalen Codeänderungen weiterarbeiten können. Auf diese Weise konzentrieren Sie sich darauf, was die Daten Ihnen sagen, und nicht darauf, wie Sie sie erst einmal in die Hände bekommen.

Über den Autor
Suciu Dan, Mitbegründer @ WebScrapingAPI
Suciu DanMitbegründer

Suciu Dan ist Mitbegründer von WebScrapingAPI und verfasst praxisorientierte, auf Entwickler zugeschnittene Anleitungen zu den Themen Web-Scraping mit Python, Web-Scraping mit Ruby und Proxy-Infrastruktur.

Los geht’s

Sind Sie bereit, Ihre Datenerfassung zu erweitern?

Schließen Sie sich den über 2.000 Unternehmen an, die WebScrapingAPI nutzen, um Webdaten im Unternehmensmaßstab ohne zusätzlichen Infrastrukturaufwand zu extrahieren.