Руководство по прокси curl_cffi: настройка, аутентификация, ротация и асинхронность
curl_cffi предоставляет TLS рукопожатие, имитирующее браузер. Прокси дает чистый выходной IP. Вот как точно соединить их вместе — и почему префикс https:// в вашем словаре прокси вызывает ErrCode 35.
curl_cffi — это привязка Python к форку curl-impersonate: он воспроизводит отпечатки TLS/JA3 и HTTP/2 настоящего браузера вместо того, чтобы объявлять себя как urllib3. Это устраняет одну ось блокировки. Другая — это выходной IP, где вступает в дело прокси curl_cffi — и где документация становится скудной. Официальный раздел о прокси занимает около пятнадцати строк, и проблема на GitHub от февраля 2023 года до сих пор входит в топ-5 по этой теме. Это руководство охватывает всю поверхность: параметр proxy, словарь в стиле requests и его реальные ключи, proxy_auth, сессии, ротация для каждого запроса, асинхронность, SOCKS5 — и точные строки ошибок, которые вы вставите в поисковую строку.
Синтаксис прокси curl_cffi: предпочтительнее proxy= вместо словаря proxies
curl_cffi принимает две формы. Родная — это строка proxy=, добавленная в версии v0.6.0; словарь proxies= существует для совместимости с requests, и документация рекомендует использовать один параметр, если вам действительно не нужны разные прокси для каждой схемы. Внутренне они сводятся к одному и тому же — proxy="..." становится {"all": "..."} — и оба работают на вспомогательных модулях, на Session, на AsyncSession и на отдельных запросах.
# 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>'}
Четыре вещи о том словаре стоит знать, потому что ни одна из них не очевидна из README:
- Действительные ключи — это
all,http,https,wsиwss.all— это универсальный ключ; ключи вебсокетов важны только для клиента WebSocket. - Ключи для каждого хоста тоже работают.
https://api.example.comилиall://example.comмаршрутизируют только этот хост через заданный прокси — полезно для отправки одного сложного домена через резидентные IP и оставления остальных напрямую. - Вы не можете передать оба.
proxy=плюсproxies=в одном вызове вызываетTypeError: Cannot specify both 'proxy' and 'proxies', и та же проверка выполняется на уровне сессии. - Переменные окружения учитываются —
http_proxy,https_proxy,ws_proxy,wss_proxy. Передайтеtrust_env=FalseвSession, когда корпоративная переменная перехватывает ваш скрейпер.
Одна заметка о импортах: с версии v0.10.0 пакет можно вызывать напрямую (curl_cffi.get, curl_cffi.Session). Старые учебники используют from curl_cffi import requests, что все еще работает, но выглядит плохо рядом с настоящей библиотекой requests — и объясняет, почему половина фрагментов в сети выглядит как другой проект.
Ловушка https://: ErrCode 35 и WRONG_VERSION_NUMBER
Эта единственная ошибка вызывает больше вопросов по прокси curl_cffi, чем все остальное вместе взятое. Issue #6 в трекере проекта — открыта и закрыта в один день в феврале 2023 года — до сих пор занимает первую страницу, потому что ошибка, которую она вызывает, выглядит как ошибка TLS, а не опечатка в конфигурации:
# 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"
Ключ указывает протокол цели; значение указывает, как вы достигаете прокси. Обычный HTTPS-over-HTTP прокси принимает текстовый CONNECT, затем туннелирует ваш зашифрованный трафик без изменений — так что URL прокси начинается с http://, даже если каждый URL, который вы запрашиваете, — это HTTPS. HTTPS-over-HTTPS прокси существуют, но редки и должны быть явно поддержаны шлюзом. Requests формулирует ту же ошибку гораздо более полезно — ваш прокси, похоже, использует только HTTP, а не HTTPS — поэтому идентичная конфигурация может выглядеть как специфическая ошибка curl_cffi. Последние версии предупреждают и ссылаются на issue #6, но это только предупреждение: запрос все равно не удается.
Аутентификация: учетные данные в URL или proxy_auth
Аутентифицированные шлюзы принимают обычную встроенную форму, http://USER:PASS@host:port, с обычной оговоркой: неэкранированные @, : или / в пароле разбивают URL в неправильном месте и вызывают ошибку аутентификации, которая выглядит как мертвый прокси. curl_cffi предлагает выход, которого нет в requests — кортеж proxy_auth, передаваемый в libcurl как отдельные параметры имени пользователя и пароля, так что кодирование вообще не требуется.
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())
Третий вариант полностью устраняет этот класс ошибок: белый список IP. Каждый резидентный план QuantumProxies позволяет авторизовать IP вашего сервера вместо отправки user:pass, так что URL прокси становится простым http://gate.quantumproxies.io:PORT — ничего кодировать, никакого секрета в вашем исходном коде. Если сами учетные данные отклоняются, наше руководство по всем причинам 407 Proxy Authentication Required охватывает остальное.

Сессии, куки и детали повторного использования учетных данных
Session хранит куки, пул соединений и ваши настройки по умолчанию в одном месте, что вам нужно для чего-либо многошагового. Установите impersonate и proxy один раз, и каждый запрос унаследует их:
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())
Два поведения заслуживают упоминания. Во-первых, когда прокси настроен, curl_cffi включает опцию libcurl proxy-credential-no-reuse: новое соединение принудительно создается, когда имя пользователя прокси изменяется, и кэш сессии TLS привязан к адресу прокси, так что предыдущий выходной IP не может просочиться в более поздний запрос через повторно используемую сессию. Если вы кодируете идентификаторы сессий в имени пользователя, как это делают большинство вращающихся шлюзов, вы получаете эту изоляцию бесплатно. Во-вторых, retry (целое число или RetryStrategy из curl_cffi.requests с задержкой, обратной связью и джиттером) повторно запускается только при транспортном исключении. Он не повторяет 403 или 429, как это делает status_forcelist в urllib3 — этот цикл все еще ваш для написания. Документы по совместимости перечисляют повторы как неподдерживаемые, что устарело: параметр появился в версии v0.15.0.
Направьте curl_cffi на вращающийся резидентный шлюз
Ротация для каждого запроса и асинхронность
curl_cffi рекламирует asyncio с ротацией прокси на каждом запросе, и это буквально: аргумент proxy= в отдельном вызове переопределяет то, что хранит сессия. Вам редко нужен список прокси, чтобы это использовать — вращающийся шлюз назначает новый выходной сервер на стороне сервера при каждом соединении, так что одна конечная точка плюс параллелизм уже является ротацией. Где вам нужен контроль (один стабильный IP на работника, на аккаунт, на корзину), поместите токен сессии в имя пользователя и позвольте шлюзу закрепить этот выход.
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 ограничивает количество одновременных curl-хэндлов в пуле (по умолчанию 10), так что это ваш реальный регулятор параллелизма — добавление семафора к неограниченному gather — обычная ошибка. Та же логика размера применяется к любому асинхронному клиенту, что мы рассмотрели в асинхронном скрейпинге Python с httpx и aiohttp. Ротация для каждого запроса или закрепление сессии зависит от того, отслеживает ли сайт состояние между запросами; компромиссы описаны в липкие сессии против вращающихся прокси.

SOCKS5, HTTP/3 и защитные переключатели
SOCKS не требует дополнительной установки — libcurl скомпилирован, так что в отличие от requests, здесь нет [socks] дополнения, о котором нужно помнить. Используйте socks5h://USER:PASS@gate.quantumproxies.io:PORT: h передает разрешение DNS прокси, что предотвращает утечки из вашей собственной сети и разрешает геоограниченные имена хостов с местоположения выхода. curl_cffi обнаруживает префикс socks и пропускает флаг HTTP туннелирования, поскольку протокол SOCKS сам обрабатывает это. Каждый план здесь предоставляет HTTP и SOCKS5 конечные точки на одном и том же шлюзе, так что переключение — это смена схемы, а не новый заказ.
- HTTP/3 через прокси появился в версии v0.15.0 вместе с отпечатками http/3, но ему нужен сервер SOCKS5, который поддерживает UDP, а не обычный HTTP шлюз. Это ниша, пока ваша цель не вознаграждает QUIC.
- Укрепление SSRF. То же обновление содержало предупреждение: если вы запрашиваете URL, предоставленные другими людьми, перенаправления могут быть проведены в вашу внутреннюю сеть. Установите
allow_redirects="safe", или отключите перенаправления. - Отладка. В версии v0.15 был выпущен CLI:
curl-cffi get tls.browserleaks.com/json --impersonate chromeсообщает вам в одной строке, приземляется ли имитация, прежде чем вы обвините прокси.
Когда curl_cffi плюс прокси достаточно
Чаще, чем люди ожидают. Если цель обслуживает JSON из внутреннего API или серверно-рендеренный HTML, и единственное препятствие — это проверка отпечатков, соответствующее рукопожатие плюс резидентный выход решает это с меньшими затратами и задержкой, чем у браузера. FAQ проекта откровенно о потолке: отпечатки — это один из нескольких факторов, наряду с качеством IP, скоростью запросов и проверками JavaScript, и более высокие уровни защиты требуют как лучшего пула прокси, так и настоящей автоматизации браузера. Когда имитация настроена правильно, и вас все еще блокируют, оставшаяся переменная почти всегда — это выходной IP — изолировать это за пять минут — тема curl_cffi против requests. Если вы предпочли бы не запускать ни то, ни другое, Scraper API обрабатывает отпечатки, прокси и опциональную JS-рендеринг за одним вызовом.
Часто задаваемые вопросы
Как использовать прокси с curl_cffi?
Передайте proxy="http://USER:PASS@host:port" в любой метод запроса, сессию или асинхронную сессию. Словарь в стиле requests proxies={"http": ..., "https": ...} также работает, но проект рекомендует один параметр, если вам не нужны разные прокси для каждой схемы. Передача обоих вызывает TypeError.
Почему curl_cffi выдает ErrCode 35 WRONG_VERSION_NUMBER?
Потому что URL прокси начинается с https://. Обычный прокси ожидает текстовый запрос CONNECT, а затем туннелирует ваш TLS; префикс https:// заставляет curl пытаться выполнить TLS-рукопожатие с самим прокси, который отвечает в формате HTTP. Измените значение на http:// — ключ https относится к цели, а не к переходу.
Поддерживает ли curl_cffi прокси SOCKS5?
Да, нативно — libcurl встроен, так что нет необходимости в дополнительной установке. Используйте схему socks5h://, чтобы имена хостов разрешались прокси, а не вашей машиной. SOCKS4, SOCKS4a и простой socks5:// также принимаются; библиотека пропускает HTTP туннелирование для любого прокси, схема которого начинается с socks.
Может ли curl_cffi вращать прокси на каждом запросе?
Да. Аргумент proxy= в отдельном вызове переопределяет значение по умолчанию сессии, включая внутри AsyncSession, что README подразумевает под asyncio с ротацией для каждого запроса. С вращающимся шлюзом вам часто не нужна никакая логика: одна и та же конечная точка выдает разный выходной IP при каждом соединении.
Может ли curl_cffi обойти Cloudflare?
Иногда. Он удаляет отпечатки TLS и HTTP/2, что достаточно для базовых уровней защиты. Он не может выполнять JavaScript-задачи, решать Turnstile или исправлять IP-адрес центра обработки данных, который уже отмечен в базе данных репутации. Рассматривайте имитацию как одно из трех требований, а не как ответ.
Вся конфигурация меньше, чем ее репутация: одна строка proxy, impersonate установлена один раз на сессии, тайм-аут на каждом вызове, и учетные данные либо закодированы в URL, либо переданы как кортеж proxy_auth. Настройте их правильно, и оставшаяся переменная — это качество IP — идеальное рукопожатие Chrome с отмеченного адреса центра обработки данных все еще остается отмеченным адресом центра обработки данных. Наш анализ JA3 и JA4 отпечатков объясняет, почему эти две проверки независимы.
Получите резидентные IP, которые соответствуют вашей имитации