Przewodnik po proxy curl_cffi: konfiguracja, uwierzytelnianie, rotacja i asynchroniczność
curl_cffi zapewnia TLS handshake przypominający przeglądarkę. Proxy daje czysty adres IP wyjściowy. Oto dokładnie, jak połączyć te dwa elementy — i dlaczego prefiks https:// w twoim słowniku proxy powoduje ErrCode 35.
curl_cffi to powiązanie Pythona z forkiem curl-impersonate: odtwarza prawdziwe odciski palców TLS/JA3 i HTTP/2 przeglądarki zamiast ogłaszać się jako urllib3. To rozwiązuje jeden z osi blokowania. Drugi to adres IP wyjściowy, gdzie wchodzi proxy curl_cffi — i gdzie dokumentacja jest skąpa. Oficjalna sekcja proxy ma około piętnastu linii, a problem na GitHubie z lutego 2023 nadal jest w pierwszej piątce dla tego tematu. Ten przewodnik obejmuje całą powierzchnię: parametr proxy, słownik w stylu requests i jego prawdziwe nazwy kluczy, proxy_auth, sesje, rotacja na żądanie, asynchroniczność, SOCKS5 — i dokładne komunikaty o błędach, które wkleisz do wyszukiwarki.
Składnia proxy curl_cffi: preferuj proxy= zamiast słownika proxies
curl_cffi akceptuje dwie formy. Rodzima to pojedynczy ciąg proxy=, dodany w wersji v0.6.0; słownik proxies= istnieje dla zgodności z requests, a dokumentacja zaleca pojedynczy parametr, chyba że naprawdę potrzebujesz różnych proxy dla różnych schematów. Wewnętrznie są one zredukowane do tego samego — proxy="..." staje się {"all": "..."} — i oba działają na pomocnikach modułu, na Session, na AsyncSession i na pojedynczych żądaniach.
# 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>'}
Cztery rzeczy o tym słowniku są warte poznania, ponieważ żadna z nich nie jest oczywista z README:
- Poprawne klucze to
all,http,https,wsiwss.allto klucz ogólny; klucze websocket mają znaczenie tylko dla klienta WebSocket. - Klucze per-host również działają.
https://api.example.comluball://example.comkieruje tylko ten host przez dane proxy — przydatne do wysyłania jednej trudnej domeny przez IP rezydencjalne i pozostawienia reszty bezpośrednio. - Nie możesz przekazać obu.
proxy=plusproxies=w tym samym wywołaniu powodujeTypeError: Cannot specify both 'proxy' and 'proxies', a to samo sprawdzenie działa na poziomie sesji. - Zmienne środowiskowe są honorowane —
http_proxy,https_proxy,ws_proxy,wss_proxy. Przekażtrust_env=FalsedoSession, gdy zmienna korporacyjna przejmuje twojego scraper'a.
Jedna uwaga na temat importów: od wersji v0.10.0 pakiet jest wywoływalny bezpośrednio (curl_cffi.get, curl_cffi.Session). Starsze samouczki używają from curl_cffi import requests, co nadal działa, ale wygląda źle obok prawdziwej biblioteki requests — i wyjaśnia, dlaczego połowa fragmentów online wygląda jak inny projekt.
Pułapka https://: ErrCode 35 i WRONG_VERSION_NUMBER
Ten pojedynczy błąd generuje więcej pytań o proxy curl_cffi niż wszystko inne razem wzięte. Problem #6 w trackerze projektu — otwarty i zamknięty tego samego dnia w lutym 2023 — nadal jest na pierwszej stronie, ponieważ błąd, który generuje, wygląda jak błąd TLS, a nie literówka w konfiguracji:
# 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"
Klucz nazywa protokół celu; wartość nazywa, jak dotrzeć do proxy. Normalne proxy HTTPS-over-HTTP przyjmuje zwykły tekst CONNECT, a następnie tuneluje twoje zaszyfrowane dane bez zmian — więc URL proxy zaczyna się od http://, nawet gdy każdy URL, który pobierasz, jest HTTPS. Proxy HTTPS-over-HTTPS istnieją, ale są rzadkie i muszą być explicite wspierane przez bramę. Requests formułuje tę samą porażkę znacznie bardziej pomocnie — twoje proxy wydaje się używać tylko HTTP, a nie HTTPS — co jest powodem, dla którego identyczna konfiguracja może wyglądać jak błąd specyficzny dla curl_cffi. Nowsze wersje ostrzegają i linkują do problemu #6, ale to tylko ostrzeżenie: żądanie nadal się nie powiedzie.
Uwierzytelnianie: poświadczenia URL lub proxy_auth
Bramy uwierzytelnione akceptują zwykłą formę osadzoną, http://USER:PASS@host:port, z typowym haczykiem: niezakodowany @, : lub / w haśle dzieli URL w złym miejscu i powoduje błąd uwierzytelniania, który wygląda jak martwe proxy. curl_cffi oferuje wyjście, którego requests nie ma — krotkę proxy_auth przekazywaną do libcurl jako oddzielne opcje nazwy użytkownika i hasła, więc nie jest wymagane żadne kodowanie.
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())
Trzecia opcja całkowicie eliminuje tę klasę błędów: biała lista IP. Każdy plan rezydencjalny QuantumProxies pozwala autoryzować IP twojego serwera zamiast wysyłania user:pass, więc URL proxy staje się gołym http://gate.quantumproxies.io:PORT — nic do kodowania, żadnych tajemnic w twoim drzewie źródłowym. Jeśli same poświadczenia są odrzucane, nasz przewodnik po wszystkich przyczynach 407 Proxy Authentication Required obejmuje resztę.

Sesje, ciasteczka i szczegóły ponownego użycia poświadczeń
Session przechowuje ciasteczka, pooling połączeń i twoje domyślne ustawienia w jednym miejscu, co jest tym, czego potrzebujesz do wszystkiego, co ma wiele kroków. Ustaw impersonate i proxy raz, a każde żądanie je dziedziczy:
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())
Dwa zachowania zasługują na uwagę. Po pierwsze, za każdym razem, gdy skonfigurowane jest proxy, curl_cffi włącza opcję proxy-credential-no-reuse libcurl: nowe połączenie jest wymuszane, gdy zmienia się nazwa użytkownika proxy, a pamięć podręczna sesji TLS jest kluczem na adresie proxy, więc poprzedni adres IP wyjściowy nie może przeciekać do późniejszego żądania przez ponownie używaną sesję. Jeśli kodujesz identyfikatory sesji sticky w nazwie użytkownika, jak robi to większość bram rotacyjnych, otrzymujesz tę izolację za darmo. Po drugie, retry (liczba całkowita lub RetryStrategy z curl_cffi.requests z opóźnieniem, backoff i jitter) ponownie uruchamia się tylko w przypadku wyjątku transportowego. Nie próbuje ponownie 403 lub 429, jak robi to status_forcelist urllib3 — ta pętla nadal jest twoja do napisania. Dokumentacja zgodności wymienia retry jako nieobsługiwane, co jest nieaktualne: parametr pojawił się w wersji v0.15.0.
Skieruj curl_cffi na rotacyjną bramę rezydencjalną
Rotacja na żądanie i asynchroniczność
curl_cffi reklamuje asyncio z rotacją proxy przy każdym żądaniu, i to dosłownie: argument proxy= w pojedynczym wywołaniu nadpisuje to, co trzyma sesja. Rzadko potrzebujesz listy proxy, aby to wykorzystać — rotacyjna brama przypisuje świeży serwer wyjściowy po stronie serwera przy każdym połączeniu, więc jeden punkt końcowy plus współbieżność to już rotacja. Gdzie chcesz kontroli (jeden stabilny IP na pracownika, na konto, na koszyk) umieść token sesji w nazwie użytkownika i pozwól bramie przypiąć ten wyjście.
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 ogranicza równoczesne uchwyty curl w puli (domyślnie 10), więc jest to twój prawdziwy regulator współbieżności — dodanie semafora do nieograniczonego zbierania to zwykły błąd. Ta sama logika rozmiaru dotyczy każdego klienta asynchronicznego, co omówiliśmy w asynchronicznym skrobaniu w Pythonie z httpx i aiohttp. Czy rotować na żądanie, czy przypiąć sesję, zależy od tego, czy strona śledzi stan między żądaniami; kompromisy są w sticky sessions vs rotating proxies.

SOCKS5, HTTP/3 i przełączniki bezpieczeństwa
SOCKS nie wymaga dodatkowej instalacji — libcurl jest skompilowany, więc w przeciwieństwie do requests nie ma [socks] extra do zapamiętania. Użyj socks5h://USER:PASS@gate.quantumproxies.io:PORT: h przenosi rozwiązywanie DNS do proxy, co zapobiega wyciekom z twojej własnej sieci i rozwiązuje geo-fenced hostnames z lokalizacji wyjścia. curl_cffi wykrywa prefiks socks i pomija flagę tunelowania HTTP, ponieważ protokół SOCKS to obsługuje sam. Każdy plan tutaj udostępnia punkty końcowe HTTP i SOCKS5 na tej samej bramie, więc przełączanie to zamiana schematu, a nie nowe zamówienie.
- HTTP/3 przez proxy pojawił się w wersji v0.15.0 wraz z odciskami palców http/3, ale potrzebuje serwera SOCKS5, który obsługuje UDP, a nie zwykłą bramę HTTP. Niszowe, dopóki twój cel nie nagradza QUIC.
- Wzmocnienie SSRF. Ta sama wersja zawierała ostrzeżenie: jeśli pobierasz URL-e dostarczone przez inne osoby, przekierowania mogą być wprowadzone do twojej wewnętrznej sieci. Ustaw
allow_redirects="safe", lub wyłącz przekierowania. - Debugowanie. Wersja v0.15 wprowadziła CLI:
curl-cffi get tls.browserleaks.com/json --impersonate chromemówi ci w jednej linii, czy naśladowanie ląduje, zanim obwinisz proxy.
Kiedy curl_cffi plus proxy wystarcza
Częściej niż ludzie się spodziewają. Jeśli cel serwuje JSON z wewnętrznego API lub renderowane na serwerze HTML, a jedyną przeszkodą jest kontrola odcisków palców, dopasowany handshake plus wyjście rezydencjalne rozwiązuje to za ułamek kosztów i opóźnień przeglądarki. FAQ projektu jest bezpośrednie co do sufitu: odciski palców to jeden z kilku czynników, obok jakości IP, szybkości żądań i kontroli JavaScript, a wyższe poziomy ochrony wymagają zarówno lepszej puli proxy, jak i prawdziwej automatyzacji przeglądarki. Gdy naśladowanie jest poprawnie skonfigurowane i nadal jesteś blokowany, pozostałą zmienną jest prawie zawsze adres IP wyjściowy — izolowanie tego w pięć minut jest tematem curl_cffi vs requests. Jeśli wolisz nie uruchamiać żadnego z nich, Scraper API obsługuje odciski palców, proxy i opcjonalne renderowanie JS w jednym wywołaniu.
Najczęściej zadawane pytania
Jak używać proxy z curl_cffi?
Przekaż proxy="http://USER:PASS@host:port" do dowolnej metody żądania, sesji lub sesji asynchronicznej. Słownik w stylu requests proxies={"http": ..., "https": ...} również działa, ale projekt zaleca pojedynczy parametr, chyba że potrzebujesz różnych proxy dla różnych schematów. Przekazanie obu powoduje TypeError.
Dlaczego curl_cffi wyrzuca ErrCode 35 WRONG_VERSION_NUMBER?
Ponieważ URL proxy zaczyna się od https://. Standardowe proxy oczekuje zwykłego tekstu CONNECT, a następnie tuneluje twoje TLS; prefiks https:// sprawia, że curl próbuje TLS-handshake z samym proxy, które odpowiada w zwykłym HTTP. Zmień wartość na http:// — klucz https odnosi się do celu, a nie do przeskoku.
Czy curl_cffi obsługuje proxy SOCKS5?
Tak, natywnie — libcurl jest dołączony, więc nie ma opcjonalnego dodatku do zainstalowania. Użyj schematu socks5h://, aby nazwy hostów były rozwiązywane przez proxy, a nie przez twoją maszynę. SOCKS4, SOCKS4a i zwykłe socks5:// są również akceptowane; biblioteka pomija tunelowanie HTTP dla każdego proxy, którego schemat zaczyna się od socks.
Czy curl_cffi może rotować proxy przy każdym żądaniu?
Tak. Argument proxy= w pojedynczym wywołaniu nadpisuje domyślną sesję, w tym wewnątrz AsyncSession, co oznacza README przez asyncio z rotacją na żądanie. Z rotacyjną bramą często nie potrzebujesz żadnej logiki: ten sam punkt końcowy przydziela inny adres IP wyjściowy na połączenie.
Czy curl_cffi może ominąć Cloudflare?
Czasami. Usuwa odciski palców TLS i HTTP/2, co wystarcza na podstawowe poziomy ochrony. Nie może wykonać wyzwań JavaScript, rozwiązać Turnstile ani naprawić adresu IP centrum danych, który został już oznaczony przez bazę reputacji. Traktuj naśladowanie jako jeden z trzech wymagań, a nie jako odpowiedź.
Cała konfiguracja jest mniejsza niż jej reputacja: jeden ciąg proxy, impersonate ustawiony raz na sesji, timeout na każdym wywołaniu, a poświadczenia albo zakodowane w URL, albo przekazane jako krotka proxy_auth. Ustaw to poprawnie, a pozostałą zmienną jest jakość IP — idealny handshake Chrome z oznaczonego adresu centrum danych to nadal oznaczony adres centrum danych. Nasze omówienie odcisków palców JA3 i JA4 wyjaśnia, dlaczego te dwa sprawdzenia są niezależne.
Zdobądź rezydencjalne IP, które pasują do twojego naśladowania