Middleware Scrapy Proxy: Rotazione dei Proxy Senza Ban
Scrapy ha introdotto il supporto per i proxy dalla versione 0.8, ma la documentazione non spiega mai come ruotare, riprovare e sopravvivere ai ban su larga scala. Ecco l'anatomia del middleware, il codice e le impostazioni che decidono se il tuo crawl si completa.
Scrapy ha introdotto un middleware per i proxy dalla versione 0.8, quindi 'Scrapy supporta i proxy' è stato risolto quindici anni fa. Ciò che la documentazione non riesce mai a comporre è il quadro di produzione: come il middleware proxy integrato di Scrapy elabora effettivamente le credenziali, quando impostare i proxy per richiesta rispetto a globalmente, e come ruotare e recuperare quando un obiettivo inizia a bannare le uscite a metà crawl. Questa guida percorre l'intera catena — il HttpProxyMiddleware integrato, il meta per richiesta, un middleware di rotazione personalizzato con gestione dei ban e le impostazioni che decidono se un crawl di 100k pagine si completa o muore alle 3 del mattino.
Come funziona il middleware proxy integrato di Scrapy
scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware è abilitato di default con priorità 750 in DOWNLOADER_MIDDLEWARES_BASE. Leggendo il suo codice sorgente (circa 100 righe in Scrapy 2.17) scopri tutto ciò che serve per il debug:
- All'avvio chiama
urllib.request.getproxies(), quindi le variabili d'ambientehttp_proxy/https_proxyconfigurano uno spider senza codice.no_proxyè rispettato solo per gli schemi http e https. - Per richiesta,
request.meta['proxy']prevale sempre sull'ambiente. Impostalo suNoneper forzare una connessione diretta per quella richiesta. - Le credenziali incorporate nell'URL del proxy (
http://user:pass@host:port) vengono rimosse, codificate in Base64 e inviate come intestazioneProxy-Authorization: Basic. La codifica predefinita è latin-1 ed è configurabile tramiteHTTPPROXY_AUTH_ENCODING. - Il middleware decodifica le credenziali prima di codificarle, quindi codifica URL i caratteri speciali come
@nelle password — passano correttamente. - Se un retry cambia il proxy, il middleware elimina l'intestazione di autenticazione obsoleta invece di perderla nel nuovo proxy — una correzione di igiene delle credenziali in Scrapy moderno, e una buona ragione per non fissare versioni antiche.
Conseguenza pratica: quasi mai hai bisogno di un pacchetto proxy di terze parti. Qualsiasi cosa che inserisca un URL proxy valido in request.meta['proxy'] prima della priorità 750 ottiene autenticazione e gestione delle intestazioni gratuitamente.
Proxy per richiesta con meta
L'integrazione più leggera è impostare meta direttamente nello spider — utile quando solo alcune richieste necessitano di un proxy, o diversi obiettivi necessitano di diversi paesi di uscita:
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"},
)
Il meta per richiesta brilla quando la scelta del proxy è guidata dai dati: instrada le pagine di prodotti tedeschi tramite un nome utente mirato alla Germania, invia i download di immagini tramite uscite datacenter economiche mentre l'HTML va residenziale, o scala un URL ostinato a una sessione più stabile al secondo tentativo. Poiché il meta sopravvive a retry e redirect, la decisione presa durante la costruzione della richiesta la segue per tutto il ciclo di download. Impostare il meta su migliaia di richieste a mano non scala, però — è per questo che esistono i downloader middlewares.
La configurazione di produzione più semplice: un gateway rotante
Con proxy rotanti dietro un singolo endpoint gateway, la rotazione avviene lato server: ogni richiesta attraverso lo stesso URL esce da un IP diverso in un pool residenziale di oltre 90M che copre oltre 200 paesi. Il tuo middleware si riduce a tre righe, e non c'è lista da controllare, potare o aggiornare:
# 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,
}
La priorità 350 è importante: il tuo middleware deve essere eseguito prima di quello integrato a 750 affinché HttpProxyMiddleware possa ancora convertire le credenziali nell'intestazione di autenticazione. Questo abbinamento — rotazione del gateway più il meccanismo di retry standard di Scrapy — offre la massima affidabilità per linea di codice, perché ogni retry viaggia automaticamente attraverso un nuovo IP di uscita.

Un middleware proxy rotante personalizzato con gestione dei ban
Se gestisci la tua lista di proxy — indirizzi ISP statici o un pool misto — il modello classico (popolarizzato dal pacchetto scrapy-proxies, che raccomandava RETRY_TIMES = 10 perché i proxy delle liste gratuite falliscono così spesso) è: scegli casualmente per richiesta, espelli sui segnali di ban, rimetti in coda la richiesta:
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
Nota tutto ciò che questo middleware deve fare che un gateway fa gratuitamente: traccia la salute del pool, espelli gli IP bruciati, rimetti in coda le richieste. Si riduce anche sotto pressione — un pool di 20 IP statici può evaporare in minuti su un obiettivo aggressivo. Il più ampio manuale anti-ban (ritmo, intestazioni, disciplina delle sessioni) è nel nostro checklist per evitare il ban IP.
Impostazioni di Scrapy che decidono l'esito
Il middleware posiziona il proxy; le impostazioni decidono se il crawl si comporta. Queste contano di più:
# 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 — aggiungi 429 e 403 in modo che i ban soft attivino un retry (e, con rotazione, un nuovo IP) invece di un elemento fallito.
- RETRY_TIMES — 3-5 è giusto per un pool di qualità; aver bisogno di 10 è un segno che il pool stesso è malsano.
- CONCURRENT_REQUESTS_PER_DOMAIN — la vera manopola di cortesia. La rotazione distribuisce il carico su IP, ma l'obiettivo vede comunque il volume totale.
- DOWNLOAD_TIMEOUT — i 180 secondi predefiniti consentono a un'uscita lenta di occupare uno slot di concorrenza per tre minuti. 30 sono sufficienti.
- AUTOTHROTTLE — adatta il ritmo alla latenza osservata; si abbina bene con la rotazione su obiettivi con limiti di velocità. Ancora sommerso dai 429? La nostra guida ai limiti di velocità copre i budget di concorrenza in dettaglio.

Rilevamento dei ban oltre i codici di stato
Gli obiettivi sofisticati non inviano 403 onesti. Servono 200 contenenti una pagina CAPTCHA, una griglia di prodotti vuota o un modello spogliato. Gli spider robusti verificano il contenuto, non solo lo stato — controlla un elemento che esiste solo sulla pagina reale e rimetti in coda quando manca. Se le pagine tornano strutturalmente vuote anche tramite buoni proxy, il contenuto è probabilmente reso lato client; vedi perché gli scraper ottengono pagine vuote. E quando un dominio sconfigge il semplice HTTP indipendentemente dall'IP, consegna quel dominio a un Scraper API che rende JavaScript e restituisce HTML pulito o markdown — Scrapy lo consuma come qualsiasi altra risposta, e mantieni la tua pipeline.
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()}
Domande frequenti
Scrapy supporta i proxy di default?
Sì. HttpProxyMiddleware è abilitato a priorità 750: legge le variabili d'ambiente http_proxy / https_proxy e rispetta request.meta['proxy'] per richiesta, incluse le credenziali user:pass@, che converte in un'intestazione Proxy-Authorization. Devi solo scrivere codice per decidere quale proxy riceve ogni richiesta.
Come imposto un proxy per una singola richiesta in Scrapy?
Passalo nel meta della richiesta: scrapy.Request(url, meta={'proxy': 'http://user:pass@host:port'}). Il meta prevale sempre sui proxy a livello di ambiente, e impostare il valore su None forza quella singola richiesta ad andare diretta — utile per mescolare traffico con e senza proxy in uno spider.
Come ruoto i proxy in Scrapy?
Scrivi un middleware downloader che seleziona un proxy per richiesta da una lista ed espelle quelli bannati, oppure punta ogni richiesta a un endpoint gateway rotante che assegna un nuovo IP di uscita lato server. L'approccio del gateway necessita di un middleware di tre righe, nessun controllo di salute, e trasforma ogni retry di Scrapy in una rotazione gratuita.
Perché il mio proxy Scrapy restituisce 407?
Il proxy ha rifiutato l'autenticazione. Controlla che le credenziali siano all'interno dell'URL del proxy nel meta, che i caratteri speciali siano codificati URL e che il tuo IP server sia nella whitelist se il tuo piano utilizza l'autenticazione IP. Se le credenziali contengono caratteri non ASCII, imposta HTTPPROXY_AUTH_ENCODING per corrispondere al tuo provider.
Il modello da ricordare: imposta meta['proxy'] presto, lascia che il middleware integrato a 750 gestisca l'autenticazione, aggiungi 429 e 403 ai tuoi codici di retry, e preferisci la rotazione lato server rispetto alla gestione delle liste. Se parti del tuo crawl necessitano di JavaScript, valuta il costo dei browser headless rispetto alle richieste HTTP prima di sceglierne uno — la maggior parte dei progetti Scrapy ha bisogno di IP migliori, non di un browser.