Вы купили резидентные прокси, поставили свежий User-Agent Chrome, а сайт всё равно отдаёт 403 на первом же запросе. Знакомо? Проблема не в IP и не в заголовках. Вас вычислили ещё до того, как сервер прочитал хотя бы один HTTP-заголовок — по TLS-рукопожатию. В 2026 году это вектор детекта №1, и обычный requests проваливает его автоматически. Разбираем, как это работает и как чинится за пару строк кода через curl_cffi.
Что происходит: вас палит TLS-рукопожатие
Когда клиент устанавливает HTTPS-соединение, он первым делом отправляет пакет ClientHello — ещё до всякого HTTP. В нём перечислены: версия TLS, список поддерживаемых шифронаборов (cipher suites), TLS-расширения (SNI, ALPN, supported_groups), эллиптические кривые и форматы точек. Порядок и состав этих полей у разных клиентов разный — и по нему клиента можно опознать до того, как он вымолвит хоть слово.
Из этих полей считают отпечаток. JA3 (стандарт 2017 года) берёт строку вида TLSVersion,Ciphers,Extensions,EllipticCurves,ECPointFormats и хэширует её MD5, получая 32-символьную сигнатуру. Проблема JA3 в том, что с января 2023 Chrome рандомизирует порядок расширений — 16 расширений дают 16! (более 20 триллионов) вариантов, и один и тот же браузер выдаёт разные JA3.
Поэтому индустрия перешла на JA4 (FoxIO, массовое внедрение 2024–2025). JA4 сортирует коды расширений по hex-значению перед хэшированием — рандомизация Chrome его больше не ломает. Хэш — усечённый SHA-256, формат человекочитаемый и трёхчастный (a_b_c), в него включены ALPN и поддержка QUIC/HTTP3. Пример: Chrome 124 даёт t13d1516h2 (15 шифров, 16 расширений, ALPN h2), а голый Python requests — t13d1715h2. Для анти-бота вторая сигнатура — прямой маркер «это скрипт».
Почему в 2026 без этого никуда
JA4-детект вшит во всех крупных вендоров: Cloudflare сверяет отпечаток против allowlist'ов, Akamai добавляет отдельный хэш HTTP/2 SETTINGS-фреймов, DataDome сравнивает с базой известных ботов. Логика простая и убийственная: если вы шлёте User-Agent: Chrome 131, а TLS-отпечаток кричит «urllib3/OpenSSL» — это рассинхрон, и вас блокируют мгновенно. Никакие прокси не спасают: идеальный резидентный IP с отпечатком Python requests всё равно проигрывает.
Ровно поэтому связка «прокси + подделка отпечатка» стала в 2026 базовой гигиеной скрапинга, а не опцией для продвинутых.
Решение: curl_cffi за 5 минут
curl_cffi — это Python-обёртка над curl-impersonate (модифицированный curl, собранный с BoringSSL от Chrome или NSS от Firefox вместо OpenSSL). Она воспроизводит подлинное браузерное рукопожатие и при этом имеет API, почти идентичный привычному requests.
Шаг 1. Установка. Бинарники curl-impersonate под Windows/macOS/Linux подтягиваются автоматически:
pip install curl-cffi
Шаг 2. Базовый запрос. Меняете импорт и добавляете один параметр:
from curl_cffi import requests
resp = requests.get("https://target.com/", impersonate="chrome")
print(resp.status_code)
print(resp.http_version) # HTTP/2 — как у настоящего браузера
Одна строка impersonate="chrome" подделывает сразу четыре слоя: TLS-отпечаток (JA3/JA4), версию HTTP (HTTP/2 вместо HTTP/1.1), порядок заголовков и ALPN-переговоры.
Шаг 3. Всегда используйте generic-алиас, а не пин версии. Пишите impersonate="chrome" (или "safari", "safari_ios") — алиас автоматически резолвится в самый свежий профиль. Захардкоженный impersonate="chrome124" протухнет: Chrome обновляется каждые ~4 недели, и старый профиль сам станет аномалией. Надёжные таргеты — Chrome, Edge и Safari/iOS (профили от chrome99 до chrome131, safari15–18).
Шаг 4. Прокси и сессии. Для реального скрапинга держите состояние в сессии и цепляйте прокси. Резидентный или мобильный IP здесь обязателен — датацентр палится по ASN отдельно от TLS:
from curl_cffi import requests
session = requests.Session(impersonate="chrome")
headers = {
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br",
"Referer": "https://www.google.com/",
}
proxies = {
"http": "http://user:pass@proxy-host:port",
"https": "http://user:pass@proxy-host:port",
}
resp = session.get("https://target.com", headers=headers, proxies=proxies)
Шаг 5. Асинхронность для объёма. В отличие от requests, у curl_cffi из коробки есть async и HTTP/2:
import asyncio
from curl_cffi.requests import AsyncSession
async def fetch(session, url):
r = await session.get(url, impersonate="chrome")
return r.status_code
async def main(urls):
async with AsyncSession() as session:
return await asyncio.gather(*[fetch(session, u) for u in urls])
asyncio.run(main(["https://target.com"] * 20))
Проверьте свой отпечаток — не гадайте
Прежде чем гнать боевой трафик, убедитесь, что подмена реально работает. Отправьте запрос на публичные верификаторы и сравните JA4 с эталонным браузерным:
- tls.peet.ws — возвращает JA3, JA4, Akamai-отпечаток и HTTP/2-фреймы в JSON. Запросите его через
curl_cffiи через реальный Chrome, сверьте хэши. - ja4db.com — база известных JA4, помогает понять, на кого вы похожи.
- browserleaks.com/tls и инструмент JA3/JA4 от Scrapfly — детальная разбивка полей.
В стейджинге удобно ставить mitmproxy между скрапером и целью и мониторить фактический JA4-хэш каждого запроса.
Диагностика: всё ещё ловите 403/429
Если отпечаток корректный, а блокировки остались — идите по чек-листу от частого к редкому:
- Датацентровый IP. Причина №1. Переходите на резидентные прокси или мобильные — почему одного отпечатка мало, подробно разобрано в материале про детект резидентных прокси через IP Intelligence.
- Протухший профиль.
pip install -U curl-cffiи generic-алиас"chrome". - Слишком высокий rate. Добавьте случайные паузы 1–3 секунды между запросами.
- Голые заголовки. Обязательно шлите
Accept-Language,Accept-Encoding,Referer— их отсутствие тоже аномалия. - Рассинхрон сессии и IP. Правило: одна сессия — один IP на всё её время жизни.
- Статус 200 ≠ успех. Проверяйте тело ответа: под кодом 200 может лежать страница с CAPTCHA.
Где curl_cffi упирается в стену
curl_cffi закрывает сетевой слой — и точка. Он не исполняет JavaScript. Поэтому против JS-челленджей он бессилен: Cloudflare Turnstile, страница «Checking your browser…» (IUAM), кука cf_clearance, которую ставит скрипт после проверки, — всё это требует настоящего браузерного окружения. Почему в 2026 солверы капчи почти перестали работать против таких превентивных систем, мы разбирали в отдельном разборе обхода CAPTCHA.
Что делать, когда упёрлись в JS-стену:
- Гибрид. Playwright или Nodriver прогоняет челлендж и получает
cf_clearance, затем куку передают в быстрыйcurl_cffiдля основной массы запросов — так вы платите за тяжёлый браузер один раз. - Solver-сервисы (CapSolver, 2Captcha) для автоматической выдачи токенов.
- Managed-скрапинг API, если не хотите держать инфраструктуру.
И помните про потокобезопасность: каждому потоку — своя сессия. Зафиксируйте версию curl-cffi в requirements.txt и ревизуйте профили раз в 6–12 недель, когда браузеры обновятся.
Альтернативы curl_cffi
- tls-client — обёртка над Go-библиотекой на базе uTLS, с профилями (
chrome_124,safari_ios_17) и флагомrandom_tls_extension_order=True. Гибкая тонкая настройка отпечатка. - primp — клиент на Rust, позволяет независимо задавать
impersonate_osи даёт выше пропускную способность; минус — API не полностью совпадает сrequestsи библиотека моложе.
Какой прокси нужен и почему
Подделка отпечатка и прокси решают разные половины одной задачи: curl_cffi закрывает вопрос «как выглядит соединение», прокси — «откуда оно идёт». Анти-бот проверяет оба сигнала независимо, поэтому идеальный JA4 с чёрным датацентр-ASN бесполезен. Для защищённых целей (маркетплейсы, соцсети, тревел-агрегаторы) берите резидентные или мобильные прокси: у них чистое операторское происхождение, а мобильные ещё и прячутся за CGNAT «эффектом толпы». Датацентр оставьте для нечувствительных целей и высокого объёма.
Вывод
В 2026 скрапинг — это игра идентичностей, а не только IP. Голый requests представляется скриптом на уровне TLS-рукопожатия и проигрывает ещё до первого заголовка. Замена импорта на curl_cffi с impersonate="chrome" убирает этот провал за пять минут, но работает только в связке с чистым резидентным или мобильным IP и с пониманием границы: сетевой слой — да, JavaScript-челленджи — нет. Собирайте стек честно: правильный отпечаток, правильный прокси, гибрид с браузером там, где стоит JS-стена — и 403 на первом запросе останется в прошлом.
