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?
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 |
Installation
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
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
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"
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
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 |
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
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
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.
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
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
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:
- Konfiguration ist Code. Bearbeiten Sie
settings.pystatt Checkboxen anzuklicken. - Extraktion ist explizit. Sie schreiben, welche Daten erfasst werden sollen.
- Planung ist nativ. Fügen Sie Befehle zu cron oder CI/CD hinzu.
- 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:
- Schnelle Audits unter 500 URLs
- Ergebnisse in Minuten benötigt
- Visuelle Site-Exploration
- Nicht vertraut mit CLI
- Screaming Frog-Daten mit Redirects.net verwenden
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.