curl_cffi против requests: что исправляет и что не может

Замена requests на curl_cffi превращает множество 403 в 200. Это также не помогает при заблокированном IP. Вот граница между ними и пятнадцатиминутный тест, который покажет, на какой стороне ваша проблема.

Вопрос curl_cffi против requests обычно возникает в разгар инцидента: скрейпер, который работал месяцами, начинает возвращать 403 на первом вызове, кто-то на Reddit говорит заменить клиент, и это работает. Это реальный эффект с реальным объяснением — но то, как это повторяется ("просто используйте curl_cffi"), скрывает как то, что происходит, так и где это перестает помогать. requests не медленный и не плохо написан. У него есть ровно один недостаток в контексте скрейпинга, он значительный, и он не имеет ничего общего с API, который вы вводите. Здесь клиенты действительно различаются, что меняется при переключении, и одна вещь, которую имитация никогда не исправит, независимо от того, какую библиотеку вы выберете.

Единственное различие, которое имеет значение

Обе библиотеки отправляют одинаковые заголовки. Различие в одном уровне ниже, в TLS рукопожатии, которое открывает соединение до того, как передается первый байт HTTP. requests использует urllib3 и OpenSSL, которые рекламируют список шифров, набор расширений и порядок, принадлежащие Python и ничему другому. curl_cffi — это привязка к исправленной версии curl, которая воспроизводит ClientHello браузера байт за байтом, вместе с его HTTP/2 SETTINGS кадром — так что его JA3, JA3N и Akamai хэши совпадают с настоящим Chrome, а не с библиотекой скриптов. Поставщики антиботов ведут базы данных этих подписей; несоответствие между заголовком User-Agent Chrome и рукопожатием Python — это противоречие, которое невозможно объяснить. Мы разобрали механику в JA3 и JA4 отпечатках, и тот же эффект объясняет, почему curl получает 403, где ваш браузер получает 200 на идентичном URL.

Все остальное в сравнении следует из реализации. Поскольку curl_cffi оборачивает libcurl, он наследует HTTP/2, HTTP/3, вебсокеты и asyncio, ни одно из которых requests никогда не поддерживал. Поскольку requests написан на чистом Python, он устанавливается на что угодно и имеет десятилетнюю экосистему. Оба утверждения верны одновременно, и какое из них доминирует, зависит полностью от вашей цели.

Воспроизводимый тест, который вы можете провести за пять минут

Не доверяйте таблице пропусков никого, включая нашу. Различие в отпечатках можно наблюдать напрямую: направьте оба клиента на TLS-echo endpoint и сравните хэши, которые они возвращают. Если две строки совпадают, ваша сборка ничего не имитирует.

# 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.

С версии v0.15 есть однострочная версия той же проверки: curl-cffi get tls.browserleaks.com/json --impersonate chrome. Запустите это перед тем, как отлаживать что-либо еще — это отделяет "моя имитация неправильно настроена" от "моя имитация в порядке, и что-то еще меня блокирует", что совершенно разные ситуации.

Сравнение requests и curl_cffi по поддержке протоколов, параллелизму, отпечаткам и портативности
requests выигрывает по портативности и экосистеме; curl_cffi выигрывает по протоколам и отпечаткам. Ни один не выигрывает по качеству IP — это не функция клиента.

Что curl_cffi не исправляет: репутация IP

Вот часть, которую упускают из виду в совете "замените библиотеку". TLS отпечаток отвечает на вопрос "какое это программное обеспечение?". Он ничего не говорит о "откуда это?" — и на этот второй вопрос отвечает отдельный запрос к вашему выходному IP: какой ASN владеет им, является ли это хостинг-провайдером или потребительским ISP, появлялся ли он в списках злоупотреблений, сколько других сессий было с этого адреса на этот сайт за последний час. Безупречное рукопожатие Chrome, прибывающее из облачного VM в диапазоне датацентра, это браузер Chrome, который, по-видимому, установлен в стойке сервера. Это не более убедительно, чем python-requests. В некоторых случаях это даже менее убедительно, потому что противоречие более явное.

FAQ проекта ставит качество IP на первое место в списке факторов, перед частотой запросов и отпечатками JavaScript, объясняя, почему одной имитации может быть недостаточно. Этот порядок не случаен: репутация — это самый дешевый сигнал для оценки защитником и самый сложный для подделки атакующим, потому что, в отличие от заголовка или списка шифров, вы не можете сгенерировать его локально. Если ваш скрейпер на базе requests уже работал через пул датацентра и блокировался, переход на curl_cffi в том же пуле изменяет одну из двух провальных проверок. Вы увидите частичное улучшение на мягких целях и никакого улучшения на жестких — это именно тот запутанный результат, о котором сообщают люди.

Исправление для этой оси — это качество адреса, а не код: резидентные IP от реальных потребительских ISP, что дает вам более 90 миллионов адресов в более чем 200 странах. Если вы хотите проверить, как выглядит ваш текущий выходной IP, прежде чем что-то менять, наш бесплатный проверщик качества IP сообщает ASN и классификацию, которую увидит цель.

2x2, который покажет, какая ось сломана

Вместо того чтобы гадать, протестируйте обе переменные независимо на вашей реальной цели. Четыре запроса, четыре строки вывода, и результат назовет вашу проблему:

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))

Читайте четыре результата как таблицу истинности:

Запустите это несколько десятков раз, а не один раз. Оба уровня блокировки вероятностные, и один 200 почти ничего не говорит.

Тестируйте строку резидентных IP с реальными домашними IP

Диаграмма, показывающая, как рукопожатие TLS, соответствующее браузеру, с IP датацентра блокируется, в то время как то же рукопожатие с резидентного IP проходит
Имитация и репутация IP — это отдельные ворота. Исправление одного и оставление другого — вот почему "я переключился на curl_cffi и ничего не изменилось" — это такой распространенный отчет.

Когда дополнительная зависимость не стоит того

Откровенность дешевле, чем переписывание. Оставайтесь на requests, когда:

И есть средний путь, который большинство людей упускает: вам не нужно отказываться от requests, чтобы получить рукопожатие. Поддерживающие указывают на curl-adapter, который монтирует curl_cffi как транспортный адаптер для requests, и httpx-curl-cffi на PyPI, который делает то же самое для httpx. Вы сохраняете свой существующий код и экосистему, и только байты на проводе меняются.

Подводные камни миграции, которые стоит знать заранее

API достаточно близок, чтобы большинство скриптов работали после изменения импорта, но страница совместимости перечисляет реальные различия, и стоит их прочитать перед крупным портом. Тела ответов на перенаправления не сохраняются в Response.history. Cookies с пустыми доменами могут теряться при перенаправлениях. Потоковые объекты ответов не могут быть сериализованы, хотя обычные ответы могут. API файлов немного отличается. И вообще нет транспортов или адаптеров, потому что библиотека намеренно приварена к libcurl-impersonate. Конфигурация прокси также отличается небольшим образом, который сбивает людей с толку:

# 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

Полная поверхность прокси — ключи словаря, proxy_auth, ротация по запросу, async и префикс https://, который вызывает бесполезную ошибку WRONG_VERSION_NUMBER — покрыта шаг за шагом в нашем руководстве по прокси curl_cffi. Если вы остаетесь на месте, эквивалентная ссылка для другой стороны — это наше руководство по прокси Python Requests.

Часто задаваемые вопросы

curl_cffi быстрее requests?

Да, и бенчмарки проекта ставят его наравне с aiohttp и pycurl, а не с requests. Прирост идет от того, что libcurl выполняет работу на C плюс мультиплексирование HTTP/2, а не от хитрого Python. Для нескольких последовательных вызовов разница незаметна; при высокой параллельности, особенно с async, она значительна.

curl_cffi безопасен в использовании?

Он лицензирован по MIT, широко используется и поставляется с предварительно скомпилированными wheels, так что нет этапа сборки для аудита. Стоит обратить внимание на одно предупреждение: в версии v0.15.0 есть рекомендация по поводу SSRF на основе перенаправлений. Если вы получаете URL от других людей, установите allow_redirects="safe" или отключите перенаправления. Имитация браузера — это техническая мера, а не разрешение игнорировать условия сайта.

curl_cffi обходит Cloudflare?

Он убирает отпечатки TLS и HTTP/2, что устраняет базовые уровни защиты. Он не может выполнить JavaScript вызов, решить Turnstile или исправить помеченный выходной IP. Поддерживающие говорят об этом в своем FAQ и рекомендуют лучший пул прокси плюс автоматизацию браузера для более высоких уровней.

curl_cffi против httpx или tls_client — что мне использовать?

httpx дает вам HTTP/2 и async, но без имитации отпечатков, так что он находится между requests и curl_cffi по скрытности. tls_client также подделывает профили TLS и имеет схожие бенчмарки; curl_cffi имеет более крупное сообщество и добавляет HTTP/3 и вебсокеты. Если httpx уже в вашем стеке, транспорт httpx-curl-cffi дает вам имитацию без переписывания.

Краткая версия: переключайтесь на curl_cffi, когда ваша цель читает рукопожатия, оставайтесь на requests, когда этого не происходит, и никогда не ожидайте, что любой из выборов очистит IP датацентра. Клиенты различаются по одной оси, прокси по другой, и заблокированные скрейперы почти всегда история о том и другом. Запустите четыре проверки, прочитайте таблицу истинности и исправьте ось, на которую указывает данные, а не ту, о которой кричал интернет.

Исправьте ось, которую не может достичь имитация