# 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'ambiente `http_proxy` / `https_proxy` configurano uno spider senza codice. `no_proxy` è rispettato solo per gli schemi http e https.
- Per richiesta, `request.meta['proxy']` prevale sempre sull'ambiente. Impostalo su `None` per 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 intestazione `Proxy-Authorization: Basic`. La codifica predefinita è latin-1 ed è configurabile tramite `HTTPPROXY_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:

```python
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@residentialpremium.quantumproxies.io:8080"},
        )
```

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](https://quantumproxies.io/rotating-proxies) 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:

```python
# middlewares.py
class RotatingGatewayMiddleware:
    PROXY = "http://USER:PASS@residentialpremium.quantumproxies.io:8080"

    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.

![Una richiesta Scrapy attraversa la catena dei downloader middleware: lo spider la genera, il tuo middleware a priorità 350 imposta meta'proxy' sul gateway, l'HttpProxyMiddleware integrato a 750 trasforma le credenziali in un header Proxy-Authorization e il downloader esce tramite il gateway rotante; i retry ricevono un nuovo IP.](https://res.cloudinary.com/dmqliab0t/image/upload/f_auto,q_auto,w_1200/v1785335142/blog/generated/qp-b9-002-diag1.png)
*Il tuo middleware deve solo impostare meta['proxy']; l'integrato a priorità 750 gestisce le credenziali, e i retry rientrano nella catena su un nuovo IP.*

## 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:

```python
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](https://quantumproxies.io/blog/avoid-ip-ban-web-scraping).

## Impostazioni di Scrapy che decidono l'esito

Il middleware posiziona il proxy; le impostazioni decidono se il crawl si comporta. Queste contano di più:

```python
# 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à](https://quantumproxies.io/blog/fix-429-too-many-requests-scraping) copre i budget di concorrenza in dettaglio.

![Due configurazioni di rotazione in Scrapy a confronto: una lista di proxy fai-da-te che si restringe man mano che gli IP bannati vengono rimossi e le richieste rimesse in coda, e un gateway rotante che è un unico endpoint con rotazione lato server. La scheda: una lista significa health check e rimozioni, 20 IP statici possono evaporare in pochi minuti, un gateway mette ogni retry su un IP nuovo.](https://res.cloudinary.com/dmqliab0t/image/upload/f_auto,q_auto,w_1200/v1785335142/blog/generated/qp-b9-002-diag2.png)
*Una lista fai-da-te significa controlli di salute, logica di espulsione e IP obsoleti. Un gateway è un endpoint che ruota lato server — i retry atterrano su IP freschi per impostazione predefinita.*

## 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](https://quantumproxies.io/blog/scraper-returns-empty-page-javascript). E quando un dominio sconfigge il semplice HTTP indipendentemente dall'IP, consegna quel dominio a un [Scraper API](https://quantumproxies.io/scraper-api) che rende JavaScript e restituisce HTML pulito o markdown — Scrapy lo consuma come qualsiasi altra risposta, e mantieni la tua pipeline.

```python
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](https://quantumproxies.io/blog/headless-browser-vs-http-requests-cost) prima di sceglierne uno — la maggior parte dei progetti Scrapy ha bisogno di IP migliori, non di un browser.

[Collega proxy rotanti al tuo progetto Scrapy](https://quantumproxies.io/rotating-proxies)
