Twoje zapytania HTTP są odrzucane przez Cloudflare, Akamai lub DataDome, mimo że wysyłasz poprawne nagłówki, realistycznego User-Agent i rotujesz adresy IP. Problemem nie są nagłówki — to Twój uścisk TLS. Impersonacja TLS z curl_cffi to technika, która pozwala Pythonowi wysyłać ClientHello identyczny z przeglądarką Chrome, omijając zaawansowane systemy anti-bot. W tym artykule rozkładamy na czynniki pierwsze, jak działają odciski JA3/JA4, jak curl_cffi replikuje stos kryptograficzny Chrome i dlaczego samej impersonacji nie wystarczy bez residential proxy.
Impersonacja TLS z curl_cffi: dlaczego Python zdradza swój odcisk
Kiedy Python requests lub urllib3 nawiązuje połączenie HTTPS, biblioteka TLS (zazwyczaj OpenSSL) wysyła wiadomość ClientHello jako pierwszy pakiet w uzgadnianiu TLS, zgodnie ze specyfikacją RFC 8446. Ten pakiet zawiera uporządkowaną listę szyfrów (cipher suites), rozszerzeń, krzywych eliptycznych i metod kompresji. Systemy anti-bot jak Cloudflare Bot Manager czy Akamai Bot Manager analizują ten pakiet i obliczają skrót — znany jako JA3 — który jednoznacznie identyfikuje stos TLS klienta.
Problem polega na tym, że OpenSSL — używany domyślnie przez Python requests — generuje ClientHello, który wygląda niczym typowy klient Pythona, a nie przeglądarka. Oto dlaczego:
- Kolejność szyfrów: OpenSSL sortuje cipher suites według własnych priorytetów, które różnią się od kolejności w Chrome. Na przykład OpenSSL może umieścić
TLS_AES_256_GCM_SHA384przedTLS_CHACHA20_POLY1305_SHA256, podczas gdy Chrome robi odwrotnie. - Brak GREASE: Chrome dodaje wartości GREASE (Generate Random Extensions And Sustain Extensibility) — sztuczne, losowe pozycje w liście szyfrów i rozszerzeń, które zapobiegają twardemu kodowaniu odcisków. OpenSSL nie generuje wartości GREASE, więc ich brak jest natychmiast wykrywany.
- Lista rozszerzeń: Chrome wysyła około 17 rozszerzeń w określonej kolejności, w tym
encrypted_client_hello,delegated_credentials,application_settings. OpenSSL wysyła inną, krótszą listę w innej kolejności. - Krzywe eliptyczne: Chrome obsługuje grupy takie jak
x25519_kyber768(hybrydowy post-kwantowy), których OpenSSL w standardowej konfiguracji nie reklamuje. - Kształt ClientHello w TLS 1.3: W TLS 1.3 ClientHello zawiera
key_sharez konkretnymi parametrami. Chrome wysyła dokładnie jedną parę kluczy dla x25519 lub P-256, podczas gdy OpenSSL może wysyłać inne kombinacje.
Wynik: JA3 dla typowego klienta Python requests to hash typu 19e29534fd98591e7d3c7c2d7c0e2b5, podczas gdy Chrome 120 ma zupełnie inną wartość. System anti-bot widzi ten hash i natychmiast wie: to nie jest przeglądarka.
JA3 vs JA4: dlaczego kolejność ma znaczenie
JA3 oblicza skrót MD5 z konkatenacji wersji TLS, cipher suites, rozszerzeń, krzywych eliptycznych i formatów podpisów — w dokładnej kolejności występowania. To oznacza, że każda zmiana kolejności rozszerzeń generuje inny hash. Chrome 110+ wprowadził permutację ClientHello — losowe przestawianie rozszerzeń przy każdym połączeniu — co sprawia, że JA3 dla tej samej wersji Chrome zwraca różne hashe przy każdym uruchomieniu.
JA4 został zaprojektowany jako order-stable — sortuje składniki przed hashowaniem, więc ten sam klient zawsze generuje ten sam JA4, niezależnie od permutacji. Format JA4 wygląda tak: t13d1516h2_8daaf6152771_b186095e22b6, gdzie t13 to TLS 1.3, d15 to 15 rozszerzeń, 16h2 to 16 cipher suites z ALPN h2, a pozostałe segmenty to skróty SHA256 z posortowanych wartości.
Kluczowa różnica: JA3 traktuje kolejność jako część odcisku, JA4 sortuje przed hashowaniem. To oznacza, że anti-bot musi teraz polegać na JA4, aby stabilnie identyfikować klientów, ale może nadal używać JA3 do wykrywania nietypowych permutacji.
Jak curl_cffi replikuje odcisk Chrome: BoringSSL, ustawienia i presets
curl-impersonate to projekt, który modyfikuje libcurl, zastępując OpenSSL BoringSSL — implementacją TLS używaną przez Chrome — i konfiguruje go tak, aby generował identyczny ClientHello jak prawdziwa przeglądarka. curl_cffi to powiązanie Pythona dla tej zmodyfikowanej biblioteki, udostępniające API podobne do requests i httpx.
BoringSSL: dlaczego to ma znaczenie
BoringSSL to fork OpenSSL stworzony i utrzymywany przez Google. Chrome używa go od 2014 roku. Kluczowa różnica polega na tym, że BoringSSL implementuje specyficzne dla Chrome zachowania TLS, których OpenSSL nie ma:
- Wbudowana obsługa GREASE: BoringSSL automatycznie wstawia wartości GREASE w losowych pozycjach listy szyfrów i rozszerzeń, dokładnie tak jak Chrome.
- Kolejność cipher suites zgodna z Chrome: BoringSSL sortuje szyfry w kolejności preferencji Chrome, a nie OpenSSL.
- Pełna lista rozszerzeń: BoringSSL wysyła wszystkie rozszerzenia, które Chrome wysyła, w tej samej kolejności — w tym
encrypted_client_hello,delegated_credentials,application_settings,renegotiation_info. - HTTP/2 SETTINGS frame: Po nawiązaniu TLS, Chrome wysyła ramkę SETTINGS z konkretnymi parametrami HTTP/2.
curl_cffireplikuje te wartości, w tymHEADER_TABLE_SIZE=65536,INITIAL_WINDOW_SIZE=6291456iMAX_CONCURRENT_STREAMS=1000. Te wartości są unikalne dla Chrome i są sprawdzane przez zaawansowane systemy anti-bot.
Presets impersonate="chrome" i nadpisywania
Podstawowe użycie curl_cffi jest proste — wystarczy ustawić parametr impersonate:
from curl_cffi import requests
response = requests.get(
"https://example.com",
impersonate="chrome",
)
print(response.status_code)
Parametr impersonate akceptuje wartości takie jak "chrome", "safari", "edge", a także konkretne wersje: "chrome110", "chrome116", "chrome120", "chrome124". Każdy preset konfiguruje BoringSSL z dokładnym zestawem cipher suites, rozszerzeń i parametrów HTTP/2 odpowiadających danej wersji przeglądarki.
Dla zaawansowanego sterowania, curl_cffi udostępnia parametry ja3, akamai i extra_fp:
ja3: Pozwala ręcznie ustawić string JA3, nadpisując domyślny preset. Format:"771,4865-4866-4867-49195-49199...,0-23-65281-10-11-35-16-5-13-18-51-45-43-10-11,...,29-23-24".akamai: Nadpisuje ramkę HTTP/2 SETTINGS. Format to lista par klucz-wartość, np."1:65536;2:0;3:6291456;4:6291456;6:262144;7:0".extra_fp: Pozwala ustawić dodatkowe parametry odcisku, w tymtls_signature_algorithms,tls_supported_curves,http2_pseudo_header_orderihttp2_window_update.
Chrome 110+ i permutacja rozszerzeń
Od wersji Chrome 110, Google wprowadził permutację rozszerzeń ClientHello — mechanizm, który losowo przestawia kolejność rozszerzeń TLS przy każdym nowym połączeniu. Cel był szlachetny: zapobiec twardemu kodowaniu odcisków JA3 przez pośredników (middleboxes). Efektem ubocznym było to, że systemy anti-bot oparte na JA3 stały się mniej wiarygodne — ten sam Chrome generował różne hashe JA3.
curl_cffi obsługuje ten mechanizm poprzez włączenie permutacji w BoringSSL, gdy używasz presetu chrome110 lub nowszego. Oznacza to, że każde żądanie może mieć inny JA3, ale JA4 pozostaje stabilny, ponieważ sortuje składniki przed hashowaniem.
Dla scraping engineerów to oznacza ważną rzecz: jeśli system anti-bot używa JA4 (a coraz więcej systemów to robi), musisz dopasować nie tylko cipher suites i rozszerzenia, ale też ich posortowaną postać. curl_cffi robi to automatycznie w presetach.
Dlaczego sama impersonacja TLS nie wystarczy: residential proxy jest obowiązkowe
Nawet jeśli Twój ClientHello jest identyczny z Chrome 120, a ramka HTTP/2 SETTINGS ma dokładne wartości, system anti-bot sprawdzi jeszcze jedną rzecz: reputację adresu IP. Datacenter IP — niezależnie od tego, jak dobrze dopasowany jest Twój stos TLS — jest natychmiast flagowany jako podejrzany.
Oto dlaczego:
- ASN reputation: Systemy anti-bot utrzymują bazy danych ASN (Autonomous System Numbers). Jeśli Twój IP należy do ASN zarejestrowanego jako hosting/datacenter (np. AWS, DigitalOcean, OVH), trafisz na listę podejrzanych jeszcze przed wysłaniem ClientHello.
- Historyczny ruch: Datacenter IP mają niski współczynnik organic traffic — większość ruchu z tych IP to boty, nie ludzie. Systemy scoringowe uczą się na podstawie wzorców ruchu.
- Geograficzna spójność: Jeśli Twój User-Agent twierdzi, że jesteś w Niemczech, ale Twój IP jest w datacenter w USA, system anti-bot notuje niespójność.
Badania pokazują, że datacenter proxy mają wskaźnik blokad sięgający 40-60% na stronach chronionych przez Cloudflare Bot Management, podczas gdy residential proxy osiągają wskaźnik sukcesu 85-95% przy tym samym poziomie impersonacji TLS. Różnica jest drastyczna.
Residential proxy używają adresów IP przydzielonych przez prawdziwych dostawców ISP (np. Deutsche Telekom, Comcast, Orange) do prawdziwych gospodarstw domowych. System anti-bot widzi taki IP jako zwykły użytkownik domowy i obniża wynik ryzyka. Połączenie residential IP + impersonacja TLS to złoty standard.
| Metoda | Odcisk JA3/JA4 | HTTP/2 SETTINGS | Reputacja IP | Wydajność | Złożoność |
|---|---|---|---|---|---|
| Python requests | Odrzucany | Brak HTTP/2 | Niska (datacenter) | d>SzybkaNiska | |
| curl_cffi + datacenter proxy | Identyczny z Chrome | Dopasowany | Niska (datacenter) | Szybka | Niska |
| curl_cffi + residential proxy | Identyczny z Chrome | Dopasowany | Wysoka (ISP) | Szybka | Średnia |
| Playwright + residential proxy | Prawdziwy (przeglądarka) | Prawdziwy | Wysoka (ISP) | Wolna (~500ms/strona) | Wysoka |
Praktyczna implementacja: curl_cffi AsyncSession z ProxyHat
Poniżej znajduje się kompletny, uruchamialny przykład łączący curl_cffi z residential proxy ProxyHat. Pokazujemy dwa podejścia: bezpośrednie użycie AsyncSession oraz integrację z ProxyHat SDK dla rotacji i ponownych prób.
Przykład 1: curl_cffi AsyncSession z ProxyHat residential proxy
import asyncio
from curl_cffi.requests import AsyncSession
async def scrape_with_proxyhat():
# ProxyHat residential proxy — Niemcy
proxy_url = "http://user-country-DE:password@gate.proxyhat.com:8080"
async with AsyncSession(impersonate="chrome120") as session:
response = await session.get(
"https://httpbin.org/headers",
proxies={"https": proxy_url, "http": proxy_url},
timeout=30,
)
print(f"Status: {response.status_code}")
print(f"Body: {response.text[:500]}")
asyncio.run(scrape_with_proxyhat())
Przykład 2: Rotacja IP i ponowne próby z ProxyHat
import asyncio
from curl_cffi.requests import AsyncSession
import random
import string
def random_session_id():
return ''.join(random.choices(string.ascii_lowercase + string.digits, k=8))
async def scrape_with_rotation():
targets = [
"https://httpbin.org/ip",
"https://httpbin.org/headers",
"https://httpbin.org/user-agent",
]
async with AsyncSession(impersonate="chrome120") as session:
for url in targets:
# Kazde zadanie otrzymuje nowa sesje = nowy IP
session_id = random_session_id()
proxy_url = f"http://user-session-{session_id}-country-DE:password@gate.proxyhat.com:8080"
for attempt in range(3):
try:
response = await session.get(
url,
proxies={"https": proxy_url, "http": proxy_url},
timeout=30,
)
if response.status_code == 200:
print(f"[{url}] OK — {response.json()}")
break
elif response.status_code == 403:
print(f"[{url}] 403 — ponowna proba z nowym IP")
session_id = random_session_id()
proxy_url = f"http://user-session-{session_id}-country-DE:password@gate.proxyhat.com:8080"
else:
print(f"[{url}] {response.status_code}")
break
except Exception as e:
print(f"[{url}] Blad: {e} — ponowna proba")
await asyncio.sleep(2)
asyncio.run(scrape_with_rotation())
Przykład 3: SOCKS5 z ProxyHat i curl_cffi
import asyncio
from curl_cffi.requests import AsyncSession
async def scrape_socks5():
# ProxyHat SOCKS5 — port 1080
proxy_url = "socks5://user-country-DE:password@gate.proxyhat.com:1080"
async with AsyncSession(impersonate="chrome124") as session:
response = await session.get(
"https://httpbin.org/ip",
proxies={"https": proxy_url, "http": proxy_url},
timeout=30,
)
print(f"IP wyjsciowe: {response.json()['origin']}")
asyncio.run(scrape_socks5())
Wskazówki konfiguracyjne
- Wybór presetu: Używaj najnowszego dostępnego presetu (
chrome124,chrome120), aby dopasować się do najczęściej spotykanej wersji Chrome. Starsze presety (chrome110) mogą być wykrywane jako nietypowe. - Geo-targeting: Używaj flagi
-country-XXw nazwie użytkownika, aby dopasować IP do języka strony i nagłówkaAccept-Language. Sprawdź dostępne lokalizacje na stronie lokalizacji ProxyHat. - Sticky sessions: Flaga
-session-XXXutrzymuje ten sam IP przez wiele żądań — przydatne dla logowania i sesji koszyka. - Limit współbieżności: Nie wysyłaj więcej niż 50-100 współbieżnych żądań na jedną sesję proxy. Rozładuj ruch na wiele sesji.
- Spójność nagłówków: Upewnij się, że nagłówki HTTP pasują do presetu impersonacji. Chrome wysyła nagłówki w określonej kolejności (najpierw
:method,:authority,:scheme,:pathw HTTP/2).
Limity i etyka: czego curl_cffi NIE rozwiąże
curl_cffi rozwiązuje problem odcisku TLS, ale nie rozwiązuje wszystkiego. Oto granice, o których musisz wiedzieć:
Wyzwania JavaScript, których nie rozwiążesz
curl_cffi nie wykonuje JavaScript. Jeśli strona wymaga rozwiązania challenge JS — np. obliczenia tokenu Cloudflare Turnstile, wykonania skryptu Akamai Sensor, czy rozwiązywania CAPTCHA — curl_cffi zwróci stronę challenge, a nie treść docelową. W takich przypadkach musisz użyć prawdziwej przeglądarki:
- Playwright/Puppeteer z residential proxy — pełna przeglądarka, wykonuje JS, ale wolniejsza (~500ms na stronę).
- Playwright Stealth — modyfikacje API przeglądarki ukrywające atrybuty
navigator.webdriver,window.chrome, canvas fingerprint.
Strategia hybrydowa: używaj curl_cffi dla stron bez JS challenge (większość API, statyczne HTML, SERP), a Playwright z residential proxy dla stron wymagających wykonania JS. Więcej o praktycznych zastosowaniach znajdziesz w naszych materiałach o web scraping i SERP tracking.
Canvas fingerprint i behavioral analytics
Nawet z perfekcyjnym odciskiem TLS, systemy anti-bot mogą wykrywać boty na podstawie dodatkowych sygnałów:
- Canvas fingerprint: Unikalny hash generowany z renderowania Canvas — niemożliwy do sfałszowania w
curl_cffi(brak silnika renderującego). - WebGL fingerprint: Informacje o karcie graficznej i sterownikach.
- Behavioral analytics: Czas między żądaniami, ruch myszy, wzorce scrollowania. Ruch botów jest zbyt regularny — interwały dokładnie co 1000ms są natychmiast wykrywane.
- JA4 fingerprint: Nawet z
curl_cffi, jeśli Twoja konfiguracja nie jest idealnie dopasowana, JA4 może się różnić od prawdziwego Chrome.
Kwestie prawne i etyczne
Impersonacja TLS jest narzędziem neutralnym — może służyć do legalnego zbierania publicznych danych lub do nadużyć. Kilka zasad, o których musisz pamiętać:
- Computer Fraud and Abuse Act (CFAA): W USA, dostęp do stron internetowych w sposób naruszający ich Terms of Service może być interpretowany jako naruszenie CFAA. Sprawa Van Buren v. United States (2021) zawęziła interpretację, ale ryzyko pozostaje.
- GDPR: W UE, zbieranie danych osobowych z publicznych stron wymaga podstawy prawnej (Art. 6 GDPR). Pseudonimizacja IP jest wymagana.
- robots.txt: Zawsze sprawdzaj i respektuj dyrektywy robots.txt — to minimalny standard etyczny.
- Rate limiting: Utrzymuj rozsądne tempo żądań. 1-2 żądania na sekundę na domenę to bezpieczny próg dla większości stron.
Pełną dokumentację ProxyHat znajdziesz na docs.proxyhat.com.
Kluczowe wnioski
Impersonacja TLS z curl_cffi rozwiązuje problem odcisku TLS, ale jest tylko jednym elementem układanki. Oto co musisz zapamiętać:
- JA3/JA4 to pierwszy filtr: Systemy anti-bot odrzucają klientów z nietypowym odciskiem TLS zanim jeszcze sprawdzią nagłówki.
curl_cffizimpersonate="chrome120"rozwiązuje ten problem. - BoringSSL to klucz:
curl_cffiużywa BoringSSL (implementacji TLS Chrome), nie OpenSSL. To zapewnia identyczny ClientHello, w tym GREASE, kolejność szyfrów i rozszerzeń. - JA4 jest order-stable: Chrome 110+ permutuje rozszerzenia, ale JA4 sortuje przed hashowaniem — musisz dopasować posortowaną postać, nie tylko listę.
- Residential proxy jest obowiązkowe: Perfekcyjny odcisk TLS nad datacenter IP to nadal podejrzany ruch. Połącz
curl_cffiz residential proxy ProxyHat przezgate.proxyhat.com:8080. - curl_cffi nie rozwiązuje JS challenge: Dla stron wymagających wykonania JavaScript użyj Playwright z residential proxy.
- Etyka i prawo: Używaj tylko do legalnego dostępu do publicznych danych. Respektuj robots.txt, GDPR i CFAA.
Gotowy, aby zacząć? Sprawdź ceny ProxyHat i wybierz pakiet residential proxy, który pasuje do Twojego obciążenia.






