Screaming Frog ist der Standard-Crawler für die meisten SEOs, aber Sie sind wahrscheinlich schon an seine Grenzen gestoßen: das 500-URL-Limit in der kostenlosen Version, RAM-Überlastung bei großen Seiten oder der Wunsch, Crawls zu automatisieren, ohne eine GUI zu beaufsichtigen. Scrapy ist das Open-Source Python-Framework, das diese Limits beseitigt.

Wenn Sie npm install oder git clone ausführen können, können Sie auch Scrapy ausführen. Die Lernkurve ist real, aber machbar, besonders wenn Sie sich bereits durch agentische Coding-Workflows mit CLI-Tools vertraut machen.

Warum Scrapy?

Hauptvorteile

Screaming Frog funktioniert gut für schnelle Audits. Aber es hat Grenzen:

Einschränkung Auswirkung
500 URL Gratis-Limit Erfordert 259$/Jahr Lizenz für größere Seiten
Speicherhungrig Große Crawls können 8GB+ RAM verbrauchen
GUI-abhängig Schwierig zu automatisieren oder zu planen
Begrenzte Anpassung Konfigurationsoptionen sind fest

Scrapy löst diese Probleme:

Scrapy Was Sie bekommen
Kostenlos und Open-Source Keine URL-Limits, keine Lizenzgebühren
Geringerer Speicherverbrauch Festplatten-gestützte Warteschlangen halten RAM im Rahmen
CLI-nativ Skriptfähig, cron-fähig, CI/CD-bereit
Volle Python-Anpassung Extrahieren Sie, was Sie brauchen, filtern Sie, wie Sie wollen
Pausieren/Fortsetzen Stoppen und setzen Sie große Crawls jederzeit fort
Scrapy wird Screaming Frog nicht für alles ersetzen. Schnelle Audits sind in einer GUI immer noch schneller. Aber für großangelegte Crawls, Automatisierung und benutzerdefinierte Extraktion lohnt es sich, es in Ihrem Toolkit zu haben.

Installation

Setup

Scrapy läuft auf Python. Verwenden Sie eine virtuelle Umgebung, um alles sauber zu halten:

Debian/Ubuntu:

sudo apt install python3.11-venv
python3 -m venv venv
source venv/bin/activate
pip install scrapy

macOS:

python3 -m venv venv
source venv/bin/activate
pip install scrapy

Windows:

python -m venv venv
venv\Scripts\activate
pip install scrapy
Virtuelle Umgebungen sind wichtig
Verwenden Sie immer ein venv. Globale Installation verursacht Abhängigkeitskonflikte und beeinträchtigt die Reproduzierbarkeit.

Ein Projekt erstellen

Mit installiertem Scrapy:

scrapy startproject myproject
cd myproject
scrapy genspider sitename example.com

Dies erstellt:

myproject/
    scrapy.cfg
    myproject/
        __init__.py
        items.py
        middlewares.py
        pipelines.py
        settings.py
        spiders/
            __init__.py
            sitename.py

Spider-Code gehört in spiders/sitename.py. Konfiguration liegt in settings.py.

Einstellungen für höfliches Crawlen

Kritisch

Konfigurieren Sie settings.py, bevor Sie irgendetwas ausführen. Blockiert zu werden verschwendet mehr Zeit als langsames Crawlen.

# Höfliches Crawlen
CONCURRENT_REQUESTS_PER_DOMAIN = 5
DOWNLOAD_DELAY = 1
ROBOTSTXT_OBEY = True

# AutoThrottle - passt Geschwindigkeit basierend auf Server-Antwort an
AUTOTHROTTLE_ENABLED = True
AUTOTHROTTLE_START_DELAY = 5
AUTOTHROTTLE_MAX_DELAY = 60
AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0
AUTOTHROTTLE_DEBUG = True

# Sicherheitsgrenzen
CLOSESPIDER_PAGECOUNT = 10000

# Ausgabe
FEED_EXPORT_ENCODING = "utf-8"
Mit aktiviertem AutoThrottle sind 5 gleichzeitige Anfragen ein vernünftiger Ausgangspunkt. AutoThrottle wird automatisch zurückfahren, wenn der Server Probleme hat. Ohne AutoThrottle beginnen Sie niedriger bei 1-3.

AutoThrottle

AutoThrottle überwacht Server-Antwortzeiten und passt die Crawl-Geschwindigkeit automatisch an:

  • Schnelle Antworten → beschleunigt
  • Langsame Antworten → verlangsamt
  • Fehler/Timeouts → verlangsamt deutlich

Im Gegensatz zu Screaming Frogs festen Verzögerungen passt es sich an tatsächliche Serverbedingungen an.

Status-Code-Behandlung

Standardmäßig verwirft Scrapys HttpErrorMiddleware nicht-2xx-Antworten stillschweigend. Das bedeutet, 404s, 301s, 500s werden verworfen, bevor sie Ihren Callback erreichen. Ihr Crawl zeigt möglicherweise 100% 200-Statuscodes, nicht weil die Seite perfekt ist, sondern weil Fehler herausgefiltert werden.

Fügen Sie dies zu Ihrer Spider-Klasse hinzu, um alle Statuscodes zu erfassen:

handle_httpstatus_list = [200, 301, 302, 403, 404, 500, 502, 503]

Screaming Frog erfasst standardmäßig alle Statuscodes. Diese Einstellung bringt Scrapy mit diesem Verhalten in Einklang.

Praxisleistung

Benchmarks

Tatsächliche Zahlen von einem Test-Crawl mit 5 gleichzeitigen Anfragen und aktiviertem AutoThrottle:

Crawl-Fortschritt Seiten/Minute Notizen
0-200 Seiten 14-22 Anlaufphase
200-500 Seiten 10-12 Stabilisierung
500-1.000 Seiten 7-10 AutoThrottle passt an
1.000+ Seiten 5-7 Stabiler Zustand
Geschwindigkeit vs. Zuverlässigkeit
Diese Geschwindigkeiten wirken langsam. Das ist der Sinn. AutoThrottle priorisiert Server-Gesundheit über rohe Geschwindigkeit. Blockiert zu werden und neu zu starten verschwendet mehr Zeit als ein methodischer Crawl.

Funktionsvergleich

Funktion Screaming Frog Scrapy
Kosten Kostenlos <500 URLs, ~259$/Jahr Kostenlos, Open Source
Max. Crawl-Größe Speicherbegrenzt Festplatten-gestützte Warteschlangen
Anpassung Begrenzte Konfigurationsoptionen Voller Python-Code
Planung Manuell oder Drittanbieter Native CLI, cron-fähig
Pausieren/Fortsetzen Ja Ja (mit JOBDIR)
Lernkurve Niedrig (GUI) Mittel (Code)
Rate-Limiting Grundlegende feste Verzögerungen AutoThrottle (adaptiv)
JavaScript-Rendering Optional (Chrome) Optional (playwright/splash)
Statuscodes Alle standardmäßig Erfordert Konfiguration
Subdomain-Filterung GUI-Checkboxen Code (flexibles Regex)
Export-Formate CSV, Excel, etc. JSON, CSV, XML, benutzerdefiniert
CI/CD-Integration Schwierig Nativ

URL-Filterung

Präzise Kontrolle

Screaming Frog verwendet Checkboxen. Scrapy verwendet Code. Der Kompromiss ist Lernkurve für Präzision.

Internationale Pfade ausschließen:

import re
from urllib.parse import urlparse

class MySiteSpider(scrapy.Spider):
    name = "mysite"
    allowed_domains = ["example.com", "www.example.com"]
    start_urls = ["https://www.example.com/"]

    # Überspringe internationale Pfade wie /uk/, /fr/, /de/
    EXCLUDED_PATTERNS = re.compile(
        r"/(in|au|th|es|hk|sg|ph|my|ca|cn|uk|kr|id|fr|vn|de|jp|nl|it|tw)/"
    )

    def filter_links(self, links):
        filtered = []
        for link in links:
            hostname = urlparse(link.url).hostname or ""
            if hostname not in ("example.com", "www.example.com"):
                continue
            if self.EXCLUDED_PATTERNS.search(link.url):
                continue
            filtered.append(link)
        return filtered

Sie können nach URL-Mustern, Query-Parametern, Response-Headern, Seiteninhalt oder jeder Kombination filtern.

Pausieren und Fortsetzen

Essentiell

Für Crawls über 1.000 Seiten aktivieren Sie Pausieren/Fortsetzen mit JOBDIR:

scrapy crawl myspider -o output.json -s JOBDIR=crawl_state

Scrapy speichert den Status in crawl_state/. Drücken Sie Strg+C zum Pausieren. Führen Sie denselben Befehl aus, um fortzusetzen.

Verwenden Sie immer JOBDIR für Produktions-Crawls. Schützt vor Netzwerkproblemen, Systemneustarts oder wenn Sie einfach für den Tag aufhören müssen.

Der Status umfasst ausstehende URLs, gesehene URLs und die Anfragewarteschlange. Dies ist robuster als die Speichern/Laden-Funktion von Screaming Frog, da es dateibasiert ist und Systemneustarts übersteht.

JavaScript-Rendering

Scrapy ruft nur rohes HTML ab. Es rendert kein JavaScript. Das ist dasselbe, was curl zurückgibt.

Für die meisten SEO-Crawls ist das in Ordnung:

  • Meta-Tags, Canonicals und H1s sind normalerweise im initialen HTML
  • Suchmaschinen indexieren hauptsächlich server-gerenderten Inhalt
  • Die meisten E-Commerce- und Content-Seiten sind server-gerendert

Wenn Ihre Zielseite Inhalte client-seitig rendert, haben Sie Optionen:

Paket Hinweise
scrapy-playwright Verwendet Chromium/Firefox/WebKit. Empfohlen für moderne JS-Seiten
scrapy-splash Leichtgewichtig, Docker-basierter Renderer
scrapy-selenium Älterer Ansatz, funktioniert noch

JS-Rendering ist deutlich langsamer und ressourcenintensiver. Fügen Sie es nur hinzu, wenn die Seite es erfordert.

Screaming Frog hat einen ähnlichen Kompromiss. Das Aktivieren von JavaScript-Rendering verwendet Chrome im Hintergrund und verlangsamt Crawls erheblich.

Speicherverwaltung

Bei ~1.300 Seiten mit vollständiger Feldextraktion:

  • Speicher: ~265 MB
  • CPU: ~4%

Die Verwendung von JOBDIR verschiebt Anfragewarteschlangen auf die Festplatte und hält den Speicher niedrig. Für sehr große Crawls (100k+ URLs) fügen Sie diese Einstellungen hinzu:

MEMUSAGE_LIMIT_MB = 1024
MEMUSAGE_WARNING_MB = 800
SCHEDULER_DISK_QUEUE = 'scrapy.squeues.PickleFifoDiskQueue'
SCHEDULER_MEMORY_QUEUE = 'scrapy.squeues.FifoMemoryQueue'

Dies begrenzt die Speichernutzung und erzwingt festplatten-gestützte Warteschlangen für den Scheduler.

Ausgabedaten

Anpassbar

Grundlegende Spider-Ausgabe:

{
    "url": "https://www.example.com/page/",
    "title": "Seitentitel hier",
    "status": 200
}

Für SEO-Crawls benötigen Sie Felder ähnlich dem, was Screaming Frog exportiert:

def parse_page(self, response):
    yield {
        "url": response.url,
        "status": response.status,
        "title": response.css("title::text").get(),
        "meta_description": response.css("meta[name='description']::attr(content)").get(),
        "meta_robots": response.css("meta[name='robots']::attr(content)").get(),
        "h1": response.css("h1::text").get(),
        "canonical": response.css("link[rel='canonical']::attr(href)").get(),
        "og_title": response.css("meta[property='og:title']::attr(content)").get(),
        "og_description": response.css("meta[property='og:description']::attr(content)").get(),
        "word_count": len(response.text.split()) if response.status == 200 else None,
        "content_type": response.headers.get("Content-Type", b"").decode("utf-8", errors="ignore"),
    }

Fügen Sie Felder basierend auf Ihren Anforderungen hinzu oder entfernen Sie sie. CSS-Selektoren funktionieren für jedes On-Page-Element.

Export-Formate: JSON (-o output.json), JSON Lines (-o output.jsonl), CSV (-o output.csv), XML (-o output.xml).

JSON Lines ist am besten für große Crawls. Dateien sind während des Crawls zeilenweise gültig, sodass Sie mit tail -f überwachen können. Standard-JSON ist erst nach Abschluss des Crawls gültig.

Screaming Frog → Scrapy

Übersetzungsanleitung

SF-Workflows auf Scrapy abbilden:

Screaming Frog Aktion Scrapy Äquivalent
Neuen Crawl starten scrapy crawl spidername
Crawl-Verzögerung setzen DOWNLOAD_DELAY in Einstellungen
Gleichzeitige Threads begrenzen CONCURRENT_REQUESTS_PER_DOMAIN
robots.txt respektieren ROBOTSTXT_OBEY = True
Nach CSV exportieren -o output.csv
Crawl speichern/laden -s JOBDIR=crawl_state
Subdomains filtern Code im Spider (Regex)
Benutzerdefinierte Extraktion CSS/XPath-Selektoren in parse()

Denkweisenänderungen:

  1. Konfiguration ist Code. Bearbeiten Sie settings.py statt Checkboxen anzuklicken.
  2. Extraktion ist explizit. Sie schreiben, welche Daten erfasst werden sollen.
  3. Planung ist nativ. Fügen Sie Befehle zu cron oder CI/CD hinzu.
  4. Debugging sind Logs. Aktivieren Sie AUTOTHROTTLE_DEBUG, um zu sehen, was passiert.

Vollständiger Workflow

Mit den oben genannten Standardeinstellungen können Sie Scrapy in unter 15 Minuten installiert haben und crawlen:

python3 -m venv venv
source venv/bin/activate  # venv\Scripts\activate unter Windows
pip install scrapy
scrapy startproject urlcrawler
cd urlcrawler
scrapy genspider mysite example.com
# Bearbeiten Sie settings.py mit höflicher Crawl-Konfiguration
# Bearbeiten Sie spiders/mysite.py mit Ihrer Parse-Logik
scrapy crawl mysite -o urls.jsonl -s JOBDIR=crawl_state

Scrapy Shell

Wenn Sie benutzerdefinierte Konfigurationen erstellen, verwenden Sie Scrapy Shell, um Ihre Selektoren und Einstellungen interaktiv zu testen:

scrapy shell "https://example.com"

Dies öffnet eine interaktive Python-Konsole mit bereits geladener Response. Testen Sie CSS- und XPath-Selektoren in Echtzeit, bevor Sie sie zu Ihrem Spider hinzufügen:

>>> response.css('title::text').get()
'Example Domain'
>>> response.xpath('//h1/text()').get()
'Example Domain'

Scrapy Shell reduziert die Iterationszeit erheblich. Validieren Sie Extraktionslogik ohne vollständige Crawls auszuführen.

Vollständige Spider-Vorlage

Ein produktionsreifer Spider mit URL-Filterung, Status-Code-Behandlung und vollständiger SEO-Feldextraktion:

import re
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from urllib.parse import urlparse


class SEOSpider(CrawlSpider):
    name = "seospider"
    allowed_domains = ["example.com"]
    start_urls = ["https://www.example.com"]

    # Erfasse alle HTTP-Statuscodes, nicht nur 2xx
    handle_httpstatus_list = [200, 301, 302, 403, 404, 500, 502, 503]

    # Auszuschließende URL-Muster
    EXCLUDED_PATTERNS = re.compile(
        r"/(in|au|th|es|hk|sg|ph|my|ca|cn|uk|kr|id|fr|vn|de|jp|nl|it|tw)/"
    )

    rules = (
        Rule(
            LinkExtractor(allow=()),
            callback="parse_page",
            follow=True,
            process_links="filter_links",
        ),
    )

    def filter_links(self, links):
        filtered = []
        for link in links:
            parsed = urlparse(link.url)
            hostname = parsed.hostname or ""

            if hostname not in ("example.com", "www.example.com"):
                continue

            if self.EXCLUDED_PATTERNS.search(link.url):
                continue

            filtered.append(link)
        return filtered

    def parse_page(self, response):
        yield {
            "url": response.url,
            "status": response.status,
            "title": response.css("title::text").get(),
            "meta_description": response.css("meta[name='description']::attr(content)").get(),
            "meta_robots": response.css("meta[name='robots']::attr(content)").get(),
            "h1": response.css("h1::text").get(),
            "canonical": response.css("link[rel='canonical']::attr(href)").get(),
            "og_title": response.css("meta[property='og:title']::attr(content)").get(),
            "og_description": response.css("meta[property='og:description']::attr(content)").get(),
            "word_count": len(response.text.split()) if response.status == 200 else None,
            "content_type": response.headers.get("Content-Type", b"").decode("utf-8", errors="ignore"),
        }

Ersetzen Sie example.com durch Ihre Zieldomain. Passen Sie EXCLUDED_PATTERNS an die URL-Struktur Ihrer Seite an.

Wann was verwenden

Screaming Frog:

Scrapy:

  • Seiten über 10.000 URLs
  • Automatisierte, geplante Crawls
  • Benutzerdefinierte Extraktionsanforderungen
  • CI/CD-Integration
  • Speicherbeschränkungen
  • Versionskontrollierte Konfigurationen

Das Fazit

Scrapy hat eine steilere Setup-Kurve als Screaming Frog, aber es beseitigt die praktischen Grenzen, die GUI-Crawler auferlegen. Keine URL-Limits, keine Lizenzgebühren, geringerer Speicherverbrauch und native Automatisierung.

Fangen Sie klein an. Crawlen Sie eine Seite, die Sie kennen. Verwenden Sie konservative Einstellungen. Vergleichen Sie die Ausgabe mit Screaming Frog. Die Daten werden übereinstimmen, aber Sie haben ein Tool, das skaliert.