curl_cffi vs requests: Cosa Risolve e Cosa Non Può

Sostituire requests con curl_cffi trasforma molti 403 in 200. Non fa nulla per un IP bruciato. Ecco la linea di demarcazione tra i due, e un test di quindici minuti che ti dice da quale parte si trova il tuo problema.

La questione curl_cffi vs requests di solito si presenta nel bel mezzo di un incidente: uno scraper che ha funzionato per mesi inizia a restituire 403 alla prima chiamata, qualcuno su Reddit dice di cambiare client, e funziona. Questo è un effetto reale con una spiegazione reale — ma il modo in cui viene ripetuto ("usa solo curl_cffi") nasconde sia cosa sta succedendo sia dove smette di aiutare. requests non è lento o scritto male. Ha esattamente un solo svantaggio in un contesto di scraping, è grande, e non ha nulla a che fare con l'API che digiti. Ecco dove i due client differiscono realmente, cosa cambia quando si passa da uno all'altro, e l'unica cosa che l'impersonificazione non risolverà mai, indipendentemente dalla libreria che scegli.

L'unica differenza che conta

Entrambe le librerie inviano gli stessi header. La differenza è a un livello più basso, nella stretta di mano TLS che apre la connessione prima che venga trasferito un singolo byte HTTP. requests si basa su urllib3 e OpenSSL, che pubblicizzano una lista di cifrari, un set di estensioni e un ordinamento che appartengono a Python e nient'altro. curl_cffi è un binding a un fork patchato di curl che riproduce il ClientHello di un browser byte per byte, insieme al suo frame HTTP/2 SETTINGS — quindi i suoi hash JA3, JA3N e Akamai corrispondono a quelli di un vero Chrome piuttosto che a una libreria di scripting. I fornitori di anti-bot mantengono database di queste firme; una discrepanza tra un header User-Agent di Chrome e una stretta di mano Python è una contraddizione da cui non puoi uscire. Abbiamo scomposto i meccanismi in JA3 e JA4 fingerprinting, e lo stesso effetto spiega perché curl ottiene un 403 dove il tuo browser ottiene un 200 su un URL identico.

Tutto il resto nel confronto deriva dall'implementazione. Poiché curl_cffi avvolge libcurl, eredita HTTP/2, HTTP/3, websockets e asyncio, nessuno dei quali requests ha mai supportato. Poiché requests è puro Python, si installa su qualsiasi cosa e ha un decennio di ecosistema alle spalle. Entrambe le affermazioni sono vere allo stesso tempo, e quale prevale dipende interamente dal tuo obiettivo.

Un test riproducibile che puoi eseguire in cinque minuti

Non fidarti ciecamente di nessuna tabella di tassi di successo, compresa la nostra. La differenza di fingerprint è direttamente osservabile: punta entrambi i client a un endpoint TLS-echo e confronta gli hash che riportano indietro. Se le due linee corrispondono, la tua build non sta impersonificando nulla.

# pip install requests curl_cffi
import requests
import curl_cffi

URL = "https://tls.browserleaks.com/json"

a = requests.get(URL, timeout=30).json()
b = curl_cffi.get(URL, impersonate="chrome", timeout=30).json()

print("requests   ja3n:", a["ja3n_hash"], "| akamai:", a.get("akamai_hash", "-"))
print("curl_cffi  ja3n:", b["ja3n_hash"], "| akamai:", b.get("akamai_hash", "-"))

# Two different ja3n hashes = impersonation is working. The curl_cffi README
# documents aa56c057ad164ec4fdcb7a5a283be9fc as a Chrome-matched ja3n value.
# The akamai (HTTP/2) fingerprint is usually empty for requests: it is
# HTTP/1.1 only, so there is no SETTINGS frame to fingerprint in the first place.

Dalla versione v0.15 c'è una versione in una sola riga dello stesso controllo: curl-cffi get tls.browserleaks.com/json --impersonate chrome. Eseguilo prima di eseguire il debug di qualsiasi altra cosa — separa "la mia impersonificazione è configurata male" da "la mia impersonificazione è a posto e qualcos'altro mi sta bloccando", che sono due pomeriggi completamente diversi.

Confronto fianco a fianco di requests e curl_cffi su supporto protocollo, concorrenza, fingerprinting e portabilità
requests vince in portabilità ed ecosistema; curl_cffi vince su protocolli e fingerprint. Nessuno vince sulla qualità degli IP — non è una caratteristica del client.

Cosa curl_cffi non risolve: reputazione IP

Ecco la parte che il consiglio di cambiare libreria omette. Un fingerprint TLS risponde alla domanda "che software è questo?". Non dice nulla su "da dove proviene?" — e quella seconda domanda viene risolta da una ricerca separata contro il tuo IP di uscita: quale ASN lo possiede, è un provider di hosting o un ISP consumer, è apparso nei feed di abuso, quante altre sessioni hanno colpito questo sito dallo stesso indirizzo nell'ultima ora. Una stretta di mano Chrome impeccabile proveniente da una VM cloud in un range di datacenter è un browser Chrome che apparentemente è stato installato in un rack server. Non è più convincente di python-requests. In alcuni casi è meno, perché la contraddizione è più netta.

Le FAQ del progetto mettono la qualità degli IP al primo posto nella lista dei fattori, davanti al tasso di richieste e ai fingerprint JavaScript, quando spiegano perché l'impersonificazione da sola potrebbe non essere sufficiente. Quell'ordine non è un caso: la reputazione è il segnale più economico da valutare per un difensore e il più difficile da falsificare per un attaccante, perché a differenza di un header o di una lista di cifrari non puoi generarla localmente. Se il tuo scraper basato su requests stava già funzionando attraverso un pool di datacenter e veniva bloccato, passare a curl_cffi sullo stesso pool cambia uno dei due controlli falliti. Vedrai un miglioramento parziale su obiettivi morbidi e nessun miglioramento su quelli duri — che è esattamente il risultato confuso che le persone riportano.

La soluzione per quell'asse è la qualità dell'indirizzo, non il codice: IP residenziali da allocazioni ISP consumer reali, che è ciò che 90M+ indirizzi in oltre 200 paesi ti comprano. Se vuoi controllare come appare la tua uscita attuale prima di cambiare qualcosa, il nostro controllo qualità IP gratuito riporta l'ASN e la classificazione che un obiettivo vedrebbe.

Il 2x2 che ti dice quale asse è rotto

Piuttosto che indovinare, testa entrambe le variabili indipendentemente contro il tuo vero obiettivo. Quattro richieste, quattro righe di output, e il risultato nomina il tuo problema:

import requests
import curl_cffi

TARGET = "https://your-target.example/api/items"
DC  = "http://USER:PASS@your-datacenter-gateway:PORT"
RES = "http://USER:PASS@gate.quantumproxies.io:PORT"

def probe(label, fn):
    try:
        print(f"{label:26} -> {fn().status_code}")
    except Exception as e:
        print(f"{label:26} -> {type(e).__name__}")

probe("requests  + datacenter",
      lambda: requests.get(TARGET, proxies={"https": DC}, timeout=30))
probe("curl_cffi + datacenter",
      lambda: curl_cffi.get(TARGET, proxy=DC, impersonate="chrome", timeout=30))
probe("requests  + residential",
      lambda: requests.get(TARGET, proxies={"https": RES}, timeout=30))
probe("curl_cffi + residential",
      lambda: curl_cffi.get(TARGET, proxy=RES, impersonate="chrome", timeout=30))

Leggi i quattro risultati come una tabella di verità:

Eseguilo alcune decine di volte piuttosto che una sola. Entrambi i livelli di blocco sono probabilistici, e un singolo 200 ti dice quasi nulla.

Testa la riga residenziale con IP domestici reali

Diagramma che mostra una stretta di mano TLS abbinata al browser da un IP di datacenter bloccato mentre la stessa stretta di mano da un IP residenziale passa
Impersonificazione e reputazione IP sono cancelli separati. Risolverne uno e lasciare l'altro è il motivo per cui 'ho cambiato a curl_cffi e non è cambiato nulla' è un rapporto così comune.

Quando la dipendenza extra non vale la pena

La sincerità è più economica di una riscrittura. Rimani su requests quando:

E c'è un percorso intermedio che la maggior parte delle persone manca: non devi abbandonare requests per ottenere la stretta di mano. I manutentori indicano curl-adapter, che monta curl_cffi come adattatore di trasporto requests, e httpx-curl-cffi su PyPI, che fa lo stesso per httpx. Mantieni il tuo codice ed ecosistema esistenti, e solo i byte sul filo cambiano.

Problemi di migrazione da conoscere prima

L'API è abbastanza vicina che la maggior parte degli script funziona dopo aver cambiato l'importazione, ma la pagina di compatibilità elenca differenze reali e vale la pena leggerle prima di un grande port. I corpi di risposta ai reindirizzamenti non vengono conservati in Response.history. I cookie con domini vuoti possono essere persi attraverso i reindirizzamenti. Gli oggetti di risposta in streaming non possono essere serializzati, sebbene le risposte normali possano. L'API dei file differisce leggermente. E non ci sono trasporti o adattatori, perché la libreria è deliberatamente saldata a libcurl-impersonate. Anche la configurazione del proxy differisce in un piccolo modo che inganna le persone:

# requests: dict, and retries mounted on an adapter
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

s = requests.Session()
s.mount("https://", HTTPAdapter(max_retries=Retry(total=4, status_forcelist=[429, 503])))
r = s.get(url, proxies={"https": "http://USER:PASS@gate.quantumproxies.io:PORT"}, timeout=30)

# curl_cffi: single proxy string preferred, retries are a session parameter
from curl_cffi import Session
from curl_cffi.requests import RetryStrategy

s = Session(
    impersonate="chrome",
    proxy="http://USER:PASS@gate.quantumproxies.io:PORT",
    retry=RetryStrategy(count=4, delay=1.0, backoff="exponential"),
    timeout=30,
)
r = s.get(url)  # note: retry fires on transport errors, not on 429/503

L'intera superficie del proxy — chiavi dict, proxy_auth, rotazione per richiesta, async e il prefisso https:// che produce un errore WRONG_VERSION_NUMBER poco utile — è coperta passo dopo passo nella nostra guida al proxy curl_cffi. Se rimani fermo, il riferimento equivalente per l'altro lato è la nostra guida al proxy Python Requests.

Domande frequenti

curl_cffi è più veloce di requests?

Sì, e i benchmark del progetto lo mettono alla pari con aiohttp e pycurl piuttosto che con requests. Il guadagno deriva da libcurl che esegue il lavoro in C oltre al multiplexing HTTP/2, non da un Python intelligente. Per una manciata di chiamate sequenziali la differenza è invisibile; ad alta concorrenza, specialmente con async, è sostanziale.

curl_cffi è sicuro da usare?

È concesso in licenza MIT, ampiamente distribuito e fornisce ruote precompilate, quindi non c'è un passaggio di build da controllare. Vale la pena agire su un avviso: un avviso v0.15.0 copre SSRF basato su reindirizzamento. Se recuperi URL forniti da altre persone, imposta allow_redirects="safe" o disabilita i reindirizzamenti. Impersonare un browser è una misura tecnica, non un permesso per ignorare i termini di un sito.

curl_cffi aggira Cloudflare?

Rimuove l'indicatore di fingerprint TLS e HTTP/2, che elimina i livelli di protezione di base. Non può eseguire una sfida JavaScript, risolvere Turnstile, o correggere un IP di uscita segnalato. I manutentori dicono tanto nelle loro FAQ, e raccomandano un pool di proxy migliore più l'automazione del browser per i livelli superiori.

curl_cffi vs httpx o tls_client — quale dovrei usare?

httpx ti offre HTTP/2 e async ma nessuna impersonificazione di fingerprint, quindi si colloca tra requests e curl_cffi in termini di stealth. tls_client imita anche i profili TLS e ha benchmark simili; curl_cffi ha la comunità più grande e aggiunge HTTP/3 e websockets. Se httpx è già nel tuo stack, il trasporto httpx-curl-cffi ti offre impersonificazione senza una riscrittura.

La versione breve: passa a curl_cffi quando il tuo obiettivo legge le strette di mano, rimani su requests quando non lo fa, e non aspettarti mai che una delle due scelte pulisca un IP di datacenter. I client differiscono su un asse, i proxy su un altro, e gli scraper bloccati sono quasi sempre una storia su entrambi. Esegui le quattro sonde, leggi la tabella di verità, e correggi l'asse a cui i dati puntano invece di quello di cui internet ha parlato.

Correggi l'asse che l'impersonificazione non può raggiungere