Scrapy Proxy Middleware: Roterende Proxies Zonder Blokkades
Scrapy ondersteunt proxies sinds versie 0.8, maar de documentatie vertelt je nooit hoe je op grote schaal kunt roteren, opnieuw proberen en blokkades kunt overleven. Hier is de anatomie van de middleware, de code en de instellingen die bepalen of je crawl voltooid wordt.
Scrapy heeft sinds versie 0.8 een proxy middleware, dus 'ondersteunt Scrapy proxies' werd vijftien jaar geleden al beantwoord. Wat de documentatie nooit helemaal samenbrengt, is het productieplaatje: hoe de ingebouwde Scrapy proxy middleware daadwerkelijk inloggegevens verwerkt, wanneer je proxies per verzoek versus globaal moet instellen, en hoe je roteert en herstelt wanneer een doelwit begint met het blokkeren van exits halverwege de crawl. Deze gids doorloopt de hele keten — de ingebouwde HttpProxyMiddleware, per-verzoek meta, een aangepaste rotatiemiddleware met blokkadebeheer en de instellingen die bepalen of een crawl van 100k-pagina's voltooid wordt of om 3 uur 's nachts stopt.
Hoe de ingebouwde Scrapy proxy middleware werkt
scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware is standaard ingeschakeld met prioriteit 750 in DOWNLOADER_MIDDLEWARES_BASE. Door de broncode te lezen (ongeveer 100 regels in Scrapy 2.17) krijg je alle informatie die je nodig hebt voor het debuggen:
- Bij het opstarten roept het
urllib.request.getproxies()aan, zodathttp_proxy/https_proxyomgevingsvariabelen een spider configureren zonder code.no_proxywordt alleen gehonoreerd voor http- en https-schema's. - Per verzoek wint
request.meta['proxy']altijd van de omgeving. Stel het in opNoneom een directe verbinding voor dat verzoek af te dwingen. - Inloggegevens die in de proxy-URL zijn ingebed (
http://user:pass@host:port) worden gestript, Base64-gecodeerd en verzonden als eenProxy-Authorization: Basicheader. Codering is standaard latin-1 en kan worden geconfigureerd viaHTTPPROXY_AUTH_ENCODING. - De middleware decodeert inloggegevens voordat ze worden gecodeerd, dus URL-codeer speciale tekens zoals
@in wachtwoorden — ze worden correct doorgegeven. - Als een retry de proxy verandert, laat de middleware de verouderde auth header vallen in plaats van deze naar de nieuwe proxy te lekken — een credential-hygiënefix in moderne Scrapy, en een goede reden om geen oude versies te gebruiken.
Praktisch gevolg: je hebt bijna nooit een externe proxy-pakket nodig. Alles wat een geldige proxy-URL in request.meta['proxy'] plaatst vóór prioriteit 750 krijgt gratis authenticatie- en headerverwerking.
Per-verzoek proxies met meta
De lichtste integratie is het direct instellen van meta in de spider — nuttig wanneer slechts enkele verzoeken een proxy nodig hebben, of verschillende doelwitten verschillende exitlanden nodig hebben:
import scrapy
class PricesSpider(scrapy.Spider):
name = "prices"
def start_requests(self):
yield scrapy.Request(
"https://example.com/product/1",
meta={"proxy": "http://USER:PASS@gate.quantumproxies.io:PORT"},
)
Per-verzoek meta schittert wanneer de proxykeuze zelf datagestuurd is: routeer Duitse productpagina's via een op Duitsland gerichte gebruikersnaam, stuur afbeeldingsdownloads via goedkope datacenter-exits terwijl HTML residentieel gaat, of escaleer een koppige URL naar een hardnekkigere sessie bij de tweede poging. Omdat meta retries en redirects overleeft, volgt de beslissing die je neemt bij het bouwen van het verzoek het door de hele downloadcyclus. Het instellen van meta op duizenden verzoeken met de hand is echter niet schaalbaar — daarvoor zijn downloader middlewares.
De eenvoudigste productiesetup: een roterende gateway
Met roterende proxies achter een enkele gateway endpoint, vindt rotatie server-side plaats: elk verzoek via dezelfde URL verlaat een ander IP in een 90M+ residentieel pool dat 200+ landen beslaat. Je middleware krimpt tot drie regels, en er is geen lijst om te controleren, te snoeien of te vernieuwen:
# middlewares.py
class RotatingGatewayMiddleware:
PROXY = "http://USER:PASS@gate.quantumproxies.io:PORT"
def process_request(self, request, spider):
request.meta["proxy"] = self.PROXY
# settings.py
DOWNLOADER_MIDDLEWARES = {
"myproject.middlewares.RotatingGatewayMiddleware": 350,
}
Prioriteit 350 is belangrijk: je middleware moet draaien vóór de ingebouwde op 750 zodat HttpProxyMiddleware de inloggegevens nog steeds kan omzetten in de auth header. Deze combinatie — gateway rotatie plus Scrapy's standaard retry mechanisme — biedt de hoogste betrouwbaarheid per regel code, omdat elke retry automatisch via een nieuw exit-IP gaat.

Een aangepaste roterende proxy middleware met blokkadebeheer
Als je je eigen proxy-lijst beheert — statische ISP-adressen of een gemengd pool — is het klassieke patroon (gepopulariseerd door het scrapy-proxies pakket, dat RETRY_TIMES = 10 aanbeveelt omdat gratis-lijst proxies zo vaak falen): kies willekeurig per verzoek, verwijder bij blokkadesignalen, zet het verzoek opnieuw in de wachtrij:
import random
class ProxyListMiddleware:
BAN_CODES = {403, 429}
def __init__(self, proxies):
self.proxies = list(proxies)
@classmethod
def from_crawler(cls, crawler):
return cls(crawler.settings.getlist("PROXY_LIST"))
def process_request(self, request, spider):
if self.proxies and "proxy" not in request.meta:
request.meta["proxy"] = random.choice(self.proxies)
def process_response(self, request, response, spider):
if response.status in self.BAN_CODES:
bad = request.meta.get("proxy")
if bad in self.proxies and len(self.proxies) > 1:
self.proxies.remove(bad)
spider.logger.warning("Evicted %s (%d left)", bad, len(self.proxies))
return request.replace(dont_filter=True) # re-queue on a new proxy
return response
Let op alles wat deze middleware moet doen dat een gateway gratis doet: poolgezondheid bijhouden, verbrande IP's verwijderen, verzoeken opnieuw in de wachtrij plaatsen. Het krimpt ook onder druk — een pool van 20 statische IP's kan binnen enkele minuten verdampen op een agressief doelwit. Het bredere anti-blokkade handboek (tempo, headers, sessiediscipline) staat in onze IP-blokkade vermijdingschecklist.
Scrapy-instellingen die het resultaat bepalen
Middleware plaatst de proxy; instellingen bepalen of de crawl zich gedraagt. Deze zijn het belangrijkst:
# settings.py — a sane baseline for proxied crawls
RETRY_TIMES = 5
RETRY_HTTP_CODES = [429, 403, 500, 502, 503, 504, 408]
CONCURRENT_REQUESTS = 32
CONCURRENT_REQUESTS_PER_DOMAIN = 8
DOWNLOAD_TIMEOUT = 30
AUTOTHROTTLE_ENABLED = True
AUTOTHROTTLE_TARGET_CONCURRENCY = 4.0
- RETRY_HTTP_CODES — voeg 429 en 403 toe zodat zachte blokkades een retry activeren (en, met rotatie, een nieuw IP) in plaats van een mislukt item.
- RETRY_TIMES — 3-5 is juist voor een kwaliteits-pool; 10 nodig hebben is een teken dat de pool zelf ongezond is.
- CONCURRENT_REQUESTS_PER_DOMAIN — de echte beleefdheidsknop. Rotatie verspreidt de belasting over IP's, maar het doelwit ziet nog steeds het totale volume.
- DOWNLOAD_TIMEOUT — de standaard 180 seconden laat één langzame exit een gelijktijdigheidsslot drie minuten bezetten. 30 is voldoende.
- AUTOTHROTTLE — past het tempo aan op waargenomen latentie; werkt goed samen met rotatie op doelen met snelheidsbeperking. Nog steeds verdrinken in 429's? Onze snelheidslimietgids behandelt gelijktijdigheidsbudgetten uitgebreid.

Blokkade detectie voorbij statuscodes
Geavanceerde doelen sturen geen eerlijke 403's. Ze serveren 200's met een CAPTCHA-pagina, een lege productgrid of een gestript sjabloon. Robuuste spiders verifiëren inhoud, niet alleen status — controleer op een element dat alleen op de echte pagina bestaat, en zet opnieuw in de wachtrij wanneer het ontbreekt. Als pagina's structureel leeg terugkomen, zelfs via goede proxies, wordt de inhoud waarschijnlijk client-side gerenderd; zie waarom scrapers lege pagina's krijgen. En wanneer één domein plain HTTP verslaat, ongeacht het IP, geef dat domein aan een Scraper API die JavaScript rendert en schone HTML of markdown retourneert — Scrapy verwerkt het als elke andere respons, en je behoudt je pijplijn.
def parse(self, response):
if not response.css("div.product-grid"):
# 200 OK but the real content is missing: soft ban or JS wall
yield response.request.replace(dont_filter=True)
return
for product in response.css("div.product-grid article"):
yield {"name": product.css("h2::text").get()}
Veelgestelde vragen
Ondersteunt Scrapy proxies standaard?
Ja. HttpProxyMiddleware wordt standaard ingeschakeld met prioriteit 750: het leest http_proxy / https_proxy omgevingsvariabelen en eerbiedigt request.meta['proxy'] per verzoek, inclusief user:pass@ inloggegevens, die het omzet in een Proxy-Authorization header. Je schrijft alleen code om te beslissen welke proxy elk verzoek krijgt.
Hoe stel ik een proxy in voor een enkel verzoek in Scrapy?
Geef het door in de meta van het verzoek: scrapy.Request(url, meta={'proxy': 'http://user:pass@host:port'}). Meta overschrijft altijd omgevingsniveau proxies, en het instellen van de waarde op None dwingt dat ene verzoek direct te gaan — handig voor het mixen van geproxied en ongeproxied verkeer in één spider.
Hoe roteer ik proxies in Scrapy?
Schrijf een downloader middleware die een proxy per verzoek kiest uit een lijst en geblokkeerde verwijdert, of richt elk verzoek op een roterende gateway endpoint die server-side een nieuw exit-IP toewijst. De gatewaybenadering vereist een middleware van drie regels, geen gezondheidscontroles, en verandert elke Scrapy retry in een rotatie gratis.
Waarom retourneert mijn Scrapy proxy 407?
De proxy wees authenticatie af. Controleer of inloggegevens in de proxy-URL in meta staan, of speciale tekens URL-gecodeerd zijn, en of je server-IP op de witte lijst staat als je plan IP-authenticatie gebruikt. Als inloggegevens niet-ASCII-tekens bevatten, stel HTTPPROXY_AUTH_ENCODING in om overeen te komen met je provider.
Het patroon om te onthouden: stel meta['proxy'] vroeg in, laat de ingebouwde middleware op 750 authenticatie doen, voeg 429 en 403 toe aan je retry-codes, en geef de voorkeur aan server-side rotatie boven lijstverzorging. Als delen van je crawl JavaScript nodig hebben, overweeg de kosten van headless browsers versus HTTP-verzoeken voordat je er een gebruikt — de meeste Scrapy-projecten hebben betere IP's nodig, niet een browser.