Scraping senza farsi bloccare: una guida pratica con codice
Farsi bloccare durante lo scraping di solito significa una di tre cose: hai inviato troppe richieste troppo in fretta, il tuo client non assomigliava per niente a un browser o a un bot noto, oppure il tuo indirizzo IP è in una lista in cui il sito ha meno fiducia. Puoi ridurre tutti e tre con alcune abitudini: notare il blocco presto, rallentare quando il sito ti respinge, mantenere un'identità coerente e onesta, e usare i proxy solo dove sono appropriati. Questa guida le illustra una per una con codice funzionante. Usala per siti di cui sei autorizzato a fare scraping.
Riconosci un blocco
Un blocco non sempre assomiglia a una pagina di errore. Osserva:
- 403 Forbidden, che spesso significa che il sito ha respinto il tuo client. Fermati e verifica se sei autorizzato a stare lì prima di riprovare.
- 429 Too Many Requests, che significa che stai inviando troppo. Spesso arriva con un'intestazione
Retry-After. - 503 Service Unavailable, che può indicare che il sito è sovraccarico. Rallenta.
- Una risposta 200 con quasi nessun contenuto, un reindirizzamento a una pagina di login o di sfida, oppure una pagina priva dei dati che ti aspettavi.
Registra il codice di stato e un breve campione del corpo per ogni errore. Un blocco che non vedi nei log è un blocco che non puoi correggere.
Attendi con nuovi tentativi
Quando il sito dice "rallenta", il tuo codice deve rallentare più di un po'. Il backoff esponenziale, con un limite sul numero di tentativi, è l'approccio standard. Rispetta anche Retry-After quando il sito lo invia:
import time
import requests
session = requests.Session()
session.headers.update({
"User-Agent": "ExampleBot/1.0 (+https://example.com/bot)",
"Accept-Language": "en-US,en;q=0.9",
})
def polite_get(url: str, max_attempts: int = 4) -> requests.Response:
delay = 2.0
resp = None
for _ in range(max_attempts):
resp = session.get(url, timeout=30)
if resp.status_code not in (429, 503):
return resp
retry_after = resp.headers.get("Retry-After", "")
wait = float(retry_after) if retry_after.isdigit() else delay
time.sleep(wait)
delay *= 2
return resp # ancora bloccato: restituisce l'ultima risposta da gestire al chiamante
resp = polite_get("https://example.com/products?page=1")
print(resp.status_code, len(resp.text))
Nota cosa la funzione non fa. Non ritenta un 403, e non ruota tra identità per superare un blocco. Un 403 è una decisione di fermarsi, non un enigma da risolvere.
Diagnostica un blocco passo passo
Quando un'esecuzione comincia a fallire, segui questi controlli prima di cambiare qualsiasi altra cosa:
- Guarda il primo URL che fallisce e il suo codice di stato. Un blocco di solito inizia in un punto preciso, non a caso.
- Confronta la frequenza delle richieste nei minuti precedenti al primo errore. Una raffica è la causa più comune.
- Verifica se il fallimento è legato a un solo percorso, a una sola sezione o a un'ora del giorno.
- Carica a mano lo stesso URL una volta in un browser normale. Se il browser lo carica, la differenza sta nello schema delle tue richieste. Se anche il browser è bloccato, il sito ha smesso per ora di servire la pagina a te.
- Solo dopo, cambia il codice. Prima rallenta, poi aggiungi il backoff, e per ultima verifica la tua identità.
Cambiare più cose insieme rende impossibile sapere quale abbia aiutato.
Mantieni un'identità coerente e onesta
Gli scraper vengono bloccati quando la loro identità cambia tra una richiesta e l'altra, o quando non corrisponde al resto del traffico. Una singola sessione con uno User-Agent chiaro è l'identità stabile più semplice. Dai un nome al tuo bot, aggiungi un link di contatto e invia solo le intestazioni che invierebbe un client normale. Cambiare gli User-Agent per sembrare molti visitatori diversi è una forma di evasione, e tende a rendere più probabile il blocco, non meno.
Se un sito serve contenuti diversi ai browser rispetto ai client HTTP semplici, usa un browser reale per quelle pagine e mantieni lo stesso limite di frequenza. È un percorso legittimo, e mantiene sensato lo schema delle richieste.
Usa i proxy con cautela
Un proxy cambia l'indirizzo da cui parte una richiesta. È uno strumento per esigenze legittime, come testare da una regione specifica su un sito che sei autorizzato a testare, oppure distribuire il carico sulla tua infrastruttura. Non è un modo per sfuggire a un limite che il sito ti ha imposto. Il formato di un proxy è quello usato dai tipi di attività proxy:
proxies = {
"http": "http://user:pass@proxy.example.net:8000",
"https": "http://user:pass@proxy.example.net:8000",
}
resp = session.get("https://example.com/products", proxies=proxies, timeout=30)
La scelta del tipo di proxy è una questione separata, trattata in proxy residenziali e datacenter e proxy rotanti per lo scraping.
Quando il blocco diventa un captcha
Alcuni siti rispondono a un problema di frequenza con un captcha anziché con un blocco definitivo. L'ordine sensato è: fermati, rallenta, ricontrolla le regole e solo poi valuta una risoluzione. Per i widget PopularCaptcha, l'API attiva di Scarnox restituisce un token che invii nel campo h-captcha-response. Per altri tipi di captcha, controlla la lista attiva, perché il supporto a reCAPTCHA, Cloudflare Turnstile, GeeTest, AWS WAF e Arkose è in arrivo. La guida dettagliata è captcha nel web scraping.
Se inizi a vedere blocchi su un sito, la causa è quasi sempre lo schema delle richieste. Evitare i captcha durante lo scraping descrive le abitudini che lo prevengono.
Scraping responsabile
- Controlla
robots.txte i termini del sito prima di iniziare, e segui i percorsi e i ritardi che richiedono. - Fermati quando ricevi un 403 o un chiaro messaggio di "non fare scraping". Non continuare a provare.
- Mantieni la frequenza abbastanza bassa da non farsi notare da un visitatore normale.
- Non raccogliere dati personali che non hai motivo di conservare, e gestisci quelli che raccogli nel rispetto delle norme sulla privacy che ti si applicano.
- Se ti serve un volume molto superiore a quello che un sito serve comodamente, chiedi al titolare un feed o un'API.