Scrapy ist das Framework, nach dem du greifst, wenn ein Scrape aus einem Skript herauswächst: es handhabt Scheduling, Nebenläufigkeit, Retries und Pipelines von Haus aus. Einen Residential Proxy hinzuzufügen ist unkompliziert, aber Scrapy macht es anders als ein schlichter HTTP-Client. Der Proxy ist eine Pro-Anfrage-Einstellung, die von einer Downloader-Middleware gehandhabt wird, und diese Architektur hat eine spezifische Falle rund um die Authentifizierung, die die Rotation still bricht. Verstehe das Middleware-Modell, und alles fügt sich.
Das ist der Scrapy-Beitrag neben Residential Proxies mit Python, das requests und httpx abdeckt. Scrapys Downloader-Middleware-Pipeline ist ein anderes Biest, also bekommt sie hier ihre eigene Behandlung.
Alles unten nutzt Shifters Residential Gateway: ein Endpunkt, p.shifter.io:443, mit dem gesamten Targeting im Benutzernamen kodiert. Tausche Host und Zugangsdaten für einen anderen Anbieter aus; die Form bleibt gleich.
Das Gateway-Modell in einem Absatz
Der Proxy-Benutzername trägt deine Authentifizierung und dein Targeting. Du wechselst nicht den Endpunkt, um Land oder Session zu ändern, du änderst die Benutzernamen-Zeichenkette:
customer-USERNAME-country-us-sid-abc123-ttl-600country-us zielt auf die USA, sid fixiert eine Sticky Session, ttl hält diese IP für N Sekunden. Lässt du sid/ttl weg, rotiert jede neue Verbindung. Das Passwort bleibt konstant. In Scrapy wird dieser Benutzername zum Proxy-Authorization-Header, und Identität zu rotieren heißt, ihn pro Anfrage zu ändern.
Wie Scrapy Proxies handhabt
Scrapy leitet den Proxy jeder Anfrage durch die eingebaute HttpProxyMiddleware, die request.meta['proxy'] liest. Das naive Setup ist, die Zugangsdaten inline in diese URL zu packen:
# Der verlockende Einzeiler. Er funktioniert, bis du rotierst.request.meta['proxy'] = 'http://customer-USER-country-us:PASS@p.shifter.io:443'Das funktioniert für eine einzelne feste Identität. Es bricht in dem Moment, in dem du zu rotieren versuchst, und der Grund ist die Falle, die es zu kennen lohnt.
Die Falle: Proxy-Authorization wird gecacht
Wenn HttpProxyMiddleware Zugangsdaten in der Proxy-URL sieht, base64-kodiert es sie in einen Proxy-Authorization-Header und, entscheidend, cacht diesen Header auf der Anfrage. Wird eine Anfrage später erneut versucht oder umgeleitet und ihr meta['proxy'] ändert sich zu einer anderen Identität, berechnet die Middleware den Header nicht immer neu, also geht die Anfrage mit einem veralteten Proxy-Authorization für den vorherigen Benutzernamen raus. Auf einem Gateway, wo der Benutzername deine Geo und Session trägt, heißt das, dass deine Rotation still nicht rotiert: du änderst den Benutzernamen in meta['proxy'], aber die Anfrage authentifiziert sich immer noch als die alte.
Die Lösung ist, überhaupt keine Zugangsdaten in die Proxy-URL zu packen. Setze den Proxy-Host ohne Userinfo, und setze den Proxy-Authorization-Header selbst, explizit, auf jeder Anfrage. Genau dafür ist eine Custom Middleware da.
Eine Custom Rotations-Middleware
Packe den Host in meta['proxy'] ohne Zugangsdaten, und berechne den Auth-Header pro Anfrage aus der Identität, die du willst. Da das Targeting im Benutzernamen lebt, ist ein Land und eine Session zu wählen nur, den richtigen Benutzernamen zu bauen.
import osfrom w3lib.http import basic_auth_header
class ShifterProxyMiddleware: def __init__(self): self.user = os.environ['SHIFTER_USER'] self.password = os.environ['SHIFTER_PASS'] self.endpoint = 'http://p.shifter.io:443' # nur Host, keine Zugangsdaten
def process_request(self, request, spider): country = request.meta.get('country', 'us') sid = request.meta.get('sid') # für eine Sticky Session setzen, zum Rotieren weglassen username = f"{self.user}-country-{country}" + (f"-sid-{sid}-ttl-600" if sid else "") request.meta['proxy'] = self.endpoint request.headers['Proxy-Authorization'] = basic_auth_header(username, self.password)Aktiviere sie, und lass sie vor der eingebauten Proxy-Middleware laufen, damit der von dir gesetzte Header der ist, der ausgeliefert wird:
DOWNLOADER_MIDDLEWARES = { 'myproject.middlewares.ShifterProxyMiddleware': 350, # vor HttpProxyMiddleware (750)}Jetzt trägt jede Anfrage ihr eigenes frisch berechnetes Proxy-Authorization, also ändert das Ändern von country oder sid im Meta einer Anfrage tatsächlich die Identität. Setze sid auf die Anfragen, die zu einer logischen Arbeitseinheit gehören, damit sie eine IP teilen, und lass es weg, um pro Verbindung zu rotieren (Sticky vs. rotierend behandelt die Unterscheidung). Arbeit auf diese Weise Identitäten zuzuordnen ist das Lastausgleich-Muster in Scrapy-Form.
Rotiere bei Retry, nicht nur nach Zeitplan
Scrapys RetryMiddleware wiederholt bereits Timeouts und 5xx, aber standardmäßig wiederholt es mit derselben Identität, was sinnlos ist, wenn der Grund des Fehlers war, dass diese Identität geblockt wurde. Der wertvolle Zug ist, die Identität gezielt zu rotieren, wenn eine Anfrage fehlschlägt oder herausgefordert zurückkommt. Erkenne in deiner Middleware einen Soft-Block oder ein 403/429 und plane die Anfrage mit einer neuen Identität neu ein:
def process_response(self, request, response, spider): if response.status in (403, 429) or looks_blocked(response): new = request.copy() new.meta.pop('sid', None) # verbrannte Session verwerfen -> frische IP new.dont_filter = True return new # über eine neue Identität erneut versuchen return responseDen Soft-Block zu erkennen ist eine eigene Disziplin, ein 200 kann immer noch eine Block-Seite sein, also kopple das mit den Prüfungen aus Blockierte oder gefälschte Inhalte erkennen. Eine herausgeforderte Antwort als Signal zum Rotieren zu behandeln, statt sie zu akzeptieren, ist es, was einen langen Crawl am Leben hält.
Nutze Scrapys Höflichkeits-Regler
Scrapy gibt dir die Rate-Limiting-Steuerungen, die ein handgebauter Scraper erst bauen muss, und mit einer Proxy-Flotte zählen sie mehr, nicht weniger. Begrenze die Nebenläufigkeit pro Domain, damit ein Ziel nicht malträtiert wird, füge eine Verzögerung hinzu, und schalte AutoThrottle ein, um dich an die Antworten der Seite anzupassen:
CONCURRENT_REQUESTS = 32CONCURRENT_REQUESTS_PER_DOMAIN = 8 # Pro-Ziel-Deckel, der der zähltDOWNLOAD_DELAY = 0.5AUTOTHROTTLE_ENABLED = TrueRETRY_ENABLED = TrueRETRY_TIMES = 3Nebenläufigkeit pro Domain ist der Regler, der dich davon abhält, einen Proxy-Pool in einen verteilten Hammer zu verwandeln. Mehr Parallelität jenseits der Toleranz eines Ziels kauft Blockaden, keinen Durchsatz (wie man Blockaden vermeidet und verantwortungsvoll scrapen gelten beide), und AutoThrottle, das bei langsamen Antworten zurückfährt, ist genau die Zurückhaltung, die ein gesunder langlaufender Crawl braucht.
Prüfe, dass du wirklich über den Proxy läufst
Richte einen Spider auf einen IP-Echo-Endpunkt und prüfe die Exit-IP, bevor du einem Lauf vertraust:
def start_requests(self): yield scrapy.Request('http://ip-api.com/json', meta={'country': 'us'}, callback=self.parse) # erwarte eine US-Residential-IPDeine eigene IP bedeutet, dass die Middleware nicht angewandt wird, oder nach HttpProxyMiddleware einsortiert ist. Eine Wand aus Timeouts bedeutet, dass der Auth-Header falsch ist oder fehlt. Beides wird im Leitfaden zur Timeout-Diagnose behandelt.
FAQ
Warum rotiert meine Proxy-Rotation in Scrapy nicht wirklich?
Fast sicher die Proxy-Authorization-Caching-Falle: du hast Zugangsdaten in die Proxy-URL gepackt, und HttpProxyMiddleware hat den Auth-Header gecacht, also sendet die Anfrage bei einem Retry, wenn du meta['proxy'] änderst, immer noch die alten Zugangsdaten. Setze den Host ohne Zugangsdaten und berechne den Proxy-Authorization-Header selbst in einer Middleware, pro Anfrage.
Wohin gehen die Targeting-Flags?
In den Benutzernamen, der zum Proxy-Authorization wird. Eine Custom Middleware baut customer-USER-country-<cc>-sid-<id>-ttl-<sek> aus dem Pro-Anfrage-meta, also ist Geo und Session zu wählen nur, country und sid auf der Anfrage zu setzen.
Wie gebe ich einer bestimmten Anfrage eine Sticky Session?
Setze eine stabile sid im meta dieser Anfrage und verwende sie über die Anfragen wieder, die zusammengehören; lass sid weg, um bei jeder Verbindung zu rotieren. Die Middleware verwandelt das in den richtigen Benutzernamen.
Sollte ich bei jeder Anfrage oder bei Retry rotieren? Beides hat seinen Platz. Rotiere pro logischer Arbeitseinheit für normalen Verkehr, und erzwinge zusätzlich eine frische Identität, wenn eine Anfrage geblockt oder rate-limitiert zurückkommt, damit eine verbrannte IP nicht als sie selbst erneut versucht wird.
Brauche ich mit rotierenden Proxies noch DOWNLOAD_DELAY und AutoThrottle? Ja. Rotation verteilt Last über IPs, aber Nebenläufigkeit pro Domain, Verzögerung und AutoThrottle halten dich davon ab, ein einzelnes Ziel zu überwältigen, egal wie viele IPs du hast. Höflichkeit und Rotation lösen verschiedene Probleme.
Das Fazit
Scrapy plus Residential Proxies ist mächtig, sobald du um seine eine Falle herumroutest: packe keine Zugangsdaten in die Proxy-URL, denn das gecachte Proxy-Authorization besiegt die Rotation still. Schreibe stattdessen eine kleine Downloader-Middleware, die den Host in meta['proxy'] setzt und den Proxy-Authorization-Header pro Anfrage aus der Identität berechnet, die du willst, rotiere diese Identität pro logischer Arbeitseinheit und erneut bei jeder geblockten oder rate-limitierten Antwort, und stütze dich auf Scrapys Nebenläufigkeit pro Domain und AutoThrottle, um höflich zu bleiben.
Mach das, und Scrapys Scheduler, Retries und Pipelines arbeiten mit deiner Proxy-Schicht statt gegen sie. Richte den Crawl auf das Residential Gateway, und denk daran, dass die Pool-Qualität entscheidet, wie oft du überhaupt erneut versuchst (IP-Reputation). Die Preisseite hat die Pro-GB-Tarife, um es gegen deine eigenen Ziele zu testen.