Der Parser in Scrapy fällt aufgrund von Zeitüberschreitungen aus, der Proxy-Pool wird viel schneller verbraucht als erwartet, und die Logs sind mit 407- und 403-Antworten überflutet – kommt Ihnen das bekannt vor? In 90 % der Fälle liegt das Problem nicht an den Proxys selbst, sondern daran, wie der DownloaderMiddleware geschrieben ist. Wir analysieren die fünf häufigsten Fehler im Middleware, die teuren Traffic in nutzlose Anfragen verwandeln, und zeigen, wie man sie mit Code beheben kann.
Fehler 1: primitive Rotation ohne Berücksichtigung des Proxy-Zustands
Die häufigste Konstruktion, die in Tutorials zu finden ist, ist random.choice(PROXY_LIST) innerhalb von process_request. Das Problem ist, dass eine solche Rotation nicht weiß, welcher Proxy gerade gesperrt wurde und welcher noch aktiv ist. Infolgedessen sendet der Parser weiterhin Anfragen über eine bereits blockierte IP, erhält 403/429, macht einen Retry – und wählt erneut dieselbe Adresse, da die Auswahl zufällig ist und „schlechte“ Knoten nicht ausschließt.
Der richtige Ansatz besteht darin, den Zustand jedes Proxys zu verfolgen: die Anzahl erfolgreicher Anfragen, die Anzahl der Fehler, die Zeit der letzten Nutzung. Hier ist eine minimal funktionierende Variante:
import random
import time
class ProxyPool:
def __init__(self, proxies):
self.proxies = {p: {"fails": 0, "last_used": 0, "banned_until": 0} for p in proxies}
def get_proxy(self):
now = time.time()
available = [
p for p, state in self.proxies.items()
if state["banned_until"] < now
]
if not available:
# wenn alle gesperrt sind, setzen wir den ältesten Ban zurück
available = list(self.proxies.keys())
return random.choice(available)
def mark_fail(self, proxy, cooldown=300):
self.proxies[proxy]["fails"] += 1
self.proxies[proxy]["banned_until"] = time.time() + cooldown
def mark_success(self, proxy):
self.proxies[proxy]["fails"] = 0
self.proxies[proxy]["last_used"] = time.time()
Ein solcher Pool schließt gesperrte IPs während der „Abkühlzeit“ (cooldown) aus und bringt sie später wieder in den Umlauf. Das reduziert bereits den Traffic erheblich, da Sie nicht ständig denselben blockierten Knoten anfragen.
Fehler 2: falsche Behandlung von Retry und Statuscodes
Der zweite typische Fehler ist die Verwendung des Standard-RetryMiddleware aus Scrapy ohne Modifikation. Standardmäßig wird die Anfrage auf demselben Proxy, der sie fehlschlagen ließ, erneut gesendet, es sei denn, Sie haben ausdrücklich einen Proxywechsel in process_exception eingefügt. Das Ergebnis ist ein klassisches Bild: 3 Retries, 3 Bans, die Anfrage fällt trotzdem aus, und der Traffic ist bereits verbraucht.
Ein weiterer Punkt ist, dass nicht alle Statuscodes gleich behandelt werden sollten. 429 (Too Many Requests) erfordert eine Pause und einen IP-Wechsel, 403 bedeutet normalerweise einen Ban eines bestimmten Proxys (sofortiger Austausch erforderlich), während 5xx oft ein vorübergehendes Problem auf der Serverseite ist, das auf demselben Proxy erneut gesendet werden kann. Wenn alles in einen Topf geworfen wird, verbrennt das Middleware entweder zu aggressiv Proxys oder wartet zu lange, wo sofort ein IP-Wechsel erforderlich gewesen wäre.
class SmartRetryMiddleware:
def __init__(self, pool):
self.pool = pool
def process_response(self, request, response, spider):
proxy = request.meta.get("proxy")
if response.status in (403, 407):
if proxy:
self.pool.mark_fail(proxy, cooldown=600)
new_request = request.copy()
new_request.meta["proxy"] = self.pool.get_proxy()
new_request.dont_filter = True
return new_request
if response.status == 429:
if proxy:
self.pool.mark_fail(proxy, cooldown=120)
new_request = request.copy()
new_request.meta["proxy"] = self.pool.get_proxy()
new_request.dont_filter = True
return new_request
if proxy:
self.pool.mark_success(proxy)
return response
Hier ist es wichtig, die Abkühlzeit nach Fehlerart zu unterscheiden: harter Ban (403/407) – lange Pause, Frequenzlimit (429) – kurze. Das spart Dutzende Prozent Traffic bei langen Durchläufen.
Fehler 3: fehlende Sticky-Sessions für Websites mit Authentifizierung
Wenn der Parser mit einer Website arbeitet, die Login, Warenkorb, Pagination mit Zustandsbeibehaltung oder Captchas mit IP-Überprüfung hat – wechselt der Proxy bei jeder Anfrage, bricht die Sitzung. Die Website sieht, dass Anfrage Nr. 1 von einer IP kam, während Anfrage Nr. 2 (im Rahmen derselben Cookie-Sitzung) von einer anderen kommt, und das löst sofort den Bot-Schutz aus, selbst wenn beide IPs „sauber“ sind.
Die Lösung besteht darin, einen Proxy an eine logische Sitzung (z. B. an ein bestimmtes Konto oder an eine Kette von Anfragen innerhalb einer Domain) für eine feste Zeit zu binden, anstatt ihn bei jeder Anfrage zu wechseln. Dies wird als Sticky-Session bezeichnet.
class StickySessionMiddleware:
def __init__(self, pool, ttl=600):
self.pool = pool
self.ttl = ttl
self.sessions = {} # session_id -> (proxy, expires_at)
def process_request(self, request, spider):
session_id = request.meta.get("session_id")
if not session_id:
return
now = time.time()
session = self.sessions.get(session_id)
if session and session[1] > now:
request.meta["proxy"] = session[0]
else:
proxy = self.pool.get_proxy()
self.sessions[session_id] = (proxy, now + self.ttl)
request.meta["proxy"] = proxy
Für Aufgaben, bei denen eine stabile IP während der gesamten Sitzung erforderlich ist – Authentifizierung, Arbeiten mit einem persönlichen Konto, mehrstufige Formulare – sind am besten residential Proxys mit Unterstützung für Sessions geeignet: Sie ermöglichen es, dieselbe ausgehende IP für mehrere Minuten oder Stunden zu halten und sie dann kontrolliert zu wechseln, anstatt sie zufällig bei jeder Anfrage zu ändern.
Fehler 4: falsche Übertragung der Proxy-Authentifizierung
Der vierte Fehler ist technisch, kommt aber in fast jedem zweiten Projekt vor. Entwickler übergeben den Benutzernamen und das Passwort des Proxys direkt in der URL wie http://user:pass@ip:port über request.meta["proxy"]. Das funktioniert in den meisten Fällen, aber bei der Verwendung von Proxys über ein HTTPS-Tunnel oder bei der Arbeit mit bestimmten Anbietern wird diese Art der Authentifizierung vom Standard-HttpProxyMiddleware nicht korrekt verarbeitet, und die Anfragen fallen mit 407 Proxy Authentication Required aus, obwohl die Anmeldedaten korrekt sind.
Eine zuverlässigere Methode besteht darin, den Proxy-Authorization-Header explizit zu übergeben, kodiert in base64:
import base64
class ProxyAuthMiddleware:
def process_request(self, request, spider):
proxy = request.meta.get("proxy")
if not proxy:
return
# proxy ohne Anmeldedaten in der URL
request.meta["proxy"] = proxy
user = request.meta.get("proxy_user")
password = request.meta.get("proxy_pass")
if user and password:
credentials = f"{user}:{password}"
encoded = base64.b64encode(credentials.encode()).decode()
request.headers["Proxy-Authorization"] = f"Basic {encoded}"
Dieser Ansatz funktioniert stabiler beim Skalieren auf Hunderte paralleler Anfragen und hängt nicht davon ab, wie die spezifische Version der Bibliothek URLs mit eingebetteten Anmeldedaten analysiert. Dies ist besonders kritisch bei der Arbeit mit mobilen Proxys, bei denen die Authentifizierung oft an eine IP-Whitelist oder eine strenge Überprüfung der Header gebunden ist.
Fehler 5: kein Monitoring und Logging von Bans
Der letzte und möglicherweise teuerste Fehler in Bezug auf die Folgen ist das Fehlen von Logs darüber, welche Proxys gesperrt werden, wie oft und auf welchen Domains. Ohne diese Daten ist es unmöglich zu verstehen, was genau den Traffic frisst: ob der Proxy-Pool auf einer bestimmten Website erschöpft ist oder ob das Problem im Parser selbst liegt (zu häufige Anfragen, fehlende Verzögerungen, verdächtige Header).
Das minimale Set an Metriken, das im Middleware protokolliert werden sollte:
- Anzahl der Anfragen pro Proxy während der Parsing-Sitzung
- Anzahl und Codes der Fehler (403, 407, 429, 5xx) pro Proxy
- Lebensdauer des Proxys bis zum ersten Ban
- Domains, auf denen Bans am häufigsten auftreten
import logging
import json
logger = logging.getLogger("proxy_stats")
class ProxyStatsMiddleware:
def __init__(self):
self.stats = {}
def process_response(self, request, response, spider):
proxy = request.meta.get("proxy", "unknown")
domain = request.url.split("/")[2]
key = f"{proxy}|{domain}"
entry = self.stats.setdefault(key, {"requests": 0, "errors": 0})
entry["requests"] += 1
if response.status in (403, 407, 429):
entry["errors"] += 1
if entry["requests"] % 50 == 0:
logger.info(json.dumps(self.stats))
return response
Ohne solche Statistiken sind alle Versuche, das Middleware zu „optimieren“, reines Rätselraten. Damit sehen Sie genau: Wenn beispielsweise eine bestimmte Domain 80 % des Proxy-Pools nach den ersten 10 Anfragen sperrt – liegt das Problem nicht an den Proxys, sondern am Muster der Anfragen (keine Rotation des User-Agent, zu hohe Frequenz, fehlende Verzögerungen zwischen den Anfragen).
Funktionierendes Beispiel für Middleware
Wir fassen alles in settings.py zusammen – die Reihenfolge der Middleware ist entscheidend, da sie bestimmt, in welcher Reihenfolge die Prüfungen angewendet werden:
DOWNLOADER_MIDDLEWARES = {
"myproject.middlewares.ProxyAuthMiddleware": 350,
"myproject.middlewares.StickySessionMiddleware": 400,
"myproject.middlewares.SmartRetryMiddleware": 550,
"myproject.middlewares.ProxyStatsMiddleware": 900,
}
RETRY_ENABLED = False # deaktivieren Sie den Standard-Retry, verwenden Sie Ihren eigenen
DOWNLOAD_TIMEOUT = 15
CONCURRENT_REQUESTS_PER_DOMAIN = 8
Achten Sie auf RETRY_ENABLED = False – das ist entscheidend, sonst wird der eingebaute Retry-Mechanismus von Scrapy mit Ihrer Logik zum Wechseln des Proxys in Konflikt geraten, und Anfragen beginnen, sich zu duplizieren oder zweimal erneut gesendet zu werden. Es ist auch wichtig, CONCURRENT_REQUESTS_PER_DOMAIN zu begrenzen – zu hohe Parallelität für eine Domain sieht selbst mit Proxy-Rotation verdächtig für Anti-Bot-Systeme aus.
Welchen Proxy-Typ für Scrapy wählen
Middleware löst die Hälfte des Problems, die andere Hälfte ist die richtige Auswahl des Proxy-Pools für die Aufgabe. Unten ist ein Vergleich für typische Parsing-Szenarien.
| Proxy-Typ | Wann verwenden | Vorteile | Nachteile |
|---|---|---|---|
| Rechenzentrums-Proxys | Massives Parsen von offenen Seiten ohne strengen Anti-Bot-Schutz | Hohe Geschwindigkeit, niedrige Kosten für Traffic | Leicht zu erkennen, oft auf der Blacklist |
| Residential Proxys | Parsen von Marktplätzen, Websites mit JS-Schutz, Authentifizierung | Echte IPs, niedriger Ban-Prozentsatz, Unterstützung für Sticky-Sessions | Geringere Geschwindigkeit im Vergleich zu DC |
| Mobile Proxys | Parsen von mobilen Versionen von Websites und APIs mit strengen Anti-Bot-Schutz | Maximales Vertrauen seitens der Zielwebsites | Höchste Kosten für Traffic |
Praktische Regel: Für das Parsen statischer Seiten ohne starken Schutz eignen sich günstige Rechenzentrums-Proxys in Kombination mit einem durchdachten Middleware aus diesem Artikel. Wenn die Website Cloudflare, PerimeterX, DataDome oder ähnliche Systeme verwendet – werden Rechenzentrums-IPs fast sofort gesperrt, und es ist vorteilhafter, sofort auf einen Residential-Pool umzusteigen, um Zeit beim Debuggen des Middleware zu sparen.
Checkliste vor dem Start des Parsers in der Produktion
- Middleware schließt gesperrte Proxys während der Abkühlzeit aus und wählt sie nicht zufällig aus
- Retry-Logik unterscheidet zwischen Fehlerarten (403/407 vs 429 vs 5xx) mit unterschiedlicher Abkühlzeit
- Für Sitzungsaufgaben wird eine Sticky-Bindung des Proxys verwendet, nicht ein Wechsel bei jeder Anfrage
- Die Authentifizierung des Proxys wird über den Header Proxy-Authorization übergeben, nicht nur über die URL
- Es wird eine Statistik über Bans nach Domains und Proxys zur Diagnose von Problemen geführt
- Der Standard-RetryMiddleware von Scrapy ist deaktiviert, um Konflikte mit benutzerdefinierter Logik zu vermeiden
- Die Parallelität ist auf vernünftige Werte begrenzt und nicht auf Maximum eingestellt
Fazit
Die meisten Probleme mit dem Verbrauch von Proxy-Traffic in Scrapy werden nicht durch den Kauf eines größeren IP-Pools gelöst, sondern durch die Korrektur der Logik des Middleware: die Unterscheidung von Fehlern nach Typen, Sticky-Sessions für komplexe Szenarien, korrekte Authentifizierung und ständiges Monitoring von Bans. Der Code aus diesem Artikel kann als Grundlage genommen und an ein spezifisches Projekt angepasst werden – die Struktur bleibt sowohl für kleine Parser als auch für verteilte Scrapy-Cluster-Installationen funktional.
Wenn das Middleware bereits richtig konfiguriert ist, aber die Bans trotzdem zu häufig auftreten – liegt es wahrscheinlich an der Qualität des IP-Pools selbst. Für das Parsen von Websites mit fortschrittlichem Anti-Bot-Schutz sollten Sie versuchen, residential Proxys mit Unterstützung für Sessions zu verwenden – sie senken den Prozentsatz der Auslösung des Schutzes im Vergleich zu Rechenzentrumsadressen bei derselben Middleware-Logik erheblich.