curl_cffi Proxy Gids: Installatie, Auth, Rotatie en Async
curl_cffi biedt je een TLS-handshake zoals een browser. Een proxy geeft je een schoon exit-IP. Hier is precies hoe je de twee samenvoegt — en waarom het https://-voorvoegsel in je proxies dict ErrCode 35 veroorzaakt.
curl_cffi is de Python-binding voor een fork van curl-impersonate: het reproduceert de TLS/JA3- en HTTP/2-vingerafdrukken van een echte browser in plaats van zichzelf aan te kondigen als urllib3. Dat lost één as van blokkering op. De andere is het exit-IP, en daar komt een curl_cffi proxy in beeld — en waar de documentatie dun wordt. De officiële proxysectie beslaat ongeveer vijftien regels, en een GitHub-issue van februari 2023 staat nog steeds in de top vijf voor dit onderwerp. Deze gids behandelt het hele oppervlak: de proxy parameter, de requests-stijl dict en zijn echte sleutelnamen, proxy_auth, sessies, rotatie per verzoek, async, SOCKS5 — en de exacte foutstrings die je in een zoekvak zult plakken.
curl_cffi proxy syntaxis: geef de voorkeur aan proxy= boven de proxies dict
curl_cffi accepteert twee vormen. De native vorm is een enkele proxy= string, toegevoegd in v0.6.0; de proxies= dict bestaat voor requests-compatibiliteit, en de documentatie raadt de enkele parameter aan tenzij je echt verschillende proxies per schema nodig hebt. Intern vallen ze samen tot hetzelfde — proxy="..." wordt {"all": "..."} — en beide werken op de modulehelpers, op Session, op AsyncSession en op individuele verzoeken.
# 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>'}
Vier dingen over die dict zijn het weten waard, omdat geen ervan duidelijk is uit de README:
- De geldige sleutels zijn
all,http,https,wsenwss.allis de catch-all; de websocketsleutels zijn alleen van belang voor de WebSocket-client. - Per-host sleutels werken ook.
https://api.example.comofall://example.comrouteert alleen die host via een gegeven proxy — handig om één moeilijk domein via residentiële IP's te sturen en de rest direct te laten. - Je kunt niet beide doorgeven.
proxy=plusproxies=in dezelfde oproep veroorzaaktTypeError: Cannot specify both 'proxy' and 'proxies', en dezelfde controle wordt uitgevoerd op sessieniveau. - Omgevingsvariabelen worden gerespecteerd —
http_proxy,https_proxy,ws_proxy,wss_proxy. Geeftrust_env=Falsedoor aan eenSessionwanneer een bedrijfsvariabele je scraper kaapt.
Een opmerking over imports: sinds v0.10.0 is het pakket direct aanroepbaar (curl_cffi.get, curl_cffi.Session). Oudere tutorials gebruiken from curl_cffi import requests, wat nog steeds werkt maar slecht leest naast de echte requests-bibliotheek — en verklaart waarom de helft van de online snippets eruitziet als een ander project.
De https://-valkuil: ErrCode 35 en WRONG_VERSION_NUMBER
Deze ene fout genereert meer curl_cffi proxyvragen dan alles samen. Issue #6 in de projecttracker — geopend en gesloten op dezelfde dag in februari 2023 — staat nog steeds op pagina één, omdat de fout die het produceert leest als een TLS-bug in plaats van een configuratiefout:
# 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"
De sleutel benoemt het protocol van het doel; de waarde benoemt hoe je de proxy bereikt. Een normale HTTPS-over-HTTP proxy neemt een platte-tekst CONNECT, en tunnel je versleutelde verkeer erdoorheen zonder aan te raken — dus de proxy-URL begint met http:// zelfs als elke URL die je ophaalt HTTPS is. HTTPS-over-HTTPS proxies bestaan maar zijn zeldzaam en moeten expliciet worden ondersteund door de gateway. Requests formuleert dezelfde fout veel behulpzamer — je proxy lijkt alleen HTTP te gebruiken en niet HTTPS — wat de reden is dat een identieke configuratie eruit kan zien als een curl_cffi-specifieke bug. Recente versies waarschuwen en linken naar issue #6, maar het is slechts een waarschuwing: het verzoek mislukt nog steeds.
Authenticatie: URL-credentials of proxy_auth
Geauthenticeerde gateways accepteren de gebruikelijke ingebedde vorm, http://USER:PASS@host:port, met de gebruikelijke valkuil: een niet-geëscapede @, : of / in het wachtwoord splitst de URL op de verkeerde plaats en produceert een auth-fout die eruitziet als een dode proxy. curl_cffi biedt een ontsnappingsmogelijkheid die requests niet heeft — een proxy_auth tuple die aan libcurl wordt doorgegeven als afzonderlijke gebruikersnaam- en wachtwoordopties, zodat er helemaal geen codering bij betrokken is.
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())
Een derde optie elimineert deze klasse van bugs volledig: IP-whitelisting. Elk QuantumProxies residentieel plan laat je het IP van je server autoriseren in plaats van user:pass te verzenden, zodat de proxy-URL een kale http://gate.quantumproxies.io:PORT wordt — niets om te coderen, geen geheim in je broncode. Als de credentials zelf worden afgewezen, behandelt onze gids voor elke oorzaak van 407 Proxy Authentication Required de rest.

Sessies, cookies en het detail van hergebruik van credentials
Een Session houdt cookies, connectiepooling en je standaardinstellingen op één plek, wat je wilt voor alles met meerdere stappen. Stel impersonate en proxy eenmaal in en elk verzoek erft ze:
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())
Twee gedragingen verdienen een vermelding. Ten eerste, wanneer een proxy is geconfigureerd, schakelt curl_cffi libcurl's proxy-credential-no-reuse optie in: een nieuwe verbinding wordt geforceerd wanneer de proxygebruikersnaam verandert, en de TLS-sessiecache is gebaseerd op het proxyadres, zodat een eerder exit-IP niet kan lekken in een later verzoek via een hergebruikte sessie. Als je sticky-session ID's codeert in de gebruikersnaam, zoals de meeste roterende gateways doen, krijg je die isolatie gratis. Ten tweede, retry (een int, of een RetryStrategy van curl_cffi.requests met vertraging, backoff en jitter) wordt alleen opnieuw uitgevoerd bij een transportuitzondering. Het doet geen retry van een 403 of 429 zoals urllib3's status_forcelist doet — die lus moet je zelf schrijven. De compatibiliteitsdocumentatie vermeldt retries als niet ondersteund, wat verouderd is: de parameter kwam in v0.15.0.
Richt curl_cffi op een roterende residentiële gateway
Rotatie per verzoek en async
curl_cffi adverteert asyncio met proxyrotatie bij elk verzoek, en dat is letterlijk: een proxy= argument bij een individuele oproep overschrijft wat de sessie vasthoudt. Je hebt zelden een proxy-lijst nodig om het te benutten — een roterende gateway wijst bij elke verbinding een nieuwe exit server-side toe, dus één eindpunt plus gelijktijdigheid is al rotatie. Waar je controle wilt (één stabiel IP per werker, per account, per winkelwagen) plaats je een sessietoken in de gebruikersnaam en laat je de gateway die exit vastpinnen.
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 beperkt de gelijktijdige curl-handles in de pool (standaard 10), dus het is je echte gelijktijdigheidsknop — een semafoor op een onbeperkte gather monteren is de gebruikelijke fout. Dezelfde dimensioneringslogica geldt voor elke async-client, die we behandelden in async Python scraping met httpx en aiohttp. Of je per verzoek moet roteren of een sessie moet vastpinnen, hangt af van of de site status bijhoudt over verzoeken heen; de afwegingen staan in sticky sessions vs rotating proxies.

SOCKS5, HTTP/3 en de veiligheidschakelaars
SOCKS heeft geen extra installatie nodig — libcurl is ingebouwd, dus in tegenstelling tot requests is er geen [socks] extra om te onthouden. Gebruik socks5h://USER:PASS@gate.quantumproxies.io:PORT: de h duwt DNS-resolutie naar de proxy, wat lekken van je eigen netwerk stopt en geo-afgeschermde hostnamen vanaf de exitlocatie oplost. curl_cffi detecteert het socks voorvoegsel en slaat de HTTP-tunneling vlag over, aangezien het SOCKS-protocol dat zelf afhandelt. Elk plan hier biedt HTTP en SOCKS5 eindpunten op dezelfde gateway, dus overschakelen is een schema-swap in plaats van een nieuwe bestelling.
- HTTP/3 over een proxy kwam in v0.15.0 naast http/3-vingerafdrukken, maar het heeft een SOCKS5-server nodig die UDP spreekt, niet een gewone HTTP-gateway. Niche totdat je doel QUIC beloont.
- SSRF-verharding. Dezelfde release bevatte een advies: als je URL's ophaalt die door anderen zijn geleverd, kunnen redirects in je interne netwerk worden gelopen. Stel
allow_redirects="safe"in, of schakel redirects uit. - Debuggen. v0.15 leverde een CLI:
curl-cffi get tls.browserleaks.com/json --impersonate chromevertelt je in één regel of nabootsing landt, voordat je de proxy de schuld geeft.
Wanneer curl_cffi plus een proxy genoeg is
Vaker dan mensen verwachten. Als het doel JSON serveert vanuit een interne API of server-gerenderde HTML, en het enige obstakel is een vingerafdrukcontrole, dan lost een gematchte handshake plus een residentieel exit het op tegen een fractie van de kosten en latentie van een browser. De FAQ van het project is duidelijk over het plafond: vingerafdrukken zijn een factor onder meerdere, naast IP-kwaliteit, verzoekfrequentie en JavaScript-controles, en hogere beschermingsniveaus vereisen zowel een betere proxypool als echte browserautomatisering. Wanneer nabootsing correct is geconfigureerd en je nog steeds wordt geblokkeerd, is de resterende variabele bijna altijd het exit-IP — dat isoleren in vijf minuten is het onderwerp van curl_cffi vs requests. Als je liever geen van beide uitvoert, handelt de Scraper API vingerafdrukken, proxies en optionele JS-rendering af achter één oproep.
Veelgestelde vragen
Hoe gebruik ik een proxy met curl_cffi?
Geef proxy="http://USER:PASS@host:port" door aan elke verzoekmethode, sessie of asynchrone sessie. De requests-stijl proxies={"http": ..., "https": ...} dict werkt ook, maar het project raadt de enkele parameter aan tenzij je verschillende proxies per schema nodig hebt. Het doorgeven van beide veroorzaakt een TypeError.
Waarom geeft curl_cffi ErrCode 35 WRONG_VERSION_NUMBER?
Omdat de proxy-URL begint met https://. Een standaard proxy verwacht een platte-tekst CONNECT-verzoek en tunnel je TLS erdoorheen; een https:// voorvoegsel laat curl proberen om TLS-handshake met de proxy zelf te doen, die in platte HTTP antwoordt. Verander de waarde naar http:// — de https sleutel verwijst naar het doel, niet naar de sprong.
Ondersteunt curl_cffi SOCKS5 proxies?
Ja, native — libcurl is gebundeld, dus er is geen optionele extra om te installeren. Gebruik het socks5h:// schema zodat hostnamen worden opgelost door de proxy in plaats van je machine. SOCKS4, SOCKS4a en gewone socks5:// worden ook geaccepteerd; de bibliotheek slaat HTTP-tunneling over voor elke proxy waarvan het schema begint met socks.
Kan curl_cffi proxies roteren bij elk verzoek?
Ja. Een proxy= argument bij een individuele oproep overschrijft de standaard van de sessie, ook binnen een AsyncSession, wat de README bedoelt met asyncio met rotatie per verzoek. Met een roterende gateway heb je vaak helemaal geen logica nodig: hetzelfde eindpunt geeft een ander exit-IP per verbinding.
Kan curl_cffi Cloudflare omzeilen?
Soms. Het verwijdert de TLS- en HTTP/2-vingerafdrukken, wat genoeg is voor basisbeschermingsniveaus. Het kan geen JavaScript-uitdagingen uitvoeren, Turnstile oplossen, of een datacenter-IP repareren dat al door een reputatiedatabase is gemarkeerd. Behandel nabootsing als een van de drie vereisten, niet als het antwoord.
De hele configuratie is kleiner dan zijn reputatie: één proxy string, impersonate eenmaal ingesteld op de sessie, een timeout bij elke oproep, en credentials ofwel URL-gecodeerd of doorgegeven als een proxy_auth tuple. Krijg die goed en de resterende variabele is IP-kwaliteit — een perfecte Chrome-handshake van een gemarkeerd datacenteradres is nog steeds een gemarkeerd datacenteradres. Onze uiteenzetting van JA3 en JA4 vingerafdrukken legt uit waarom de twee controles onafhankelijk zijn.