# 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](https://quantumproxies.io/blog/fix-429-too-many-requests-scraping).

## 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ì.

```bash
# 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](https://quantumproxies.io/blog/cloudflare-error-1020-access-denied). 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](https://quantumproxies.io/blog/curl-403-but-browser-works).

![Uno script bloccato sale una scala di soluzioni a quattro gradini: header da browser, poi un'impronta TLS da browser con curl_cffi, poi un IP di uscita residenziale, poi il rendering per le sfide JavaScript. La scheda dice di ritestare dopo ogni singola modifica, di non cambiare mai due cose insieme e di riprovare un 403 al massimo una volta.](https://res.cloudinary.com/dmqliab0t/image/upload/f_auto,q_auto,w_1200/v1785335150/blog/generated/qp-b9-011-diag1.png)
*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.

```python
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:

```python
# pip install curl_cffi
from curl_cffi import requests as cffi

proxy = "http://USER:PASS@residentialpremium.quantumproxies.io:8080"

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](https://quantumproxies.io/blog/tls-ja3-fingerprinting-scraping).

## 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](https://quantumproxies.io/ip-checker) 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](https://quantumproxies.io/scraper-api)

![Una tabella confronta i segnali della richiesta uno per uno, scraper contro browser reale, con la colonna del browser evidenziata. La scheda elenca le incongruenze che portano a un 403: uno User-Agent python-requests, uno UA di Chrome su una stretta di mano OpenSSL, un Chrome statunitense con lingua de-DE e un IP di uscita brasiliano. La soluzione è una storia coerente.](https://res.cloudinary.com/dmqliab0t/image/upload/f_auto,q_auto,w_1200/v1785335150/blog/generated/qp-b9-011-diag2.png)
*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](https://quantumproxies.io/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

- Ritesta dopo ogni singolo cambiamento. Due cambiamenti contemporaneamente e non impari nulla su quale abbia funzionato.
- Memorizza nella cache il corpo grezzo del 403 durante lo sviluppo. Le pagine di blocco portano Ray ID, nomi di regole e impronte digitali del fornitore che nominano il tuo avversario.
- Tratta un 403 alla primissima richiesta come un problema di identità; un 403 che appare dopo 200 buone pagine è un problema di reputazione o ritmo.
- Riprova un 403 al massimo una volta, e solo dopo aver cambiato qualcosa. Martellare la stessa richiesta dallo stesso IP converte un blocco morbido in uno duro.
- Se esiste un'API pubblica o un feed per gli stessi dati, usalo. È più economico di ogni gradino sopra e non si rompe mai su un redesign.

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