Zurück zum Blog
Anleitungen
Mihai MaximLast updated on May 13, 202615 min read

Web Scraping mit Scrapy: 2026 Playbook

Web Scraping mit Scrapy: 2026 Playbook
TL;DR: Dies ist ein eigenwilliger, umfassender Leitfaden zum Web-Scraping mit Scrapy im Jahr 2026. Sie werden Scrapy installieren, Selektoren in der Shell prototypisieren, einen mehrseitigen E-Commerce-Spider erstellen, Elemente mit Item Loaders bereinigen, Daten in einer Datenbank speichern, Einstellungen gegen Sperren absichern und Scrapy-Playwright für JavaScript-gerenderte Seiten integrieren.

Scrapy ist seit über einem Jahrzehnt das Rückgrat des professionellen Python-Crawlings und hat sich trotz einer Welle neuerer asynchroner Bibliotheken nach wie vor bewährt. Wenn Sie heute Web-Scraping mit Scrapy betreiben, erhalten Sie ein eigenwilliges Framework, das die langweiligen Teile (Anforderungsplanung, Deduplizierung, Wiederholungsversuche, Item-Pipelines) übernimmt, sodass Sie sich auf die Teile konzentrieren können, die tatsächlich Probleme bereiten: Selektoren, Anti-Bot-Maßnahmen und Speicherung.

Dieser Leitfaden ist eher am Lebenszyklus von Anfragen und Antworten ausgerichtet als an einem chronologischen Aufbau. Jeder Abschnitt entspricht einer Scrapy-Komponente, mit der du in der Produktion zu tun haben wirst, von der Engine und den Downloader-Middlewares bis hin zu Item Loaders und Feed-Exporten. Wir verwenden durchgehend ein einziges Ziel, die öffentliche Übungsseite books.toscrape.com, sodass jeder Code-Block in ein einheitliches mentales Modell passt.

Am Ende verfügen Sie über einen lauffähigen Spider, der einen Katalog paginiert, Items validiert und bereinigt, sowohl in JSON Lines als auch in SQLite schreibt, bei 429 Storms und auf einen echten Browser ausweicht, wenn eine Seite JavaScript benötigt. Wir werden auch die Teile des Frameworks hervorheben, die Neulinge regelmäßig falsch verwenden, und kopierbare Lösungen bereitstellen.

Warum Scrapy auch 2026 noch das Maß aller Dinge beim Produktions-Scraping ist

Es ist verlockend, nach httpx plus selectolax zu greifen und es dabei zu belassen. Für ein einmaliges Skript ist das der richtige Schritt. Für einen Crawler, der jede Nacht laufen, URLs deduplizieren, einen teilweisen Ausfall überstehen und an zwei Ziele schreiben muss, braucht man ein Framework. Scrapy bleibt zum Zeitpunkt des Verfassens dieses Artikels der Industriestandard für groß angelegte Datenextraktion, und der Grund ist einfach: Es wird mit einem Scheduler, einem Dupe-Filter, Retry-Middleware, Throttling, Signalen und Feed-Exporten ausgeliefert, die bereits miteinander verknüpft sind.

Im Vergleich zum Zusammenfügen von requests und „BeautifulSoup“ ist Scrapy auf nützliche Weise eigenwillig. Es läuft auf der Event-Loop von Twisted, sodass ein einzelner Prozess Hunderte von gleichzeitigen Anfragen verteilen kann, ohne den kognitiven Aufwand von async/await. Sie schreiben die Crawling-Schleife nicht selbst. Sie geben die Einstiegs-URLs und die Parsing-Logik an, und die Engine kümmert sich um die Warteschlange. Genau dieser Ansatz macht Scrapy die steilere Lernkurve wert.

So funktioniert Web-Scraping mit Scrapy: der Lebenszyklus von Anfrage und Antwort

Bevor Sie einen Spider schreiben, sollten Sie sich den Lebenszyklus verinnerlichen. Ein Scrapy-Lauf sieht wie folgt aus:

  1. Die Engine holt eine Request vom Scheduler.
  2. Die Anfrage durchläuft die Downloader-Middlewares (in der Reihenfolge ihrer Priorität). Hier werden Header gesetzt, Cookies angehängt, Proxys gewechselt und Wiederholungsversuche ausgelöst.
  3. Der Downloader sendet den HTTP-Aufruf und gibt eine Response.
  4. Die Antwort durchläuft auf dem Rückweg zunächst die Downloader-Middlewares, dann die Spider-Middlewares und gelangt schließlich in den Callback Ihres Spiders (in der Regel parse).
  5. Ihr Callback yieldentweder weitere Request Objekte (die an den Scheduler zurückgehen) oder Items (die in die Item-Pipelines fließen).
  6. Pipelines validieren, transformieren, verwerfen oder speichern jedes Element.
  7. Alles, was übrig bleibt, wird an den Feed-Exporter übergeben, der auf Festplatte, S3 oder stdout schreibt.

Zwei Begriffe, die Sie in Callbacks sehen werden: callback ist die Funktion, die Scrapy ausführt, wenn eine Anfrage erfolgreich ist, und errback ist die Funktion, die ausgeführt wird, wenn eine Anfrage fehlschlägt. Spiders werden typischerweise als Python-Generatoren geschrieben, die Anfragen und Elemente verzögert ausgeben, damit die Engine die Arbeit verschachteln kann.

Das Verständnis dieser Schleife macht den Unterschied zwischen „mein Spider funktioniert“ und „mein Spider ist skalierbar“. Wenn Seiten leer zurückkommen, liegt die Antwort fast immer in der Downloader-Middleware-Schicht. Wenn Elemente verschwinden, liegt die Antwort in einer Pipeline. Wenn die Paginierung versagt, liegt es an Ihrem Callback. Ordnen Sie das Symptom der jeweiligen Stufe zu und beheben Sie dann die richtige Komponente.

Eine detailliertere Anleitung findest du in der offiziellen Scrapy-Architekturdokumentation, die es wert ist, mit einem Lesezeichen versehen zu werden.

Installation von Scrapy und Bootstrapping eines Projekts

Scrapy zielt auf modernes Python 3 ab (die Mindestversion zum Zeitpunkt der Installation entnehmen Sie bitte der offiziellen Installationsanleitung). Die Dokumentation empfiehlt dringend eine dedizierte virtuelle Umgebung, damit die festgelegten Abhängigkeiten von Scrapy nicht mit Systempaketen kollidieren.

python -m venv .venv
source .venv/bin/activate     # Windows: .venv\Scripts\activate
pip install --upgrade pip
pip install scrapy
scrapy version

Sobald scrapy version eine Versionszeichenfolge ausgibt, erstelle ein Projektgerüst:

scrapy startproject bookstore
cd bookstore

Sie haben nun eine Projektstruktur, die in jeder Scrapy-Codebasis auf der Welt gleich aussieht – und genau darum geht es. Jedes Mal, wenn Sie sich in ein neues Scrapy-Repo einarbeiten, wissen Sie bereits, wo sich die Spider befinden, wo die Einstellungen gespeichert sind und welche Datei die Pipelines enthält. Diese Wiederholbarkeit macht die Hälfte des Nutzens aus, den die Verwendung eines Frameworks überhaupt erst bietet. Widerstehen Sie dem Drang, die Struktur zu vereinfachen: Nachgelagerte Tools wie scrapyd und scrapy crawl sind darauf angewiesen.

Innerhalb eines Scrapy-Projekts: Was jede Datei tut

scrapy startproject erzeugt fünf Dateien und einen Ordner, mit denen du täglich zu tun haben wirst.

  • scrapy.cfg ist die Projektkonfiguration auf oberster Ebene. Sie benennt das Projekt und gibt an, scrapyd , wo sich das Einstellungsmodul befindet.
  • items.py ist die Schema-Ebene. Hier definieren Sie Product, Articleoder beliebige andere Klassen, die jeweils von scrapy.Item. Behandeln Sie dies wie eine Datenklasse für die Scraping-Ausgabe.
  • pipelines.py Hier werden extrahierte Elemente bereinigt, validiert, verworfen oder in eine Datenbank geschrieben. Jede Pipeline ist eine einfache Klasse mit einer process_item Methode.
  • middlewares.py enthält Downloader- und Spider-Middlewares. Dies ist die Datei, in der Sie User-Agents rotieren, Proxys einbinden oder Anfragen über eine verwaltete Scraping-API leiten.
  • settings.py ist das zentrale Konfigurationsobjekt: Parallelität, Drosselung, Wiederholungsversuche, Pipelines, Middlewares und Feed-Exporte sind hier alle untergebracht.
  • spiders/ ist der Ordner, in dem sich die einzelnen Spider-Dateien befinden. Ein Spider pro Zielseite ist eine sinnvolle Standardeinstellung.

Prototyping von Selektoren in der Scrapy-Shell

Die Scrapy-Shell ist die Geheimwaffe, von der niemand genug spricht. Bevor Sie auch nur eine einzige Zeile Spider-Code schreiben, öffnen Sie die Shell mit einer echten URL und probieren Sie Selektoren interaktiv aus. Das spart Stunden.

scrapy shell "https://books.toscrape.com/catalogue/page-1.html"

Innerhalb der Shell erhalten Sie ein Live- response Objekt, das mit der Seite vorbeladen ist. Drei Befehle sind wichtig:

  • fetch("https://example.com") tauscht eine neue Antwort ein, ohne die Shell zu verlassen.
  • view(response) öffnet den heruntergeladenen HTML-Code in deinem Standardbrowser. So kannst du sicherstellen, dass du mit demselben DOM arbeitest, das der Spider sieht, und nicht mit dem gerenderten, das dein Browser normalerweise anzeigen würde.
  • response.css(...) und response.xpath(...) ermöglicht es dir, Selektoren anhand der Live-Antwort zu testen.

Probieren Sie dies auf der Übungsseite aus:

>>> response.css("article.product_pod h3 a::attr(title)").getall()[:3]
['A Light in the Attic', 'Tipping the Velvet', 'Soumission']
>>> response.xpath("//article[@class='product_pod']//p[@class='price_color']/text()").get()
'£51.77'

Wiederholen Sie den Vorgang, bis beide Selektoren saubere Daten zurückgeben. Erst dann fügen Sie den Ausdruck in Ihren Spider ein. Der Aufwand für das Debuggen eines fehlerhaften XPaths innerhalb eines 5-minütigen Crawls ist viel höher als der Aufwand für eine einzige Shell-Sitzung.

Schreiben Ihres ersten Spiders für Web-Scraping mit Scrapy

Generieren Sie einen Spider-Stub für Ihre Zieldomain:

scrapy genspider books books.toscrape.com

Dadurch wird spiders/books.py. Ersetze dessen Inhalt durch den unten stehenden Spider. Er scrapt die Katalog-Landingpage, extrahiert Titel, Preis und Bewertung jedes Buches und gibt dann ein Python-Dict pro Buch zurück. Wir werden ihn in einem späteren Abschnitt auf echte Items erweitern.

import scrapy

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

    def parse(self, response):
        for card in response.css("article.product_pod"):
            yield {
                "title": card.css("h3 a::attr(title)").get(),
                "price": card.css("p.price_color::text").get(),
                "rating": card.css("p.star-rating::attr(class)").get(),
                "url": response.urljoin(card.css("h3 a::attr(href)").get()),
            }

Führen Sie ihn vom Projektstammverzeichnis aus:

scrapy crawl books -o books.jsonl

Du solltest sehen, wie Scrapy eine Anfrage an Seite 1 protokolliert, zwanzig Elemente scrapt und dann sauber herunterfährt. Öffne books.jsonl und vergewissere dich, dass pro Zeile ein JSON-Objekt steht.

Ein paar Dinge sind zu beachten. start_urls ist der Einstiegspunkt, die Engine plant jede URL automatisch ein. parse ist der Standard-Callback. response.urljoin Löst eine relative href gegen die aktuelle Seite auf, damit keine defekten Links entstehen. Das rating Feld enthält immer noch Störsignale wie "star-rating Three", was genau die Art von Bereinigung ist, die Item Loader später übernehmen werden.

Hinweis zur Produktion: Die Ausführung mit -o ist für einen schnellen Test in Ordnung, aber verlassen Sie sich in einem geplanten Job niemals darauf. Konfigurieren Sie die FEEDS Einstellung stattdessen settings.py , damit das Ausgabziel, das Format und das Überschreibverhalten versionsverwaltet sind. Wir werden dies im Abschnitt zur Persistenz zusammen mit einer Datenbank-Pipeline einrichten. Betrachten Sie das CLI-Flag als Abkürzung für die Entwicklung, nicht als Artefakt für die Bereitstellung.

CSS vs. XPath: Auswahl von Selektoren, die nicht versagen

Beide Selektor-Engines sind in Scrapy enthalten und laufen auf demselben geparsten Baum. Verwenden Sie je nach Aufgabe diejenige, die kürzer und übersichtlicher ist. Als Faustregel gilt: CSS ist besser für klassenbasierte und strukturelle Abfragen, XPath ist besser, wenn Sie den Baum nach Textinhalt, Geschwistern oder Vorfahren durchlaufen müssen.

# CSS: short, idiomatic, fast to write
response.css("article.product_pod p.price_color::text").get()

# XPath equivalent
response.xpath("//article[@class='product_pod']//p[@class='price_color']/text()").get()

XPath kommt zum Einsatz, wenn CSS nicht ausdrücken kann, was Sie benötigen:

# "Find the <td> that follows the <th> whose text is 'Stock'"
response.xpath("//th[normalize-space()='Stock']/following-sibling::td/text()").get()

# "Find all links whose visible text contains 'Next'"
response.xpath("//a[contains(., 'Next')]/@href").getall()

Ein paar Gewohnheiten, die Selektoren stabil halten: Bevorzuge Attribut-Selektoren gegenüber instabilen Positionsselektoren (nth-child(3) werden irgendwann nicht mehr funktionieren), normalisieren Sie Leerzeichen, wenn Sie Text vergleichen (normalize-space()) und kombinieren Sie .get() für eine einzelne Übereinstimmung mit .getall() für eine Liste, indizieren Sie niemals blindlings das Ergebnis von .getall() blind. Für einen tiefergehenden Vergleich, wann welche Engine die richtige Wahl ist, ist unser Leitfaden „XPath vs. CSS-Selektoren“ eine gute Lektüre.

Hinweis zur Produktion: Wenn ein Selektor None in der Produktion, funktioniert aber in der Shell, wurde die Seite wahrscheinlich mit JavaScript gerendert. Überprüfen Sie dies mit view(response) , bevor Sie den Selektor dafür verantwortlich machen.

Elemente und Item-Loader: Wiederverwendbare Bereinigungsmuster

Die Ausgabe einfacher Dicts ist für zehn Zeilen Code in Ordnung. Bei größerem Umfang benötigen Sie ein typisiertes Schema, damit ein Tippfehler in einem Feldnamen schnell auffällt, anstatt stillschweigend fehlerhafte Zeilen zu erzeugen. Definieren Sie ein Item in items.py:

import scrapy
from itemloaders.processors import MapCompose, TakeFirst, Join

def to_float(value):
    return float(value.replace("£", "").replace("$", "").strip())

def normalize_rating(value):
    # "star-rating Three" -> "Three"
    parts = value.split()
    return parts[1] if len(parts) > 1 else value

class ProductItem(scrapy.Item):
    title = scrapy.Field(input_processor=MapCompose(str.strip), output_processor=TakeFirst())
    price = scrapy.Field(input_processor=MapCompose(str.strip, to_float), output_processor=TakeFirst())
    rating = scrapy.Field(input_processor=MapCompose(normalize_rating), output_processor=TakeFirst())
    description = scrapy.Field(input_processor=MapCompose(str.strip), output_processor=Join(" "))

MapCompose Chain-Transformatoren, TakeFirst eine Liste von Übereinstimmungen zu einem einzigen Wert zusammenfasst und Join fügt mehrere Absätze zu einem zusammen. Verwenden Sie einen Loader im Spider, damit dieser lesbar bleibt:

from scrapy.loader import ItemLoader
from bookstore.items import ProductItem

def parse(self, response):
    for card in response.css("article.product_pod"):
        loader = ItemLoader(item=ProductItem(), selector=card)
        loader.add_css("title", "h3 a::attr(title)")
        loader.add_css("price", "p.price_color::text")
        loader.add_css("rating", "p.star-rating::attr(class)")
        yield loader.load_item()

Der Vorteil ist die Wiederverwendbarkeit. Sobald to_float in items.py, kann jedes preisbehaftete Element in jedem Spider ihn aufrufen. Die Bereinigungslogik muss nicht mehr über Callbacks hinweg kopiert und eingefügt werden.

Es gibt zwei gängige Methoden, um mehrere Seiten in Scrapy zu crawlen. Wähle die Methode danach aus, wie vorhersehbar die Linkstruktur ist.

Manuelle Paginierung ist die richtige Wahl, wenn es einen einzigen „Weiter“-Link gibt, dem man folgen kann. Füge dies am Ende von parse:

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

response.follow verarbeitet relative URLs und verwendet denselben Callback wieder, was genau den Anforderungen der Paginierung im Katalogstil entspricht. Das Crawlen endet auf natürliche Weise, wenn der „Weiter“-Link auf der letzten Seite verschwindet.

CrawlSpider ist die richtige Wahl, wenn Sie eine gesamte Website durch Abgleichen von URL-Mustern durchsuchen möchten. Es verwendet Rule und LinkExtractor , um Links automatisch zu erkennen und zu folgen:

from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor

class BooksCrawl(CrawlSpider):
    name = "books_crawl"
    allowed_domains = ["books.toscrape.com"]
    start_urls = ["https://books.toscrape.com/"]
    rules = (
        Rule(LinkExtractor(restrict_css=".pager a")),  # follow pagination
        Rule(LinkExtractor(restrict_css="h3 a"), callback="parse_book"),
    )

    def parse_book(self, response):
        yield {
            "title": response.css("h1::text").get(),
            "price": response.css("p.price_color::text").get(),
        }

Scrapys integrierte RFPDupeFilter stellt sicher, dass dieselbe URL nicht zweimal in die Warteschlange gestellt wird, sodass Sie besuchte Links nicht selbst nachverfolgen müssen. Setzen Sie DEPTH_LIMIT in settings.py , wenn Sie eine tief strukturierte Website crawlen und einen harten Stopp wünschen.

Anmerkung zur Produktion: Bei Sitemap-freundlichen Websites SitemapSpider ist es noch einfacher. Es liest /sitemap.xml direkt ein und ermöglicht es Ihnen, URL-Muster mit sitemap_rules.

Persistente Ergebnisse: FEEDS und eine Datenbank-Pipeline

Beim Web-Scraping mit Scrapy stehen Ihnen zwei Persistenz-Ebenen zur Verfügung, und in der Regel benötigen Sie beide. Die FEEDS-Einstellung übernimmt strukturierte Exporte automatisch, während eine Pipeline benutzerdefinierte Ziele wie eine relationale Datenbank verwaltet.

Konfigurieren Sie Feeds in settings.py. Die aktuelle Syntax finden Sie in der Scrapy-Dokumentation zu Feed-Exporten, aber eine moderne Konfiguration sieht in etwa so aus:

FEEDS = {
    "data/books.jsonl": {
        "format": "jsonlines",
        "encoding": "utf-8",
        "overwrite": True,
    },
    "data/books.csv.gz": {
        "format": "csv",
        "postprocessing": ["scrapy.extensions.postprocessing.GzipPlugin"],
    },
}

JSON Lines ist die richtige Standardeinstellung: streamfähig, anreihungsfreundlich und einfach in Pandas oder ein Data Warehouse zu laden. CSV mit gzip eignet sich gut für die Übergabe an Analysten. Beide versagen bei relationalen Abfragen, und hier kommen Pipelines ins Spiel.

Eine SQLite-Pipeline, die nach einem Validator läuft:

# pipelines.py
import sqlite3
from itemadapter import ItemAdapter

class SqlitePipeline:
    def open_spider(self, spider):
        self.conn = sqlite3.connect("data/books.db")
        self.conn.execute(
            "CREATE TABLE IF NOT EXISTS products (title TEXT, price REAL, rating TEXT)"
        )

    def close_spider(self, spider):
        self.conn.commit()
        self.conn.close()

    def process_item(self, item, spider):
        a = ItemAdapter(item)
        self.conn.execute(
            "INSERT INTO products(title, price, rating) VALUES (?, ?, ?)",
            (a["title"], a["price"], a["rating"]),
        )
        return item

Registrieren Sie sie mit einer Priorität. Niedrigere Zahlen werden früher ausgeführt, sodass ein Validator mit 100 vor dem Datenbank-Writer mit 200 ausgelöst wird:

ITEM_PIPELINES = {
    "bookstore.pipelines.PriceRangeValidator": 100,
    "bookstore.pipelines.SqlitePipeline": 200,
}

Jetzt werden ungültige Preise verworfen, bevor sie überhaupt in die Datenbank gelangen.

Sicherer machen: settings.py – AutoThrottle, Retries und Caching

Die Standardeinstellungen funktionieren in der Entwicklung, führen aber in der Produktion zu einer Sperrung. Die folgenden wenigen sind die wichtigsten. Überprüfen Sie die genauen Standardeinstellungen anhand Ihrer installierten Scrapy-Version.

# settings.py
ROBOTSTXT_OBEY = True            # respect the site's policy unless you have a contract
CONCURRENT_REQUESTS = 8          # global cap; lower for fragile sites
CONCURRENT_REQUESTS_PER_DOMAIN = 4
DOWNLOAD_DELAY = 0.5             # base delay; AutoThrottle adjusts dynamically

AUTOTHROTTLE_ENABLED = True
AUTOTHROTTLE_TARGET_CONCURRENCY = 2.0
AUTOTHROTTLE_START_DELAY = 1.0
AUTOTHROTTLE_MAX_DELAY = 30.0

RETRY_ENABLED = True
RETRY_TIMES = 5
RETRY_HTTP_CODES = [429, 500, 502, 503, 504, 408, 522, 524]

HTTPCACHE_ENABLED = True         # huge time-saver during development
HTTPCACHE_EXPIRATION_SECS = 3600
HTTPCACHE_IGNORE_HTTP_CODES = [429, 500, 502, 503, 504]

AutoThrottle ist hier das entscheidende Feature. Anstatt einen DOWNLOAD_DELAY, geben Sie eine Ziel-Parallelität vor, und Scrapy verlangsamt sich, wenn die Latenz steigt. Das allein verhindert die meisten unbeabsichtigten DDoS-Situationen auf langsamen Websites.

HTTPCACHE_ENABLED ist eine Einstellung, die die Arbeit in der Entwicklung erleichtert: Während Sie Selektoren durchlaufen, werden identische Anfragen von der Festplatte zurückgegeben, sodass Sie das Ziel nicht überlasten. Deaktivieren Sie diese Funktion in der Produktion.

Für echten Anti-Bot-Schutz reichen Einstellungen allein nicht aus, und unser Leitfaden darüber, warum Scraper blockiert werden, behandelt die tieferen Muster. So oder so, die nächste Ebene sind Middlewares.

Downloader-Middleware: Header, Proxys und verwaltete APIs

Wenn eine Website anfängt, 403s, liegt die Lösung fast immer in einer Downloader-Middleware. Das Grundgerüst ist klein:

# middlewares.py
import random

class RandomUserAgentMiddleware:
    UAS = [
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ...",
        "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_4) AppleWebKit/605.1.15 ...",
    ]
    def process_request(self, request, spider):
        request.headers["User-Agent"] = random.choice(self.UAS)

Registriere es in settings.py. Scrapy wird mit einem vorbelegten Standard-Middleware-Stack ausgeliefert (über zehn sind standardmäßig aktiviert), und die Prioritätsnummern der Middleware werden in der Regel als dokumentierter ganzzahliger Bereich angegeben. Gemäß den Empfehlungen der Community sollte benutzerdefinierte Anti-Bot-Middleware vor der integrierten RetryMiddleware, deren Standardpriorität 550 ist, damit bei Wiederholungsversuchen Ihre rotierte Identität verwendet wird.

DOWNLOADER_MIDDLEWARES = {
    "bookstore.middlewares.RandomUserAgentMiddleware": 400,
    "scrapy.downloadermiddlewares.useragent.UserAgentMiddleware": None,  # disable default
}

Für die Proxy-Rotation setzen Sie request.meta["proxy"] in process_request. Es gibt Community-Plugins sowohl für rotierende Proxys als auch für zufällige User-Agents (sowie für verteiltes Crawling, persistentes Caching und Überwachung), aber überprüfen Sie den aktuellen Wartungsstatus jedes Projekts, bevor Sie es in der Produktion für Web-Scraping mit Scrapy in ernsthaftem Umfang einsetzen.

Der ehrliche Kompromiss: Irgendwann wird das Erstellen eigener Header, privater IP-Adressen und das Lösen von CAPTCHAs zu einem Nebenprojekt. Genau hier lässt sich eine verwaltete Scraper-API nahtlos einbinden. Implementieren Sie eine Middleware, die request.url so umschreibt, dass sie auf den API-Endpunkt verweist, und Ihren API-Schlüssel als Header hinzufügt – der Rest Ihres Spiders bleibt unverändert.

Scrapy-Playwright: Die JavaScript-Fluchtklappe

Scrapy führt JavaScript nicht selbst aus, daher geben mit Angular, React oder einem anderen clientseitigen Framework erstellte Websites die Shell-HTML-Datei zurück und nicht die Daten, die du im Browser sehen kannst. Die sauberste Lösung im Jahr 2026 für das Web-Scraping mit Scrapy auf dynamischen Seiten ist scrapy-playwright, das den Standard-Downloader gegen ein echtes headless Chromium austauscht, wenn du dies pro Anfrage aktivierst.

Installieren Sie es und überprüfen Sie die aktuelle Syntax für die Handler-Registrierung anhand der scrapy-playwright-README-Datei bei der Installation:

# settings.py
DOWNLOAD_HANDLERS = {
    "http": "scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler",
    "https": "scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler",
}
TWISTED_REACTOR = "twisted.internet.asyncioreactor.AsyncioSelectorReactor"

Aktivieren Sie die Anfragen, indem Sie meta:

def start_requests(self):
    yield scrapy.Request(
        "https://example-spa.com/products",
        meta={
            "playwright": True,
            "playwright_page_methods": [
                ("wait_for_selector", "article.product"),
            ],
        },
    )

Markieren Sie nur die URLs, die tatsächlich einen Browser benötigen. Jede Playwright-Anfrage ist sowohl in Bezug auf die CPU-Auslastung als auch auf die Latenz deutlich aufwendiger als ein einfacher Scrapy-Fetch, daher ist ein Hybrid-Spider (HTML für Listen, Playwright für Produktdetails) in der Regel die richtige Wahl. Wenn Sie eine ausführlichere Anleitung oder einen Vergleich mit dem älteren Splash-Backend wünschen, behandelt unser Scrapy-Playwright-Tutorial die Muster im Detail.

Protokollierung, Verträge und Bereitstellung

Produktionsreifes Web-Scraping mit Scrapy erfordert drei Dinge, die in Tutorials meist übersprungen werden.

Protokollierung. Setze LOG_LEVEL = "INFO" in settings.py für normale Läufe und "DEBUG" nur, wenn etwas schiefgeht. Leiten Sie die Protokolle mit LOG_FILE oder leiten Sie sie an ein strukturiertes Backend weiter.

Spider-Verträge. Fügen Sie Callbacks Docstring-Verträge hinzu und führen Sie diese scrapy check in CI aus. Ein typischer Vertrag legt die URL, erwartete Felder und die Mindestanzahl an Elementen fest, sodass eine unbemerkte Änderung an der Website den Build unterbricht, anstatt den Datensatz.

def parse(self, response):
    """
    @url https://books.toscrape.com/
    @returns items 20 20
    @scrapes title price rating
    """

Planung und Bereitstellung. scrapyd führt dein Projekt als langlebigen Daemon aus, den du über scrapyd-client. Für containerbasierte Stacks erstellen Sie ein schlankes Docker-Image mit Ihrem Projekt und führen es scrapy crawl nach einem Cron-Zeitplan (oder einem Kubernetes CronJob). Speichern Sie die Ausgaben in jedem Fall auf einem dauerhaften Speichermedium, nicht im Dateisystem des Containers.

Häufige Fallstricke und wie man sie behebt

  • Leere Selektoren. Der Selektor funktionierte in der Shell, gibt None im Spider. Fast immer JavaScript-gerendert. Überprüfen Sie dies mit view(response) und wechsle zu scrapy-playwright für diese URL.
  • 403 und 429 Stürme. Dein Fingerabdruck ist offensichtlich. Füge eine zufällige User-Agent-Middleware hinzu, senke CONCURRENT_REQUESTS_PER_DOMAIN, erhöhe AUTOTHROTTLE_START_DELAYund bestätige RETRY_HTTP_CODES enthält 429.
  • Unendliche Paginierungsschleifen. Der „next“-Selektor trifft auch auf die letzte Seite. Verankere ihn an einer CSS-Klasse, die am Ende verschwindet, oder lege DEPTH_LIMIT.
  • Elemente wurden stillschweigend entfernt. Eine Pipeline hat einen Fehler ausgelöst DropItem und Sie haben es nie bemerkt. Bump LOG_LEVEL auf DEBUG, durchsuche das Protokoll nach Dropped:und überprüfe deine Bereichsprüfungen.
  • Doppelte URLs schlüpfen durch. RFPDupeFilter vergleicht anhand des Fingerabdrucks, sodass sich URLs einschleichen können, die sich nur in der Reihenfolge der Abfrageparameter unterscheiden. Normalisieren Sie URLs, bevor Sie Anfragen weiterleiten.

Wichtige Erkenntnisse

  • Web-Scraping mit Scrapy lohnt sich, wenn Sie Zeitplanung, Deduplizierung, Wiederholungsversuche, Drosselung und Pipelines benötigen, die von Haus aus miteinander verknüpft sind – nicht, wenn ein 20-zeiliges Skript ausreicht.
  • Ordnen Sie jedes Symptom einer Lebenszyklusphase zu: Blöcke befinden sich in Downloader-Middlewares, fehlende Elemente in Pipelines und Selektor-Fehler deuten meist auf JavaScript-Rendering hin.
  • Item-Loader mit MapCompose, TakeFirstund Join sorgen dafür, dass die Bereinigungslogik in allen Spidern wiederverwendbar bleibt, anstatt sie über Callbacks hinweg zu kopieren.
  • Speichern Sie mit FEEDS für portable Formate und einer benutzerdefinierten Pipeline für relationale Speicherung. Verwenden Sie beides, wobei die Pipeline-Prioritäten die Validierung vor dem Datenbank-Writer anordnen.
  • Behandeln Sie AutoThrottle, Wiederholungscodes und eine verwaltete Scraping-API als mehrstufige Verteidigung gegen Sperren. Greifen Sie auf scrapy-playwright nur dann, wenn der HTML-Code tatsächlich leer ist.

FAQ

Lohnt es sich im Jahr 2026 noch, Scrapy zu lernen, im Vergleich zu neueren asynchronen Bibliotheken?

Ja, für Crawls, die über einige hundert Seiten hinausgehen. Neuere asynchrone Stacks wie httpx plus selectolax eignen sich hervorragend für einmalige Skripte, aber Scrapy bündelt den Scheduler, den Duplikatsfilter, die Retry-Middleware, Signale und Feed-Exporte, die Sie sonst selbst schreiben müssten. Für einen wiederkehrenden Produktions-Crawler überzeugt dieses „Batteries-included“-Design nach wie vor bei den Wartungskosten.

Kann Scrapy JavaScript-gerenderte Seiten selbst scrapen, oder brauche ich Playwright oder Splash?

Nicht von selbst. Scrapy ruft rohes HTML ab und führt kein JavaScript aus, sodass Single-Page-Apps Shell-Markup zurückgeben. Die derzeit beste Option ist scrapy-playwright, das den Downloader pro Anfrage gegen ein echtes headless Chromium austauscht. scrapy-splash funktioniert für manche Teams immer noch, aber Playwright bietet eine breitere Browserunterstützung und wird aktiv gewartet.

Wie schneidet Scrapy im Vergleich zu Beautiful Soup und Selenium bei Projekten unterschiedlicher Größe ab?

Beautiful Soup ist ein Parser, kein Crawler, und eignet sich gut requests für kleine statische Scrapes. Selenium steuert einen vollständigen Browser an und eignet sich am besten für zustandsbehaftete, interaktive Abläufe wie Dashboards nach dem Einloggen. Scrapy liegt dazwischen: ein Crawling-Framework mit hohem Durchsatz für Hunderte bis Millionen von Seiten, bei dem die Browser-Rendering-Funktionalität bei Bedarf scrapy-playwright bei Bedarf integriert.

Wie stelle ich einen Scrapy-Spider so bereit, dass er in der Produktion nach einem Zeitplan läuft?

Drei gängige Muster. Führen Sie scrapyd als Daemon aus und lösen Sie Jobs über dessen HTTP-API aus. Erstellen Sie ein Docker-Image mit Ihrem Projekt und planen Sie scrapy crawl <name> über Cron oder einen Kubernetes CronJob. Oder nutzen Sie eine verwaltete Scraping-Plattform, die Spiders für Sie hostet. Speichern Sie die Ergebnisse in jedem Fall auf einem dauerhaften Speicher wie S3 oder einer Datenbank, niemals im Dateisystem eines Containers.

Wie verhindere ich, dass mein Scrapy-Spider blockiert oder per IP gesperrt wird?

Setze mehrere Verteidigungsschichten ein. Aktiviere AutoThrottle, randomisieren User-Agent Header über eine Downloader-Middleware, fügen Sie 429 in RETRY_HTTP_CODESund senken CONCURRENT_REQUESTS_PER_DOMAIN. Bei sichereren Websites leiten Sie Anfragen über Residential-Proxys oder eine verwaltete Scraper-API weiter, die die Rotation und das Lösen von CAPTCHAs hinter einem Endpunkt übernimmt. Halten Sie robots.txt und Ratenbegrenzungen, wann immer möglich.

Zusammenfassung

Der Sinn des Web-Scrapings mit Scrapy besteht nicht darin, dass man weniger Code schreibt als mit requests plus BeautifulSoup. Meistens schreiben Sie am ersten Tag sogar mehr. Der Sinn besteht darin, dass der Code, den Sie am ersten Tag schreiben, auch am neunzigsten Tag noch funktioniert, da sich die Engine, der Scheduler, der Duplikatsfilter, die Drosselung, die Wiederholungsschicht und der Pipeline-Vertrag im Hintergrund nicht ändern. Sie schaffen sich eine stabile Grundlage und passen dann die Spider, Items und Middlewares an jede Ziel-Website an.

Wenn du dir eine Sache aus diesem Leitfaden merken solltest, dann ist es der Lebenszyklus von Anfrage und Antwort. Jeder Scrapy-Fehler, auf den du jemals stoßen wirst, tritt in einer bestimmten Phase dieser Schleife auf, und die Phase zu benennen ist schon die halbe Lösung. Selektoren schlagen im Callback fehl. Items verschwinden in der Pipeline. Sperren treten im Downloader auf. Die Paginierung dreht sich endlos in deiner Callback-Logik. Ordne das Symptom der Phase zu, und die Lösung wird offensichtlich.

Wenn der Anti-Bot-Druck das übersteigt, was Sie in middlewares.py, ist das der richtige Moment, die Anforderungsschicht auszulagern. Bei WebScrapingAPI haben wir die Scraper-API genau für diese Übergabe entwickelt: Behalte deine Scrapy-Spider, deine Items und deine Pipelines und lass einen verwalteten Endpunkt sich um Proxys, CAPTCHA-Lösung und JavaScript-Rendering kümmern. Dein Spider bleibt Scrapy. Die Hindernisse werden zum Problem eines anderen.

Über den Autor
Mihai Maxim, Full-Stack-Entwickler @ WebScrapingAPI
Mihai MaximFull-Stack-Entwickler

Mihai Maxim ist Full-Stack-Entwickler bei WebScrapingAPI, wo er in verschiedenen Bereichen des Produkts mitwirkt und an der Entwicklung zuverlässiger Tools und Funktionen für die Plattform mitarbeitet.

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.