Torna al blog

Configurazione dei proxy nel framework Scrapy: guida completa con esempi di codice

Guida completa all'integrazione dei proxy in Scrapy: dalla configurazione di base a metodi avanzati di rotazione degli indirizzi IP con esempi di codice funzionante.

📅14 febbraio 2026

Scrapy è uno dei framework Python più potenti per il web scraping, ma senza una corretta configurazione del proxy, i tuoi scraper verranno bloccati dopo pochi minuti di lavoro. In questa guida mostrerò tutti i modi per integrare i proxy in Scrapy: dalla configurazione più semplice ai metodi avanzati di rotazione degli indirizzi IP con gestione automatica degli errori.

Il materiale si basa su esperienze reali di scraping di grandi piattaforme di e-commerce e siti protetti. Otterrai esempi di codice pronti all'uso nei tuoi progetti.

Perché Scrapy senza proxy riceve blocchi

I siti moderni utilizzano una protezione multilivello contro lo scraping. Anche se hai configurato l'User-Agent e i ritardi tra le richieste, il tuo indirizzo IP rivela l'automazione per diversi motivi:

  • Frequenza delle richieste: un IP effettua 100+ richieste al minuto — chiaro segno di un bot
  • Modelli di comportamento: navigazione sequenziale delle pagine senza transizioni casuali
  • Assenza di JavaScript: Scrapy non esegue JS, il che è facilmente rilevabile
  • Geolocalizzazione: accesso da un data center invece che da una rete domestica

Il risultato è un ban per IP che dura ore o giorni. Le piattaforme di marketplace (Amazon, Wildberries, Ozon), i social network e i siti con Cloudflare utilizzano una protezione particolarmente aggressiva. I proxy risolvono questo problema distribuendo le richieste tra molti indirizzi IP.

Importante: Anche con i proxy è necessario rispettare i limiti di frequenza. Velocità consigliata: 1-3 richieste al secondo per un singolo IP. Per scraping ad alta velocità, utilizza un pool di 50+ proxy con rotazione.

Configurazione di base del proxy in Scrapy

Il modo più semplice è specificare il proxy direttamente nelle impostazioni del ragno. Questo metodo è adatto per testare o per scraping di piccole quantità di dati con un singolo server proxy.

Metodo 1: Tramite 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):
        # La tua logica di scraping
        self.log(f'Scraped {response.url} via {response.meta["proxy"]}')

Il formato del proxy dipende dal protocollo e dal metodo di autenticazione:

  • http://proxy.example.com:8080 — senza autenticazione
  • http://user:pass@proxy.example.com:8080 — con login/password
  • socks5://user:pass@proxy.example.com:1080 — proxy SOCKS5

Metodo 2: Configurazione globale in settings.py

# settings.py

# Proxy HTTP per tutte le richieste
HTTPPROXY_ENABLED = True
HTTPPROXY_AUTH_ENCODING = 'utf-8'

# Configurazione tramite variabili d'ambiente
HTTP_PROXY = 'http://username:password@proxy.example.com:8080'
HTTPS_PROXY = 'http://username:password@proxy.example.com:8080'

Questo metodo è comodo per test rapidi, ma non è adatto per la produzione: non c'è rotazione IP, se il proxy fallisce, l'intero scraper si ferma, non è possibile utilizzare proxy diversi per siti diversi.

Creazione di un Proxy Middleware personalizzato

Per lo scraping in produzione è necessario un middleware personalizzato che gestisca il pool di proxy, gestisca gli errori e ruoti automaticamente gli IP. Ecco una implementazione di base:

# 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):
        # Carichiamo la lista dei proxy dalle impostazioni
        proxy_list = crawler.settings.getlist('PROXY_LIST')
        
        if not proxy_list:
            raise NotConfigured('PROXY_LIST non configurato')
        
        return cls(proxy_list)
    
    def process_request(self, request, spider):
        # Selezioniamo un proxy casuale dal pool
        proxy = random.choice(self.proxy_list)
        request.meta['proxy'] = proxy
        
        spider.logger.info(f'Utilizzando proxy: {proxy}')
    
    def process_exception(self, request, exception, spider):
        # In caso di errore proviamo un altro proxy
        proxy = random.choice(self.proxy_list)
        request.meta['proxy'] = proxy
        
        spider.logger.warning(
            f'Errore proxy, passando a: {proxy}'
        )
        
        return request

Ora configuriamo l'uso del middleware in settings.py:

# settings.py

# Lista dei proxy (può essere caricata da un file o API)
PROXY_LIST = [
    'http://user1:pass1@proxy1.example.com:8080',
    'http://user2:pass2@proxy2.example.com:8080',
    'http://user3:pass3@proxy3.example.com:8080',
    # ... aggiungi 50+ proxy per una rotazione efficace
]

# Colleghiamo il middleware
DOWNLOADER_MIDDLEWARES = {
    'myproject.middlewares.RandomProxyMiddleware': 350,
    'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': 400,
}

# Tentativi ripetuti in caso di errori
RETRY_TIMES = 3
RETRY_HTTP_CODES = [500, 502, 503, 504, 408, 429]

Rotazione dei proxy: tre metodi funzionanti

La selezione casuale dei proxy (come nell'esempio sopra) è il metodo più semplice, ma non il più efficace. Consideriamo tre strategie di rotazione per diversi scenari.

Metodo 1: Round-robin (rotazione sequenziale)

I proxy vengono selezionati in modo circolare. Adatto per una distribuzione uniforme del carico:

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):
        # Prendiamo il prossimo proxy in circolo
        proxy = self.proxy_list[self.current_index]
        self.current_index = (self.current_index + 1) % len(self.proxy_list)
        
        request.meta['proxy'] = proxy

Metodo 2: Rotazione intelligente con blacklist

Monitoriamo i proxy problematici ed escludiamo temporaneamente dalla rotazione:

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 minuti
        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):
        # Rimuoviamo dalla blacklist i proxy il cui timeout è scaduto
        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
        
        # Restituiamo i proxy funzionanti
        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('Tutti i proxy sono in blacklist!')
            return
        
        proxy = random.choice(working_proxies)
        request.meta['proxy'] = proxy
    
    def process_response(self, request, response, spider):
        # Se riceviamo un blocco — aggiungiamo alla blacklist
        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} in blacklist per {self.blacklist_timeout}s'
                )
        
        return response

Metodo 3: Rotazione tramite API del fornitore

Molti fornitori di proxy (inclusi proxy residenziali) offrono un endpoint rotante — un URL che cambia automaticamente l'IP ad ogni richiesta:

# settings.py

# Unico endpoint con rotazione automatica
ROTATING_PROXY = 'http://username:password@rotating.proxy.com:8080'

# Middleware semplice
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):
        # Un URL, ma ogni richiesta avviene con un nuovo IP
        request.meta['proxy'] = self.proxy

Questo è il metodo più comodo per la produzione: non è necessario gestire un pool di proxy, il fornitore si occupa della qualità degli IP e sostituisce quelli problematici. Funziona particolarmente bene con proxy residenziali, dove il pool di IP può raggiungere milioni di indirizzi.

Autenticazione: login/password vs IP whitelist

I fornitori di proxy offrono due metodi di autenticazione. La scelta influisce sulla velocità di connessione e sulla facilità di configurazione.

Autenticazione User:Pass

Login e password vengono trasmessi nell'URL del proxy. Scrapy li converte automaticamente nell'intestazione HTTP Proxy-Authorization:

proxy = 'http://username:password@proxy.example.com:8080'
request.meta['proxy'] = proxy

# Scrapy aggiungerà automaticamente l'intestazione:
# Proxy-Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=

Pro: funziona da qualsiasi IP, facile cambiare proxy nel codice.
Contro: piccolo overhead su ogni richiesta (~50-100ms), credenziali in chiaro nel codice.

Autenticazione IP Whitelist

Aggiungi l'IP del tuo server nella whitelist del fornitore, non è necessaria l'autenticazione:

proxy = 'http://proxy.example.com:8080'  # senza login/password
request.meta['proxy'] = proxy

Pro: più veloce di 50-100ms, più sicuro (niente credenziali nel codice).
Contro: funziona solo da IP specifici, è necessario aggiornare la whitelist quando si cambia server.

Raccomandazione per la produzione:

Utilizza IP whitelist per lo scraping da server dedicati (AWS, Google Cloud, Hetzner). Per sviluppo e test da macchina locale — autenticazione user:pass.

Gestione degli errori e cambio automatico dell'IP

Anche con proxy di qualità ci saranno errori: timeout, rifiuti di connessione, blocchi. Una corretta gestione degli errori è fondamentale per il funzionamento stabile dello scraper.

Gestione degli stati HTTP

class ProxyMiddleware:
    def process_response(self, request, response, spider):
        # Codici per cui è necessario cambiare proxy e ripetere
        ban_codes = [403, 407, 429, 503]
        
        if response.status in ban_codes:
            proxy = request.meta.get('proxy')
            spider.logger.warning(
                f'Ricevuto {response.status} da {proxy}, riprovando...'
            )
            
            # Segniamo per retry con un nuovo proxy
            request.meta['dont_retry'] = False
            request.meta['proxy'] = self.get_new_proxy()
            
            return request
        
        return response

Gestione delle eccezioni di rete

from twisted.internet.error import TimeoutError, ConnectionRefusedError
from scrapy.exceptions import IgnoreRequest

class ProxyMiddleware:
    def process_exception(self, request, exception, spider):
        # Errori di connessione al proxy
        proxy_errors = (
            TimeoutError,
            ConnectionRefusedError,
            ConnectionLost,
        )
        
        if isinstance(exception, proxy_errors):
            proxy = request.meta.get('proxy')
            spider.logger.error(
                f'Connessione proxy {proxy} fallita: {exception}'
            )
            
            # Cambiamo proxy e riproviamo
            request.meta['proxy'] = self.get_new_proxy()
            return request
        
        # Per altre eccezioni utilizziamo la gestione standard
        return None

Rilevamento dei blocchi tramite contenuto

Alcuni siti restituiscono HTTP 200, ma mostrano un captcha o una pagina di blocco:

class ProxyMiddleware:
    def process_response(self, request, response, spider):
        # Segnali di blocco nel contenuto
        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'Spotted ban page from {request.meta.get("proxy")}'
            )
            
            # Cambiamo proxy e ripetiamo
            request.meta['proxy'] = self.get_new_proxy()
            return request
        
        return response

Quale tipo di proxy scegliere per Scrapy

La scelta del tipo di proxy dipende dal sito target, dal budget e dalla velocità di scraping richiesta. Ecco un confronto delle principali opzioni:

Tipo di proxy Velocità Costo Quando utilizzare
Proxy di data center Alta (50-200ms) Basso ($1-3/IP) Siti semplici senza protezione, API, strumenti interni
Proxy residenziali Media (300-800ms) Media ($5-15/GB) E-commerce, social media, siti con Cloudflare, geotargeting
Proxy mobili Bassa (500-1500ms) Alta ($50-150/IP) App mobili, Instagram, TikTok, massima protezione

Raccomandazioni per la scelta

Per lo scraping di marketplace (Amazon, Wildberries, Ozon, AliExpress) — solo proxy residenziali. Questi siti bloccano aggressivamente i data center. È necessaria la rotazione e il geotargeting (ad esempio, IP russi per Wildberries).

Per lo scraping di siti di notizie, blog, forum — sono adatti i proxy di data center. La protezione è minima, la velocità e il costo del traffico sono importanti.

Per lo scraping di siti con Cloudflare — i proxy residenziali sono obbligatori. I data center vengono rilevati quasi istantaneamente da Cloudflare. Aggiungi a Scrapy la libreria cloudscraper per bypassare le sfide JS.

Per lo scraping di Google Search, strumenti SEO — proxy residenziali con geotargeting. Google mostra risultati diversi per diversi paesi e città.

Consiglio: Inizia con un pool di 10 proxy residenziali per testare. Se ricevi blocchi — aumenta il pool a 50-100 IP. Per scraping ad alta velocità (1000+ richieste/minuto) utilizza un endpoint rotante con un pool di 10.000+ IP.

Tecniche avanzate: sessioni e IP sticky

Durante lo scraping di alcuni siti è necessario mantenere un IP durante l'intera sessione (autenticazione, carrello, moduli multi-passaggio). Ecco come implementare sessioni sticky in Scrapy.

IP sticky per un dominio

from urllib.parse import urlparse

class StickyProxyMiddleware:
    def __init__(self, proxy_list):
        self.proxy_list = proxy_list
        # Mappa: dominio -> 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):
        # Estraiamo il dominio dall'URL
        domain = urlparse(request.url).netloc
        
        # Se per questo dominio c'è già un proxy — lo utilizziamo
        if domain in self.domain_proxy_map:
            proxy = self.domain_proxy_map[domain]
        else:
            # Altrimenti scegliamo uno nuovo e lo memorizziamo
            proxy = random.choice(self.proxy_list)
            self.domain_proxy_map[domain] = proxy
            spider.logger.info(f'Assegnato {proxy} a {domain}')
        
        request.meta['proxy'] = proxy

IP sticky con timeout di sessione

Un'opzione più avanzata: il proxy è legato al dominio per un certo periodo (ad esempio, 10 minuti), poi cambia:

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 minuti
        # Mappa: dominio -> (proxy, tempo di creazione)
        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()
        
        # Controlliamo se c'è una sessione attiva
        if domain in self.sessions:
            proxy, created_at = self.sessions[domain]
            
            # Se la sessione non è scaduta — utilizziamo lo stesso proxy
            if current_time - created_at < self.session_timeout:
                return proxy
        
        # Creiamo una nuova sessione con un nuovo 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

Integrazione con Cookie Middleware

Per sessioni complete è necessario sincronizzare proxy e cookies. Scrapy memorizza i cookies separatamente per ogni dominio, ma quando si cambia proxy è necessario pulire i cookies:

# settings.py

# Attiviamo il cookie middleware
COOKIES_ENABLED = True
COOKIES_DEBUG = False

# Middleware per sincronizzare proxy e cookies
class ProxyCookieMiddleware:
    def process_request(self, request, spider):
        # Otteniamo il proxy attuale
        current_proxy = request.meta.get('proxy')
        
        # Se il proxy è cambiato — puliamo i cookies
        previous_proxy = request.meta.get('previous_proxy')
        
        if previous_proxy and previous_proxy != current_proxy:
            # Puliamo i cookies per questo dominio
            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 puliti per {domain}')
        
        request.meta['previous_proxy'] = current_proxy

Conclusione

Una corretta configurazione del proxy in Scrapy è la base per uno scraping stabile senza blocchi. Abbiamo esaminato tutti gli aspetti chiave: dall'integrazione di base a tecniche avanzate di rotazione e gestione delle sessioni.

Principali conclusioni:

  • Per la produzione utilizza un middleware personalizzato con rotazione intelligente e blacklist degli IP problematici
  • Gestisci tutti i tipi di errori: stati HTTP, eccezioni di rete, blocchi tramite contenuto
  • Scegli il tipo di proxy in base al compito: data center per siti semplici, residenziali per siti protetti
  • Per siti con autenticazione utilizza sessioni sticky legando il proxy al dominio
  • Inizia con un pool di 10-50 proxy, scala in base all'aumento del carico

Se intendi fare scraping di siti protetti (marketplace, social media, siti con Cloudflare), ti consiglio di utilizzare proxy residenziali — offrono la massima anonimato e il minimo rischio di blocchi. Per scraping ad alta velocità, scegli fornitori con endpoint rotante e un pool di 10.000 indirizzi IP.

Tutti gli esempi di codice di questo articolo sono stati testati su Scrapy 2.x e sono pronti per l'uso in produzione. Adattali alle tue esigenze e scala man mano che il progetto cresce.