403 Forbidden nel Web Scraping: La Scala di Soluzioni che Funziona Davvero

Un 403 non è un problema di permessi — è un problema di rilevamento. Quattro gradini separano uno script bloccato da un 200, e la maggior parte delle persone si ferma al primo.

Un errore 403 forbidden nel web scraping quasi mai significa ciò che dice il codice di stato. HTTP 403 è definito come il server che comprende la tua richiesta e si rifiuta di autorizzarla — ma quando colpisce uno scraper, raramente ha a che fare con permessi o un login mancante. Significa che il sito ha esaminato la tua richiesta, ha deciso che è stata inviata da una macchina e ha chiuso la porta. Questo ti dice cosa cambiare: non le tue credenziali, ma la forma del tuo traffico. Di seguito è riportata la scala delle soluzioni, dal gradino più economico, con i controlli che identificano su quale gradino sei bloccato.

403 vs 401 vs 429: cosa ti dice ciascuno

Fai la diagnosi corretta prima di scrivere codice. Un 401 Unauthorized chiede credenziali — fornirle lo risolve. Un 403 Forbidden rifiuta indipendentemente dalle credenziali, quindi accedere non cambia nulla se è stato attivato il rilevamento bot. Un 429 Too Many Requests riguarda il volume e si risolve quando la finestra si resetta; un 403 riguarda l'identità e persiste finché non cambi l'aspetto della tua richiesta. Se il tuo scraper riceve 429 invece di 403, la cura è il ritmo, non il travestimento — ne parliamo in risolvere 429 troppe richieste.

Diagnostica in 60 secondi, prima di cambiare qualsiasi codice

Tre comandi ti dicono quasi tutto. Esegui la richiesta base, eseguila di nuovo con solo un User-Agent del browser scambiato, e poi leggi il corpo della risposta — il motivo del blocco è solitamente scritto lì.

# 1. Bare request: what does the target give a naked client?
curl -sS -o /dev/null -w '%{http_code}\n' https://target.example/page

# 2. Same request, browser User-Agent only
curl -sS -o /dev/null -w '%{http_code}\n' \
  -A 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36' \
  https://target.example/page

# 3. Read the body and the response headers - the reason is in there
curl -sS -D - https://target.example/page | head -c 600

Interpretalo così. Se il passo 2 restituisce 200, il problema è tutto nei headers e hai finito al gradino 1. Se il corpo menziona Cloudflare, un Ray ID o Errore 1020, sei dietro una regola WAF — vedi Cloudflare errore 1020. Una semplice pagina Forbidden di Apache o nginx solitamente significa un modulo server come mod_security, che ha bloccato User-Agent noti come bot da molto prima che esistesse la gestione moderna dei bot. E se un browser sulla stessa macchina carica la pagina mentre il tuo client no, lavora attraverso curl 403 ma il browser funziona.

La scala delle soluzioni 403 forbidden per il web scraping: headers, impronta digitale TLS, tipo di IP e rendering come quattro gradini crescenti
Scala un gradino, ritesta, fermati non appena ottieni un 200. Il rendering costa di più e risolve di meno.

Gradino 1: smetti di annunciarti nei headers

urllib di Python si identifica come qualcosa tipo python-urllib/3.3.0; requests invia python-requests/2.x. Queste stringhe sono una confessione, e la risposta più votata nel thread canonico di Stack Overflow sui 403 nel scraping Python è semplicemente: invia un User-Agent del browser invece. Funziona ancora su molti siti. Ma due cose sono cambiate da quando quella risposta è stata scritta. Primo, un semplice Mozilla/5.0 è ora esso stesso una bandiera — i commentatori su quello stesso thread riportano che i siti lo bloccano direttamente, perché nessun vero browser invia un UA a due token. Secondo, i server moderni confrontano l'intero set di headers, non un solo campo.

Invia un set coerente: un UA del browser attuale, la catena Accept corrispondente, una lingua, e i headers di metadati Sec-Fetch-* che Chromium aggiunge a ogni navigazione. Anche l'ordine dei headers è importante su obiettivi più rigidi — usa una mappatura ordinata e mettili nella sequenza che usa un browser.

import requests

HEADERS = {
    "User-Agent": (
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
        "(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
    ),
    "Accept": (
        "text/html,application/xhtml+xml,application/xml;q=0.9,"
        "image/avif,image/webp,*/*;q=0.8"
    ),
    "Accept-Language": "en-GB,en;q=0.9",
    "Accept-Encoding": "gzip, deflate",   # add 'br' only if brotli is installed
    "Upgrade-Insecure-Requests": "1",
    "Sec-Fetch-Dest": "document",
    "Sec-Fetch-Mode": "navigate",
    "Sec-Fetch-Site": "none",
    "Sec-Fetch-User": "?1",
    "Connection": "keep-alive",
}

with requests.Session() as s:
    s.headers.update(HEADERS)
    r = s.get("https://target.example/page", timeout=20)
    print(r.status_code, len(r.content))

Una trappola di coerenza cattura quasi tutti: un UA che dichiara un desktop Chrome USA, abbinato a Accept-Language: de-DE e un IP di uscita in Brasile, è una discrepanza che qualsiasi sistema decente nota. Mantieni User-Agent, lingua e geografia IP raccontando la stessa storia.

Alcuni 403 a questo gradino sono ancora più semplici: un Referer mancante. I server che servono un asset solo quando la richiesta sembra provenire dalla loro pagina restituiranno 403 a un colpo diretto e 200 nel momento in cui aggiungi l'URL di riferimento. È un classico, e costa un header per testarlo.

Gradino 2: l'impronta digitale TLS che i headers non possono correggere

Se headers perfetti restituiscono ancora 403, il blocco è avvenuto prima che i tuoi headers fossero letti. Ogni client HTTPS annuncia i suoi cipher suites, estensioni, curve ellittiche e ALPN nel TLS ClientHello, e quella combinazione si trasforma in un'impronta digitale JA3 o JA4. requests di Python, net/http di Go e curl standard hanno ciascuno un'impronta distintiva che nessun fornitore anti-bot ha difficoltà a distinguere da Chrome. Dichiarare di essere Chrome 131 in un header mentre si effettua l'handshake come OpenSSL è la contraddizione più forte che uno scraper possa fare.

La soluzione è un client che impersona un vero browser al livello TLS. In Python è curl_cffi, un binding a una libcurl modificata che riproduce i ClientHello del browser:

# pip install curl_cffi
from curl_cffi import requests as cffi

proxy = "http://USER:PASS@gate.quantumproxies.io:8000"

r = cffi.get(
    "https://target.example/page",
    impersonate="chrome",              # Chrome JA3/JA4 + HTTP/2 settings
    proxies={"http": proxy, "https": proxy},
    timeout=20,
)
print(r.status_code)

Node ha equivalenti costruiti sugli stessi stack TLS modificati. Se vuoi la meccanica completa del perché questo singolo cambiamento trasforma un 403 in un 200 su siti protetti, leggi come l'impronta digitale JA3/JA4 rivela il tuo scraper.

Gradino 3: l'IP è il messaggio

Headers e TLS descrivono il client. L'IP descrive chi sta chiedendo, ed è pesantemente ponderato. Gli indirizzi in hosting e cloud sono pubblicati, facili da mappare tramite ASN, e portano un punteggio di fiducia inferiore prima che un singolo byte della tua richiesta venga ispezionato — ecco perché uno scraper su un VPS ottiene 403 che lo stesso codice su una connessione domestica supera senza problemi. Gli indirizzi residenziali appartengono a fornitori di internet per consumatori e sono trattati come persone; gli IP dei carrier mobili si trovano dietro CGNAT con migliaia di veri abbonati ciascuno, il che li rende i più difficili da bloccare all'ingrosso.

Quindi il gradino 3 è uno scambio, non una riscrittura: invia la stessa richiesta ben formata da un'uscita residenziale. QuantumProxies gestisce oltre 90 milioni di IP residenziali in oltre 200 paesi con rotazione per richiesta o sessioni sticky, HTTP e SOCKS5 su ogni piano, e fatturazione pay-per-GB — una linea di configurazione cambia l'ASN che il tuo obiettivo vede. Già su residenziale e ancora bloccato? Controlla la reputazione del pool con il nostro controllo punteggio qualità IP gratuito: qualsiasi punteggio sopra 75 è bruciato e raccoglierà 403 indipendentemente da quanto buoni siano i tuoi headers.

Salta la scala: recupera qualsiasi pagina con l'API Scraper

Checklist che confronta i segnali di richiesta dello scraper che attivano un errore 403 forbidden contro i segnali che invia un vero browser
I sistemi anti-bot testano la coerenza. Un segnale non corrispondente in questo elenco è sufficiente per un 403.

Gradino 4: rendering e sfide

L'ultimo gradino è il più costoso. Alcuni 403 sono la metà visibile di una sfida JavaScript: il server invia un piccolo script, si aspetta una risposta entro pochi secondi, e rifiuta tutto ciò che non può eseguirlo. Nessun set di headers e nessun IP risolve questo, perché il test è se puoi eseguire codice. Opzioni in ordine crescente di costo: un browser headless con un set di patch stealth, un pool di browser gestito, o un'API di scraping che esegue il rendering su richiesta. Il rendering costa molte volte di più per pagina rispetto a una semplice richiesta HTTP, quindi esegui l'escalation solo per gli URL che ne hanno veramente bisogno.

L'API Scraper di QuantumProxies Scraper API collassa i gradini 2 a 4 in una richiesta: TLS di livello browser, uscite residenziali, rendering JavaScript quando una pagina ne ha bisogno, e markdown, JSON o HTML grezzo di ritorno. Questo è il commercio onesto — smetti di mantenere la scala e paghi per pagina di successo invece.

Lavorare con la scala nella pratica

La prevenzione del ban è la disciplina sorella: una volta che hai un 200, mantenerlo è una questione di ritmo, igiene delle sessioni e salute del pool piuttosto che di travestimento.

Domande frequenti

Cosa causa un errore 403 forbidden nel web scraping?

Rilevamento, nella quasi totalità dei casi. I trigger usuali sono un User-Agent di libreria predefinito, un set di headers incompleto o contraddittorio, un IP di datacenter con una cattiva reputazione, un ritmo di richieste troppo regolare, o un'impronta digitale TLS che non corrisponde al browser che dichiari di essere. Gli errori di permesso genuini esistono, ma restituiscono 403 anche ai browser — testalo in uno prima di presumere.

Come posso bypassare un errore 403 forbidden in Python?

Lavora con la scala. Aggiungi un set completo di headers del browser a un requests.Session; se fallisce, passa a un client che impersona il TLS del browser come curl_cffi; se fallisce, instrada attraverso un proxy residenziale; se la pagina invia una sfida JavaScript, eseguila o usa un'API di scraping. Esegui l'escalation solo quando il gradino più economico è stato effettivamente testato.

Perché httpx restituisce 403 quando il mio browser no?

Stesso motivo di requests: httpx invia un set di headers minimo e un'impronta digitale TLS di Python. Copia la richiesta esatta del browser da DevTools, riproducila con httpx, e il 403 solitamente scompare — il che ti dice che la differenza erano i headers. Se persiste con headers identici, il blocco è a livello TLS o IP.

Come posso correggere 403 forbidden in Scrapy?

Imposta un DEFAULT_REQUEST_HEADERS realistico più USER_AGENT, mantieni ROBOTSTXT_OBEY onesto su cosa ti è permesso recuperare, abilita AUTOTHROTTLE_ENABLED, e instrada le richieste attraverso un middleware proxy rotante. I retry di Scrapy non riprovano 403 di default — aggiungilo a RETRY_HTTP_CODES solo se ruoti l'IP tra i tentativi.

Un 403 è informazione, non un muro. Ti dice quale dei quattro segnali ti ha tradito, e ogni gradino della scala costa più di quello sotto. Inizia dal più economico, testa dopo ogni cambiamento, e smetti di salire nel momento in cui il codice di stato diventa 200.

Ottieni proxy residenziali che superano il gradino 3