curl Restituisce 403 ma il Browser Funziona: Trova il Pezzo Mancante

Il browser lo carica. curl riceve 403. La differenza tra queste due richieste è sempre finita e sempre individuabile — ecco come sezionarla in cinque minuti.

Incolli un URL in Chrome e la pagina si carica. Incolli lo stesso URL in curl e ottieni 403 Forbidden. Nulla è cambiato nella risorsa tra quei due secondi, quindi la differenza è interamente nella richiesta — e una richiesta è una cosa finita e ispezionabile. Questa guida è una procedura di sezionamento: riproduci esattamente ciò che il browser ha inviato, quindi rimuovi i pezzi finché il 403 non ritorna. Qualunque cosa tu abbia rimosso per ultima è la tua risposta. I sospetti, nell'ordine in cui sono solitamente colpevoli, sono User-Agent, Referer, cookie, impronta TLS e JavaScript.

Passo 0: dimostra che le richieste sono davvero diverse

Prima di teorizzare, guarda cosa invia effettivamente curl. Con -v vedi la linea di richiesta, ogni header e la stretta di mano TLS. Una richiesta curl predefinita è sorprendentemente scarna — tipicamente Host, User-Agent: curl/8.x e Accept: */*. Un browser invia una dozzina di elementi in più.

# what you send, what you get back, and the TLS details
curl -v -o /dev/null https://target.example/page

# just the response headers, quickly
curl -sS -o /dev/null -D - https://target.example/page

Leggi gli header di risposta con la stessa attenzione della linea di stato. Uno di essi risolve la questione immediatamente nel caso più comune: Vary: User-Agent significa che il server serve deliberatamente risposte diverse a seconda di chi dichiari di essere. In un caso ben documentato su Stack Overflow, curl -f contro un host Apache 2.4.38 restituito 403 mentre wget ha recuperato lo stesso file con un 200 — e la risposta di successo conteneva esattamente quell'header Vary: User-Agent. Passare -A 'Wget/1.21.2' a curl lo ha risolto istantaneamente. Il proprietario del sito aveva inserito nella blacklist l'user agent di curl dopo abusi; nient'altro della richiesta importava.

Mentre leggi l'output: curl: (22) L'URL richiesto ha restituito errore: 403 non è un problema separato. Il codice di uscita 22 è ciò che -f/--fail fa con qualsiasi errore HTTP — il flag sopprime il corpo e fallisce il comando. Rimuovi temporaneamente -f in modo da poter effettivamente leggere la pagina di blocco, che di solito nomina il sistema che ti ha fermato.

Passo 1: Copia come cURL, la risposta da 30 secondi

Entrambi i principali browser possono fornirti la richiesta esatta che hanno appena fatto. Apri DevTools, vai alla scheda Rete, fai clic con il pulsante destro del mouse sulla richiesta e scegli "Copia come cURL". Chrome ha introdotto questa funzione dalla versione 26 e Firefox dalla 31, e l'output include ogni header, ogni cookie e il referer. Incollalo nel tuo terminale: se restituisce 200, il tuo problema è definitivamente nella forma della richiesta, e il passo 2 trova quale parte.

Un intoppo qui fa perdere molto tempo. Se l'URL reindirizza, il pannello Rete si svuota alla navigazione e copi la richiesta sbagliata. Spunta "Preserva log" in Chrome o "Log Persistenti" in Firefox prima, così puoi vedere sia la richiesta che ha reindirizzato sia quella che ha finalmente servito il contenuto. Le catene di reindirizzamento contano: in un noto thread su Unix Stack Exchange il server controllava il Referer, poi passava attraverso un 302 a una posizione che non controllava nulla — il che faceva sembrare il fallimento casuale finché l'intera catena non era visibile.

Confronto fianco a fianco di una richiesta del browser Chrome e una richiesta curl predefinita che mostra il conteggio degli header, i cookie, l'impronta TLS e il supporto JavaScript
Ogni 403 in questo scenario si nasconde dentro questo divario. Chiudilo una colonna alla volta.

Passo 2: seziona gli header

Parti dal comando "Copia come cURL" funzionante e cancella gli header uno alla volta, eseguendo nuovamente dopo ogni cancellazione. La prima cancellazione che riporta il 403 nomina il tuo colpevole. In pratica è quasi sempre uno di quattro.

curl -sS -o /dev/null -w '%{http_code}\n' \
  -A 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36' \
  -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' \
  -H 'Accept-Language: en-GB,en;q=0.9' \
  -e 'https://target.example/' \
  -b 'session=abc123; consent=1' \
  -L \
  'https://target.example/page'

Due dettagli di chiusura per questo gradino. Cita l'URL: una stringa di query contenente & o un token di accesso viene danneggiata dalla tua shell altrimenti, e il 403 risultante non ha nulla a che fare con il server. E se stai eseguendo il debug da PHP o Node piuttosto che dalla shell, replica lo stesso set di header lì — i default di libcurl all'interno di PHP differiscono da quelli dello strumento a riga di comando, motivo per cui la richiesta identica può passare in un terminale e fallire nel codice. Le nostre ricette curl con proxy coprono la sintassi dei flag in dettaglio.

Passo 3: quando gli header identici restituiscono ancora 403

Se una copia byte per byte degli header del browser fallisce ancora, la decisione è stata presa prima che i tuoi header fossero analizzati. Due livelli si trovano sotto di essi.

Impronta TLS. Il tuo ClientHello — suite di cifratura, estensioni, preferenze delle curve, ALPN, più il frame delle impostazioni HTTP/2 che segue — si hash a un valore JA3 o JA4. curl costruito contro OpenSSL ne produce uno che nessun browser produce mai, e i sistemi anti-bot lo confrontano con il tuo User-Agent dichiarato. Dichiarare di essere Chrome mentre si effettua una stretta di mano come OpenSSL è una contraddizione che sono costruiti per catturare. La soluzione è un client che riproduce le strette di mano del browser: curl-impersonate al comando, o curl_cffi da Python.

# pip install curl_cffi
from curl_cffi import requests

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

r = requests.get(
    "https://target.example/page",
    impersonate="chrome",          # browser ClientHello + HTTP/2 settings
    proxies={"http": proxy, "https": proxy},
    timeout=20,
)
print(r.status_code, r.headers.get("content-type"))

Il tuo IP. Il browser che funziona è solitamente sulla tua connessione domestica mentre curl gira su un VPS. Gli ASN di hosting sono pubblicati e pre-valutati, quindi la stessa richiesta da un indirizzo residenziale viene giudicata diversamente prima ancora di essere letta. Cambiare l'uscita è una modifica di una riga con proxy residenziali — oltre 90 milioni di IP in più di 200 paesi, HTTP e SOCKS5 su ogni piano — ed è il modo più veloce per escludere la rete. La meccanica del livello di impronta è in impronta TLS JA3/JA4.

Escludi la rete con proxy residenziali

Albero decisionale dal curl 403 a una risposta 200: copia come cURL, seziona gli header, impersona TLS, quindi rendi o usa un'API di scraper
Quattro passi, ognuno un test. Fermati non appena lo stato diventa 200 — non salire più in alto di quanto necessario.

Passo 4: la pagina ha bisogno di un browser, non di un client

A volte il 403 non è un giudizio su di te — è la modalità di fallimento di una sfida che non hai mai tentato. Una discussione pubblica su GitHub sui controllori di link che colpiscono npmjs.com lo mette chiaramente: curl non può produrre una soluzione valida alla sfida, quindi la richiesta viene bloccata con un 403. Il server emette un piccolo problema JavaScript, attende un momento per la risposta e rifiuta qualsiasi cosa che non possa eseguirlo. Nessun set di header, nessuna impronta e nessun IP supera un test che richiede l'esecuzione del codice.

A quel punto hai tre opzioni oneste: guidare un vero browser e pagare il costo, trovare l'endpoint JSON che la pagina stessa chiama (spesso seduto nella stessa scheda Rete che hai già aperto), o consegnare l'URL a un servizio che rende su richiesta. L'API Scraper di QuantumProxies fa l'ultimo — TLS di grado browser, uscite residenziali, rendering JavaScript solo dove una pagina ne ha bisogno, e markdown, JSON o HTML grezzo restituiti da una richiesta. Se ciò che ottieni è una pagina vuota piuttosto che una vietata, è una diagnosi diversa: vedi perché il tuo scraper restituisce una pagina vuota. E se la pagina di blocco porta un ID Ray di Cloudflare, vai a Cloudflare errore 1020 invece.

Domande frequenti

Perché curl riceve 403 quando il mio browser no?

Perché curl invia circa tre header, nessun cookie, nessun referer e un'impronta TLS non da browser, mentre il tuo browser invia una dozzina di header, un contenitore di cookie e una stretta di mano Chrome. Il server sta rifiutando la richiesta, non la risorsa. Riproduci la richiesta esatta del browser con "Copia come cURL", quindi rimuovi gli header uno per uno per trovare quale differenza conta.

Perché wget riesce dove curl riceve 403?

Quasi sempre l'User-Agent. Alcuni server inseriscono nella blacklist specificamente l'UA di curl dopo abusi lasciando quello di wget intatto — un caso documentato ha mostrato un header di risposta Vary: User-Agent che conferma che il server si ramifica su di esso, e curl -A 'Wget/1.21.2' ha ripristinato il 200. wget invia anche Accept-Encoding e Connection di default, il che occasionalmente conta anche.

Come imposto un User-Agent in curl?

Usa -A 'string', o l'equivalente -H 'User-Agent: string'. Preferisci una stringa completa e attuale del browser rispetto a un Mozilla/5.0 troncato, che alcuni server ora rifiutano proprio perché nessun vero browser invia solo due token. Abbinalo a valori Accept e Accept-Language corrispondenti in modo che l'intero set rimanga coerente.

Cosa significa l'errore 22 di curl?

Il codice di uscita 22 è prodotto da -f/--fail ogni volta che il server restituisce un errore HTTP, e il messaggio cita lo stato — comunemente 403. È un flag di segnalazione, non un difetto distinto. Rimuovi -f per vedere il corpo della risposta, che di solito spiega il blocco molto meglio del codice di uscita.

Un proxy può risolvere un curl 403?

Risolve il sottoinsieme causato dalla reputazione o dalla geografia dell'IP — un ampio sottoinsieme quando il tuo script gira su un host cloud e il tuo browser no. Non risolverà un referer mancante, un cookie assente o una sfida JavaScript. Testa prima gli header, poiché non costano nulla, quindi cambia l'IP di uscita per isolare il livello di rete.

Non c'è mistero qui, solo un divario: il browser ha inviato una richiesta e tu ne hai inviata un'altra. Copia quella del browser, riducila finché non si rompe, e troverai sempre il pezzo che contava — di solito un header, a volte un'impronta, occasionalmente una sfida che richiede un vero browser per rispondere.

Recupera qualsiasi pagina con l'API Scraper