Guida ai Proxy curl_cffi: Configurazione, Autenticazione, Rotazione e Async
curl_cffi ti offre una stretta di mano TLS simile a quella di un browser. Un proxy ti fornisce un IP di uscita pulito. Ecco esattamente come collegare i due — e perché il prefisso https:// nel tuo dizionario di proxy sta generando ErrCode 35.
curl_cffi è il binding Python di un fork di curl-impersonate: riproduce le impronte digitali TLS/JA3 e HTTP/2 di un vero browser invece di annunciarsi come urllib3. Questo risolve un asse di blocco. L'altro è l'IP di uscita, ed è qui che entra in gioco un proxy curl_cffi — e dove la documentazione diventa scarsa. La sezione ufficiale sui proxy è di circa quindici righe, e un issue su GitHub di febbraio 2023 è ancora tra i primi cinque su questo argomento. Questa guida copre l'intera superficie: il parametro proxy, il dizionario in stile requests e i suoi veri nomi di chiave, proxy_auth, sessioni, rotazione per richiesta, async, SOCKS5 — e le esatte stringhe di errore che incollerai in un motore di ricerca.
Sintassi del proxy curl_cffi: preferire proxy= rispetto al dizionario proxies
curl_cffi accetta due forme. Quella nativa è una singola stringa proxy=, aggiunta nella v0.6.0; il dizionario proxies= esiste per compatibilità con requests, e i documenti raccomandano il singolo parametro a meno che non si necessitino proxy diversi per schema. Internamente si riducono alla stessa cosa — proxy="..." diventa {"all": "..."} — e funzionano entrambi sui moduli helper, su Session, su AsyncSession e su richieste individuali.
# pip install curl_cffi --upgrade (Python 3.10+ since v0.14)
import curl_cffi
PROXY = "http://USER:PASS@gate.quantumproxies.io:PORT"
# Native form — one string, applies to every scheme
r = curl_cffi.get(
"https://tls.browserleaks.com/json",
impersonate="chrome",
proxy=PROXY,
timeout=30,
)
print(r.status_code, r.json()["ja3n_hash"])
# requests-compatible form
r = curl_cffi.get(
"https://httpbin.org/ip",
impersonate="chrome",
proxies={"http": PROXY, "https": PROXY},
timeout=30,
)
print(r.json()) # {'origin': '<proxy exit IP>'}
Quattro cose su quel dizionario meritano di essere conosciute, perché nessuna di esse è ovvia dal README:
- Le chiavi valide sono
all,http,https,wsewss.allè il jolly; le chiavi websocket sono rilevanti solo per il client WebSocket. - Funzionano anche le chiavi per host.
https://api.example.comoall://example.cominstradano solo quell'host attraverso un dato proxy — utile per inviare un dominio difficile attraverso IP residenziali e lasciare il resto diretto. - Non puoi passare entrambi.
proxy=piùproxies=nella stessa chiamata generaTypeError: Cannot specify both 'proxy' and 'proxies', e lo stesso controllo viene eseguito a livello di sessione. - Le variabili d'ambiente sono rispettate —
http_proxy,https_proxy,ws_proxy,wss_proxy. Passatrust_env=Falsea unaSessionquando una variabile aziendale dirotta il tuo scraper.
Una nota sulle importazioni: dalla v0.10.0 il pacchetto è chiamabile direttamente (curl_cffi.get, curl_cffi.Session). I tutorial più vecchi usano from curl_cffi import requests, che funziona ancora ma si legge male accanto alla vera libreria requests — e spiega perché metà degli snippet online sembrano un progetto diverso.
La trappola https://: ErrCode 35 e WRONG_VERSION_NUMBER
Questo singolo errore genera più domande sui proxy curl_cffi di tutto il resto combinato. L'issue #6 nel tracker del progetto — aperto e chiuso lo stesso giorno di febbraio 2023 — è ancora in prima pagina, perché l'errore che produce sembra un bug TLS piuttosto che un errore di configurazione:
# WRONG: this asks curl to open a TLS connection *to the proxy itself*
proxies = {"https": "https://USER:PASS@gate.quantumproxies.io:PORT"}
# Failed to perform, ErrCode: 35, Reason:
# 'error:100000f7:SSL routines:OPENSSL_internal:WRONG_VERSION_NUMBER'
# RIGHT: plain HTTP CONNECT, then the TLS tunnel runs through to the target
proxies = {"https": "http://USER:PASS@gate.quantumproxies.io:PORT"}
# Or skip the dict entirely
proxy = "http://USER:PASS@gate.quantumproxies.io:PORT"
I nomi delle chiavi indicano il protocollo del target; il valore indica come raggiungere il proxy. Un normale proxy HTTPS-over-HTTP accetta un CONNECT in testo semplice, quindi incanala il tuo traffico criptato senza toccarlo — quindi l'URL del proxy inizia con http:// anche quando ogni URL che recuperi è HTTPS. I proxy HTTPS-over-HTTPS esistono ma sono rari e devono essere esplicitamente supportati dal gateway. Requests formula lo stesso fallimento in modo molto più utile — il tuo proxy sembra usare solo HTTP e non HTTPS — motivo per cui una configurazione identica può sembrare un bug specifico di curl_cffi. Le versioni recenti avvertono e collegano l'issue #6, ma è solo un avvertimento: la richiesta fallisce comunque.
Autenticazione: credenziali URL o proxy_auth
I gateway autenticati accettano la solita forma incorporata, http://USER:PASS@host:port, con la solita trappola: un @, : o / non scappato nella password divide l'URL nel posto sbagliato e produce un fallimento di autenticazione che sembra un proxy morto. curl_cffi offre una via di fuga che requests non ha — una tupla proxy_auth passata a libcurl come opzioni separate di nome utente e password, quindi non è coinvolta alcuna codifica.
import curl_cffi
from urllib.parse import quote
# Option A — credentials in the URL, password URL-encoded
pw = quote("p@ss:word", safe="")
r = curl_cffi.get(
"https://httpbin.org/ip",
proxy=f"http://USER:{pw}@gate.quantumproxies.io:PORT",
impersonate="chrome",
timeout=30,
)
# Option B — keep credentials out of the URL entirely
r = curl_cffi.get(
"https://httpbin.org/ip",
proxy="http://gate.quantumproxies.io:PORT",
proxy_auth=("USER", "PASS"),
impersonate="chrome",
timeout=30,
)
print(r.json())
Una terza opzione elimina completamente questa classe di bug: la whitelist degli IP. Ogni piano residenziale QuantumProxies ti consente di autorizzare l'IP del tuo server invece di inviare user:pass, quindi l'URL del proxy diventa un semplice http://gate.quantumproxies.io:PORT — niente da codificare, nessun segreto nel tuo albero delle sorgenti. Se le credenziali stesse vengono rifiutate, la nostra guida a ogni causa del 407 Proxy Authentication Required copre il resto.

Sessioni, cookie e il dettaglio del riutilizzo delle credenziali
Una Session mantiene cookie, pooling delle connessioni e i tuoi valori predefiniti in un unico posto, che è ciò che vuoi per qualsiasi cosa multi-step. Imposta impersonate e proxy una volta e ogni richiesta li eredita:
from curl_cffi import Session
with Session(
impersonate="chrome",
proxy="http://USER-session-a1b2:PASS@gate.quantumproxies.io:PORT",
timeout=30,
retry=3,
) as s:
s.get("https://httpbin.org/cookies/set/foo/bar")
r = s.get("https://httpbin.org/cookies")
print(r.json(), s.cookies.get_dict())
Due comportamenti meritano una menzione. Primo, ogni volta che un proxy è configurato, curl_cffi attiva l'opzione proxy-credential-no-reuse di libcurl: una nuova connessione è forzata quando cambia il nome utente del proxy, e la cache della sessione TLS è chiave sull'indirizzo del proxy, quindi un precedente IP di uscita non può trapelare in una richiesta successiva attraverso una sessione riutilizzata. Se codifichi ID di sessioni sticky nel nome utente, come fanno la maggior parte dei gateway rotanti, ottieni quell'isolamento gratuitamente. Secondo, retry (un int, o una RetryStrategy da curl_cffi.requests con delay, backoff e jitter) viene eseguito solo su un'eccezione di trasporto. Non riprova un 403 o 429 come fa il status_forcelist di urllib3 — quel ciclo è ancora tuo da scrivere. I documenti di compatibilità elencano i retry come non supportati, il che è obsoleto: il parametro è stato introdotto nella v0.15.0.
Punta curl_cffi a un gateway residenziale rotante
Rotazione per richiesta e async
curl_cffi pubblicizza asyncio con rotazione del proxy su ogni richiesta, e questo è letterale: un argomento proxy= su una chiamata individuale sovrascrive ciò che la sessione contiene. Raramente hai bisogno di una lista di proxy per sfruttarlo — un gateway rotante assegna un nuovo server di uscita lato server su ogni connessione, quindi un endpoint più concorrenza è già rotazione. Dove vuoi controllo (un IP stabile per lavoratore, per account, per carrello) metti un token di sessione nel nome utente e lascia che il gateway fissi quell'uscita.
import asyncio
from curl_cffi import AsyncSession
GATE = "gate.quantumproxies.io:PORT"
URLS = ["https://httpbin.org/ip"] * 20
async def fetch(session, url, worker):
# one sticky exit IP per worker; drop the -session- suffix for full rotation
proxy = f"http://USER-session-{worker}:PASS@{GATE}"
r = await session.get(url, proxy=proxy, timeout=30)
return r.status_code, r.json()["origin"]
async def main():
async with AsyncSession(impersonate="chrome", max_clients=10) as s:
return await asyncio.gather(
*(fetch(s, u, i % 5) for i, u in enumerate(URLS))
)
for status, ip in asyncio.run(main()):
print(status, ip)
max_clients limita i gestori curl concorrenti nel pool (10 di default), quindi è il tuo vero controllo di concorrenza — aggiungere un semaforo a un gather illimitato è l'errore usuale. La stessa logica di dimensionamento si applica a qualsiasi client async, che abbiamo trattato in scraping Python async con httpx e aiohttp. Se ruotare per richiesta o fissare una sessione dipende dal fatto che il sito tracci lo stato tra le richieste; i compromessi sono in sessioni sticky vs proxy rotanti.

SOCKS5, HTTP/3 e gli interruttori di sicurezza
SOCKS non necessita di installazioni aggiuntive — libcurl è compilato, quindi a differenza di requests non c'è nessun extra [socks] da ricordare. Usa socks5h://USER:PASS@gate.quantumproxies.io:PORT: la h spinge la risoluzione DNS al proxy, che impedisce perdite dalla tua rete e risolve i nomi host geo-recintati dalla posizione dell'uscita. curl_cffi rileva il prefisso socks e salta il flag di tunneling HTTP, poiché il protocollo SOCKS gestisce questo da solo. Ogni piano qui espone endpoint HTTP e SOCKS5 sullo stesso gateway, quindi il passaggio è uno scambio di schema piuttosto che un nuovo ordine.
- HTTP/3 su un proxy è arrivato nella v0.15.0 insieme alle impronte digitali http/3, ma necessita di un server SOCKS5 che parli UDP, non di un gateway HTTP semplice. Niche fino a quando il tuo target non premia QUIC.
- Indurimento SSRF. La stessa release ha portato un avviso: se recuperi URL forniti da altre persone, i reindirizzamenti possono essere camminati nella tua rete interna. Imposta
allow_redirects="safe", o disattiva i reindirizzamenti. - Debugging. La v0.15 ha spedito una CLI:
curl-cffi get tls.browserleaks.com/json --impersonate chrometi dice in una riga se l'impersonificazione sta atterrando, prima di incolpare il proxy.
Quando curl_cffi più un proxy sono sufficienti
Più spesso di quanto le persone si aspettino. Se il target serve JSON da un'API interna o HTML reso dal server, e l'unico ostacolo è un controllo delle impronte digitali, una stretta di mano abbinata più un'uscita residenziale lo risolvono a una frazione del costo e della latenza di un browser. Le FAQ del progetto sono schiette sul limite: le impronte digitali sono un fattore tra molti, insieme alla qualità dell'IP, al tasso di richieste e ai controlli JavaScript, e i livelli di protezione più alti necessitano sia di un pool di proxy migliore che di una vera automazione del browser. Quando l'impersonificazione è configurata correttamente e sei ancora bloccato, la variabile rimanente è quasi sempre l'IP di uscita — isolare questo in cinque minuti è l'argomento di curl_cffi vs requests. Se preferisci non eseguire nessuno dei due, l'Scraper API gestisce impronte digitali, proxy e rendering JS opzionale dietro una chiamata.
Domande frequenti
Come uso un proxy con curl_cffi?
Passa proxy="http://USER:PASS@host:port" a qualsiasi metodo di richiesta, sessione o sessione asincrona. Anche il dizionario in stile requests proxies={"http": ..., "https": ...} funziona, ma il progetto raccomanda il singolo parametro a meno che tu non abbia bisogno di proxy diversi per schema. Passare entrambi genera un TypeError.
Perché curl_cffi genera ErrCode 35 WRONG_VERSION_NUMBER?
Perché l'URL del proxy inizia con https://. Un proxy standard si aspetta una richiesta CONNECT in testo semplice e poi incanala il tuo TLS; un prefisso https:// fa sì che curl tenti di fare una stretta di mano TLS con il proxy stesso, che risponde in HTTP semplice. Cambia il valore in http:// — la chiave https si riferisce al target, non al passaggio.
curl_cffi supporta i proxy SOCKS5?
Sì, nativamente — libcurl è incluso, quindi non c'è nessun extra opzionale da installare. Usa lo schema socks5h:// in modo che i nomi host siano risolti dal proxy piuttosto che dalla tua macchina. SOCKS4, SOCKS4a e il semplice socks5:// sono anche accettati; la libreria salta il tunneling HTTP per qualsiasi proxy il cui schema inizi con socks.
curl_cffi può ruotare i proxy su ogni richiesta?
Sì. Un argomento proxy= su una chiamata individuale sovrascrive il default della sessione, anche all'interno di un AsyncSession, che è ciò che il README intende per asyncio con rotazione per richiesta. Con un gateway rotante spesso non hai bisogno di alcuna logica: lo stesso endpoint distribuisce un IP di uscita diverso per connessione.
curl_cffi può bypassare Cloudflare?
A volte. Rimuove l'indicazione delle impronte digitali TLS e HTTP/2, che è sufficiente per i livelli di protezione di base. Non può eseguire sfide JavaScript, risolvere Turnstile o riparare un IP di datacenter che un database di reputazione ha già segnalato. Considera l'impersonificazione come uno dei tre requisiti, non la risposta.
L'intera configurazione è più piccola della sua reputazione: una stringa proxy, impersonate impostato una volta sulla sessione, un timeout su ogni chiamata, e credenziali o codificate nell'URL o passate come una tupla proxy_auth. Ottieni questi giusti e la variabile rimanente è la qualità dell'IP — una stretta di mano Chrome perfetta da un indirizzo di datacenter segnalato è ancora un indirizzo di datacenter segnalato. La nostra analisi di JA3 e JA4 fingerprinting spiega perché i due controlli sono indipendenti.
Ottieni IP residenziali che corrispondono alla tua impersonificazione