Scrapy ist eines der leistungsstärksten Python-Frameworks für Web-Scraping, aber ohne die richtige Proxy-Einrichtung werden Ihre Scraper bereits nach wenigen Minuten blockiert. In diesem Leitfaden zeige ich alle Möglichkeiten zur Integration von Proxys in Scrapy: von der einfachsten Einrichtung bis hin zu fortgeschrittenen Methoden der IP-Rotation mit automatischer Fehlerbehandlung.
Das Material basiert auf realen Erfahrungen beim Scraping großer E-Commerce-Plattformen und geschützter Websites. Sie erhalten fertige Codebeispiele, die Sie sofort in Ihren Projekten verwenden können.
Warum Scrapy ohne Proxy blockiert wird
Moderne Websites verwenden mehrschichtige Schutzmaßnahmen gegen Scraping. Selbst wenn Sie den User-Agent und Verzögerungen zwischen Anfragen eingestellt haben, gibt Ihre IP-Adresse die Automatisierung durch mehrere Merkmale preis:
- Anfragefrequenz: Eine IP stellt 100+ Anfragen pro Minute — ein eindeutiges Zeichen für einen Bot
- Verhaltensmuster: Konsistentes Durchlaufen von Seiten ohne zufällige Übergänge
- Fehlender JavaScript: Scrapy führt kein JS aus, was leicht erkannt werden kann
- Geolokalisierung: Zugriff aus einem Rechenzentrum anstelle eines Heimnetzwerks
Das Ergebnis — IP-Sperrung für mehrere Stunden oder Tage. Besonders aggressive Schutzmaßnahmen verwenden Marktplätze (Amazon, Wildberries, Ozon), soziale Netzwerke und Websites mit Cloudflare. Proxys lösen dieses Problem, indem sie Anfragen auf viele IP-Adressen verteilen.
Wichtig: Auch mit Proxys müssen die Rate Limits eingehalten werden. Empfohlene Geschwindigkeit: 1-3 Anfragen pro Sekunde pro IP. Für schnelles Scraping verwenden Sie einen Pool von 50+ Proxys mit Rotation.
Grundlegende Proxy-Einrichtung in Scrapy
Der einfachste Weg ist, den Proxy direkt in den Einstellungen des Spiders anzugeben. Diese Methode eignet sich für Tests oder das Scraping kleiner Datenmengen mit einem Proxy-Server.
Methode 1: Über meta in Request
import scrapy
class MySpider(scrapy.Spider):
name = 'example'
start_urls = ['https://example.com']
def start_requests(self):
proxy = 'http://username:password@proxy.example.com:8080'
for url in self.start_urls:
yield scrapy.Request(
url=url,
callback=self.parse,
meta={'proxy': proxy}
)
def parse(self, response):
# Ihre Scraping-Logik
self.log(f'Scraped {response.url} via {response.meta["proxy"]}')
Das Format des Proxys hängt vom Protokoll und der Authentifizierungsmethode ab:
http://proxy.example.com:8080— ohne Authentifizierunghttp://user:pass@proxy.example.com:8080— mit Benutzername/Passwortsocks5://user:pass@proxy.example.com:1080— SOCKS5-Proxy
Methode 2: Globale Einstellungen in settings.py
# settings.py
# HTTP-Proxy für alle Anfragen
HTTPPROXY_ENABLED = True
HTTPPROXY_AUTH_ENCODING = 'utf-8'
# Einstellungen über Umgebungsvariablen
HTTP_PROXY = 'http://username:password@proxy.example.com:8080'
HTTPS_PROXY = 'http://username:password@proxy.example.com:8080'
Diese Methode ist praktisch für schnelle Tests, eignet sich jedoch nicht für die Produktion: Es gibt keine IP-Rotation, bei einem Ausfall des Proxys stoppt der gesamte Scraper, und es ist nicht möglich, verschiedene Proxys für verschiedene Websites zu verwenden.
Erstellung einer benutzerdefinierten Proxy-Middleware
Für das Produktions-Scraping benötigen Sie eine eigene Middleware, die den Proxy-Pool verwaltet, Fehler behandelt und IPs automatisch rotiert. Hier ist eine grundlegende Implementierung:
# middlewares.py
import random
from scrapy import signals
from scrapy.exceptions import NotConfigured
class RandomProxyMiddleware:
def __init__(self, proxy_list):
self.proxy_list = proxy_list
@classmethod
def from_crawler(cls, crawler):
# Laden der Proxy-Liste aus den Einstellungen
proxy_list = crawler.settings.getlist('PROXY_LIST')
if not proxy_list:
raise NotConfigured('PROXY_LIST nicht konfiguriert')
return cls(proxy_list)
def process_request(self, request, spider):
# Wählen Sie einen zufälligen Proxy aus dem Pool
proxy = random.choice(self.proxy_list)
request.meta['proxy'] = proxy
spider.logger.info(f'Using proxy: {proxy}')
def process_exception(self, request, exception, spider):
# Bei einem Fehler versuchen wir einen anderen Proxy
proxy = random.choice(self.proxy_list)
request.meta['proxy'] = proxy
spider.logger.warning(
f'Proxy-Fehler, Wechsel zu: {proxy}'
)
return request
Jetzt konfigurieren wir die Verwendung der Middleware in settings.py:
# settings.py
# Liste der Proxys (kann aus einer Datei oder API geladen werden)
PROXY_LIST = [
'http://user1:pass1@proxy1.example.com:8080',
'http://user2:pass2@proxy2.example.com:8080',
'http://user3:pass3@proxy3.example.com:8080',
# ... fügen Sie 50+ Proxys für eine effektive Rotation hinzu
]
# Middleware aktivieren
DOWNLOADER_MIDDLEWARES = {
'myproject.middlewares.RandomProxyMiddleware': 350,
'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': 400,
}
# Wiederholungsversuche bei Fehlern
RETRY_TIMES = 3
RETRY_HTTP_CODES = [500, 502, 503, 504, 408, 429]
Proxy-Rotation: drei funktionierende Methoden
Die zufällige Auswahl von Proxys (wie im obigen Beispiel) ist die einfachste, aber nicht die effektivste Methode. Lassen Sie uns drei Rotationsstrategien für verschiedene Szenarien betrachten.
Methode 1: Round-robin (sequentielle Rotation)
Proxys werden im Kreis ausgewählt. Geeignet für eine gleichmäßige Lastverteilung:
class RoundRobinProxyMiddleware:
def __init__(self, proxy_list):
self.proxy_list = proxy_list
self.current_index = 0
@classmethod
def from_crawler(cls, crawler):
proxy_list = crawler.settings.getlist('PROXY_LIST')
return cls(proxy_list)
def process_request(self, request, spider):
# Nehmen Sie den nächsten Proxy im Kreis
proxy = self.proxy_list[self.current_index]
self.current_index = (self.current_index + 1) % len(self.proxy_list)
request.meta['proxy'] = proxy
Methode 2: Intelligente Rotation mit Blacklist
Verfolgen Sie problematische Proxys und schließen Sie diese vorübergehend von der Rotation aus:
import time
from collections import defaultdict
class SmartProxyMiddleware:
def __init__(self, proxy_list):
self.proxy_list = proxy_list
self.proxy_errors = defaultdict(int)
self.blacklist = set()
self.blacklist_timeout = 300 # 5 Minuten
self.blacklist_time = {}
@classmethod
def from_crawler(cls, crawler):
proxy_list = crawler.settings.getlist('PROXY_LIST')
return cls(proxy_list)
def get_working_proxies(self):
# Entfernen Sie Proxys aus der Blacklist, deren Timeout abgelaufen ist
current_time = time.time()
expired = [
proxy for proxy, ban_time in self.blacklist_time.items()
if current_time - ban_time > self.blacklist_timeout
]
for proxy in expired:
self.blacklist.discard(proxy)
self.proxy_errors[proxy] = 0
# Geben Sie die funktionierenden Proxys zurück
return [p for p in self.proxy_list if p not in self.blacklist]
def process_request(self, request, spider):
working_proxies = self.get_working_proxies()
if not working_proxies:
spider.logger.error('Alle Proxys sind auf der Blacklist!')
return
proxy = random.choice(working_proxies)
request.meta['proxy'] = proxy
def process_response(self, request, response, spider):
# Wenn eine Sperrung auftritt, fügen Sie sie zur Blacklist hinzu
if response.status in [403, 429, 503]:
proxy = request.meta.get('proxy')
self.proxy_errors[proxy] += 1
if self.proxy_errors[proxy] >= 3:
self.blacklist.add(proxy)
self.blacklist_time[proxy] = time.time()
spider.logger.warning(
f'Proxy {proxy} für {self.blacklist_timeout}s auf die Blacklist gesetzt'
)
return response
Methode 3: Rotation über den API-Anbieter
Viele Proxy-Anbieter (einschließlich residential Proxys) bieten einen rotating endpoint — eine URL, die bei jeder Anfrage automatisch die IP wechselt:
# settings.py
# Ein einziger Endpoint mit automatischer Rotation
ROTATING_PROXY = 'http://username:password@rotating.proxy.com:8080'
# Einfache Middleware
class RotatingProxyMiddleware:
def __init__(self, proxy):
self.proxy = proxy
@classmethod
def from_crawler(cls, crawler):
proxy = crawler.settings.get('ROTATING_PROXY')
return cls(proxy)
def process_request(self, request, spider):
# Eine URL, aber jede Anfrage erfolgt mit einer neuen IP
request.meta['proxy'] = self.proxy
Dies ist die bequemste Methode für die Produktion: Es ist nicht erforderlich, einen Proxy-Pool zu verwalten, der Anbieter überwacht die Qualität der IPs und ersetzt problematische. Besonders effektiv funktioniert es mit residential Proxys, bei denen der Pool von IPs Millionen von Adressen erreichen kann.
Authentifizierung: Benutzername/Passwort vs. IP-Whitelist
Proxy-Anbieter bieten zwei Authentifizierungsmethoden an. Die Wahl beeinflusst die Verbindungsgeschwindigkeit und die Benutzerfreundlichkeit der Einrichtung.
Benutzer:Pass-Authentifizierung
Benutzername und Passwort werden in der URL des Proxys übergeben. Scrapy konvertiert sie automatisch in den HTTP-Header Proxy-Authorization:
proxy = 'http://username:password@proxy.example.com:8080'
request.meta['proxy'] = proxy
# Scrapy fügt automatisch den Header hinzu:
# Proxy-Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
Vorteile: funktioniert von jeder IP, einfach Proxys im Code zu ändern.
Nachteile: geringer Overhead pro Anfrage (~50-100ms), Anmeldedaten im Klartext im Code.
IP-Whitelist-Authentifizierung
Sie fügen die IP Ihres Servers in die Whitelist des Anbieters ein, eine Authentifizierung ist nicht erforderlich:
proxy = 'http://proxy.example.com:8080' # ohne Benutzername/Passwort
request.meta['proxy'] = proxy
Vorteile: schneller um 50-100ms, sicherer (keine Anmeldedaten im Code).
Nachteile: funktioniert nur von bestimmten IPs, die Whitelist muss bei einem Serverwechsel aktualisiert werden.
Empfehlung für die Produktion:
Verwenden Sie die IP-Whitelist für das Scraping von dedizierten Servern (AWS, Google Cloud, Hetzner). Für Entwicklung und Tests von einem lokalen Rechner — Benutzer:Pass-Authentifizierung.
Fehlerbehandlung und automatische IP-Wechsel
Selbst mit hochwertigen Proxys werden Fehler auftreten: Zeitüberschreitungen, Verbindungsabbrüche, Sperrungen. Eine korrekte Fehlerbehandlung ist entscheidend für den stabilen Betrieb des Scrapers.
HTTP-Statuscodes behandeln
class ProxyMiddleware:
def process_response(self, request, response, spider):
# Codes, bei denen der Proxy gewechselt und erneut versucht werden muss
ban_codes = [403, 407, 429, 503]
if response.status in ban_codes:
proxy = request.meta.get('proxy')
spider.logger.warning(
f'Erhalten {response.status} von {proxy}, erneut versuchen...'
)
# Markieren für einen Retry mit neuem Proxy
request.meta['dont_retry'] = False
request.meta['proxy'] = self.get_new_proxy()
return request
return response
Netzwerkausnahmen behandeln
from twisted.internet.error import TimeoutError, ConnectionRefusedError
from scrapy.exceptions import IgnoreRequest
class ProxyMiddleware:
def process_exception(self, request, exception, spider):
# Verbindungsfehler zu Proxys
proxy_errors = (
TimeoutError,
ConnectionRefusedError,
ConnectionLost,
)
if isinstance(exception, proxy_errors):
proxy = request.meta.get('proxy')
spider.logger.error(
f'Proxy {proxy} Verbindungsfehler: {exception}'
)
# Proxy wechseln und erneut versuchen
request.meta['proxy'] = self.get_new_proxy()
return request
# Für andere Fehler verwenden wir die Standardbehandlung
return None
Blockierungen durch Inhalte erkennen
Einige Websites geben HTTP 200 zurück, zeigen jedoch ein Captcha oder eine Sperrseite an:
class ProxyMiddleware:
def process_response(self, request, response, spider):
# Anzeichen einer Sperrung im Inhalt
ban_indicators = [
'captcha',
'access denied',
'blocked',
'unusual traffic',
'robot check',
]
body_text = response.text.lower()
if any(indicator in body_text for indicator in ban_indicators):
spider.logger.warning(
f'Sperrseite erkannt von {request.meta.get("proxy")}'
)
# Proxy wechseln und erneut versuchen
request.meta['proxy'] = self.get_new_proxy()
return request
return response
Welchen Proxy-Typ für Scrapy wählen
Die Wahl des Proxy-Typs hängt von der Zielwebsite, dem Budget und der erforderlichen Scraping-Geschwindigkeit ab. Hier ist ein Vergleich der Hauptoptionen:
| Proxy-Typ | Geschwindigkeit | Kosten | Wann verwenden |
|---|---|---|---|
| Rechenzentrums-Proxys | Hoch (50-200ms) | Niedrig ($1-3/IP) | Einfache Websites ohne Schutz, APIs, interne Tools |
| Residential Proxys | Mittel (300-800ms) | Mittel ($5-15/GB) | E-Commerce, soziale Netzwerke, Websites mit Cloudflare, Geotargeting |
| Mobile Proxys | Niedrig (500-1500ms) | Hoch ($50-150/IP) | Mobile Anwendungen, Instagram, TikTok, maximaler Schutz |
Empfehlungen zur Auswahl
Für das Scraping von Marktplätzen (Amazon, Wildberries, Ozon, AliExpress) — nur residential Proxys. Diese Websites sperren Rechenzentren aggressiv. Rotation und Geotargeting sind erforderlich (z. B. russische IPs für Wildberries).
Für das Scraping von Nachrichten-Websites, Blogs, Foren — eignen sich Rechenzentrums-Proxys. Der Schutz ist minimal, Geschwindigkeit und niedrige Kosten für den Datenverkehr sind wichtig.
Für das Scraping von Websites mit Cloudflare — sind residential Proxys unerlässlich. Rechenzentren werden von Cloudflare fast sofort erkannt. Fügen Sie Scrapy die Bibliothek cloudscraper hinzu, um JS-Herausforderungen zu umgehen.
Für das Scraping von Google-Suchen, SEO-Tools — verwenden Sie residential Proxys mit Geotargeting. Google zeigt unterschiedliche Ergebnisse für verschiedene Länder und Städte an.
Tipp: Beginnen Sie mit einem Pool von 10 residential Proxys für Tests. Wenn Sie Sperrungen erhalten — erhöhen Sie den Pool auf 50-100 IPs. Für schnelles Scraping (1000+ Anfragen/Minute) verwenden Sie einen rotating endpoint mit einem Pool von 10.000+ IPs.
Fortgeschrittene Techniken: Sessions und Sticky IP
Beim Scraping bestimmter Websites muss eine IP während der gesamten Sitzung beibehalten werden (Authentifizierung, Warenkorb, mehrstufige Formulare). So implementieren Sie Sticky Sessions in Scrapy.
Sticky IP für eine Domain
from urllib.parse import urlparse
class StickyProxyMiddleware:
def __init__(self, proxy_list):
self.proxy_list = proxy_list
# Dictionary: Domain -> Proxy
self.domain_proxy_map = {}
@classmethod
def from_crawler(cls, crawler):
proxy_list = crawler.settings.getlist('PROXY_LIST')
return cls(proxy_list)
def process_request(self, request, spider):
# Extrahieren Sie die Domain aus der URL
domain = urlparse(request.url).netloc
# Wenn für diese Domain bereits ein Proxy vorhanden ist — verwenden Sie ihn
if domain in self.domain_proxy_map:
proxy = self.domain_proxy_map[domain]
else:
# Andernfalls wählen Sie einen neuen und merken Sie sich
proxy = random.choice(self.proxy_list)
self.domain_proxy_map[domain] = proxy
spider.logger.info(f'{proxy} für {domain} zugewiesen')
request.meta['proxy'] = proxy
Sticky IP mit Sitzungs-Timeout
Eine fortgeschrittenere Variante: Der Proxy wird für eine bestimmte Zeit (z. B. 10 Minuten) an die Domain gebunden und wechselt dann:
import time
from urllib.parse import urlparse
class SessionProxyMiddleware:
def __init__(self, proxy_list, session_timeout=600):
self.proxy_list = proxy_list
self.session_timeout = session_timeout # 10 Minuten
# Dictionary: Domain -> (Proxy, Erstellungszeit)
self.sessions = {}
@classmethod
def from_crawler(cls, crawler):
proxy_list = crawler.settings.getlist('PROXY_LIST')
timeout = crawler.settings.getint('PROXY_SESSION_TIMEOUT', 600)
return cls(proxy_list, timeout)
def get_proxy_for_domain(self, domain):
current_time = time.time()
# Überprüfen Sie, ob eine aktive Sitzung vorhanden ist
if domain in self.sessions:
proxy, created_at = self.sessions[domain]
# Wenn die Sitzung nicht abgelaufen ist — verwenden Sie denselben Proxy
if current_time - created_at < self.session_timeout:
return proxy
# Erstellen Sie eine neue Sitzung mit einem neuen Proxy
new_proxy = random.choice(self.proxy_list)
self.sessions[domain] = (new_proxy, current_time)
return new_proxy
def process_request(self, request, spider):
domain = urlparse(request.url).netloc
proxy = self.get_proxy_for_domain(domain)
request.meta['proxy'] = proxy
Integration mit Cookie Middleware
Für vollständige Sessions müssen Proxy und Cookies synchronisiert werden. Scrapy speichert Cookies separat für jede Domain, aber beim Wechsel des Proxys müssen die Cookies gelöscht werden:
# settings.py
# Aktivieren Sie die Cookie-Middleware
COOKIES_ENABLED = True
COOKIES_DEBUG = False
# Middleware zur Synchronisierung von Proxy und Cookies
class ProxyCookieMiddleware:
def process_request(self, request, spider):
# Holen Sie sich den aktuellen Proxy
current_proxy = request.meta.get('proxy')
# Wenn der Proxy gewechselt wurde — löschen Sie die Cookies
previous_proxy = request.meta.get('previous_proxy')
if previous_proxy and previous_proxy != current_proxy:
# Löschen Sie die Cookies für diese Domain
jar = spider.crawler.engine.downloader.middleware.middlewares[0].jars
domain = urlparse(request.url).netloc
if domain in jar:
jar[domain].clear()
spider.logger.info(f'Cookies für {domain} gelöscht')
request.meta['previous_proxy'] = current_proxy
Fazit
Die richtige Proxy-Einrichtung in Scrapy ist die Grundlage für stabiles Scraping ohne Sperrungen. Wir haben alle wichtigen Aspekte behandelt: von der grundlegenden Integration bis hin zu fortgeschrittenen Techniken der Rotation und Sitzungsverwaltung.
Die wichtigsten Erkenntnisse:
- Für die Produktion verwenden Sie eine benutzerdefinierte Middleware mit intelligenter Rotation und Blacklist für problematische IPs
- Behandeln Sie alle Arten von Fehlern: HTTP-Statuscodes, Netzwerkausnahmen, Blockierungen durch Inhalte
- Wählen Sie den Proxy-Typ je nach Aufgabe: Rechenzentren für einfache Websites, residential Proxys für geschützte
- Für Websites mit Authentifizierung verwenden Sie Sticky Sessions mit Bindung des Proxys an die Domain
- Beginnen Sie mit einem Pool von 10-50 Proxys, skalieren Sie bei wachsendem Bedarf
Wenn Sie planen, geschützte Websites (Marktplätze, soziale Netzwerke, Websites mit Cloudflare) zu scrapen, empfehle ich die Verwendung von residential Proxys — sie bieten maximale Anonymität und minimales Risiko von Sperrungen. Für schnelles Scraping wählen Sie Anbieter mit rotating endpoint und einem Pool von 10.000 IP-Adressen.
Alle Codebeispiele aus diesem Artikel wurden auf Scrapy 2.x getestet und sind bereit für die Verwendung in der Produktion. Passen Sie sie an Ihre Anforderungen an und skalieren Sie mit dem Wachstum Ihres Projekts.