Cloudflare через IPv6 прокси: обход challenge и парсинг защищённых сайтов
Содержание
- Почему Cloudflare стал проблемой для парсеров
- Чем IPv6 отличается от IPv4 в контексте антибота
- TLS-фингерпринт важнее IP
- Практика: curl_cffi через IPv6
- Когда JS-challenge неизбежен
- Ротация IPv6-адресов внутри /64
- Таблица: что работает, а что нет
- Реальный пример: парсинг маркетплейса
- Прокси-слой lexic.ml и IPv6
- Заголовки, которые выдают автоматизацию
- HTTP/2 и HTTP/3
- Итоговые грабли
Почему Cloudflare стал проблемой для парсеров
Cloudflare защищает примерно 20% всех сайтов в интернете. Для обычного пользователя это невидимый щит. Для парсера — стена с капчей, JS-challenge и TLS-фингерпринтингом.
Механика защиты многослойная. Сначала CDN проверяет репутацию IP по ASN. Потом смотрит на TLS ClientHello — набор шифров, порядок расширений, ALPN. Дальше идёт JS-челлендж: браузер должен выполнить обфусцированный код и вернуть cookie `cf_clearance`. И только тогда запрос проходит.
Датацентровые IP из DigitalOcean, Hetzner, AWS горят на первом слое. Residential-прокси решают проблему, но стоят от \$3 за гигабайт. IPv6 в этом раскладе — недооценённый инструмент.
Чем IPv6 отличается от IPv4 в контексте антибота
У IPv6 гигантский пул адресов. Провайдер выдаёт /64 — это 18 квинтиллионов адресов на одного клиента. Cloudflare физически не может банить подсети /64 целиком, иначе положит легитимных пользователей.
На практике репутация строится не по отдельному адресу, а по префиксу /64 или /48. Свежий /64 от жилого провайдера имеет нейтральную репутацию. Датацентровый /64 — красный флаг.
Второй момент: многие антибот-системы до 2023 года плохо обрабатывали IPv6. Логика была заточена под IPv4. Сейчас дыры закрыли, но инерция осталась — некоторые проверки просто пропускают IPv6-трафик без глубокого анализа.
TLS-фингерпринт важнее IP
Голый IPv6 не спасёт, если у вас Python `requests`. JA3-хеш этого клиента известен всем WAF. Cloudflare сравнивает отпечаток с базой легитимных браузеров.
Что смотреть в ClientHello:
- порядок cipher suites
- список extensions (особенно GREASE-значения)
- supported_versions
- elliptic_curves
- ALPN-протоколы
`requests` с urllib3 даёт JA3 вроде `3b5074b1b5d032e5620f69f9f700ff0e`. Chrome 120 — совсем другой хеш. Разница видна с первого пакета.
Решение — либо curl_cffi с импersonation, либо реальный браузер через Playwright. Первое легче, второе надёжнее.
Практика: curl_cffi через IPv6
Библиотека `curl_cffi` подменяет TLS-отпечаток на браузерный. Плюс поддерживает привязку к IPv6-интерфейсу.
```python
from curl_cffi import requests
proxies = {
"http": "http://[2a01:4f8:c17:1234::1]:8080",
"https": "http://[2a01:4f8:c17:1234::1]:8080"
}
r = requests.get(
"https://example-protected.com/api/data",
impersonate="chrome120",
proxies=proxies,
timeout=30
)
print(r.status_code, r.json())
```
Ключевой параметр — `impersonate`. Без него даже через IPv6 вы получите 403 за 200 мс. С ним — 200 OK, если IP не в чёрном списке.
Проверить, что трафик реально идёт через IPv6, можно так:
```bash
curl -6 -x "http://[2a01:4f8:c17:1234::1]:8080" \
-H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)" \
https://api64.ipify.org?format=json
```
Ответ покажет IPv6-адрес прокси. Если вернулся IPv4 — прокси не поддерживает IPv6-выход, и вся затея бессмысленна.
Когда JS-challenge неизбежен
`cf_clearance` cookie выдаётся после прохождения JS-задачи. Иногда — managed challenge без капчи, иногда — Turnstile. Обойти это без браузера почти нереально.
Playwright с IPv6-прокси работает, но требует настройки. Прокси задаётся на уровне контекста браузера.
```python
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(
headless=True,
args=["--disable-blink-features=AutomationControlled"]
)
context = browser.new_context(
proxy={
"server": "http://[2a01:4f8:c17:1234::1]:8080",
"username": "user",
"password": "pass"
},
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
)
page = context.new_page()
page.goto("https://target.com", wait_until="networkidle")
cookies = context.cookies()
print(cookies)
```
Cookie `cf_clearance` живёт 30 минут, привязана к IP и User-Agent. Смена IP — сброс. Смена UA — сброс. Это ломает ротацию прокси, если делать её слишком агрессивно.
Ротация IPv6-адресов внутри /64
Провайдер даёт /64, и все адреса в нём маршрутизируются на ваш сервер. Можно менять исходящий адрес на каждый запрос без переподключения к прокси.
На Linux это делается через `ip -6 addr add`:
```bash
for i in \$(seq 1 100); do
ip -6 addr add 2a01:4f8:c17:1234::\${i}/64 dev eth0
done
```
Дальше в приложении выбираете случайный адрес из пула как source. В `curl` это `--interface`, в Python — `socket.bind()` перед connect.
Подводный камень: Cloudflare считает репутацию по /64. Если с одного префикса летит 1000 запросов в минуту с разных адресов — это выглядит как ботнет. Ротация помогает от per-IP лимитов, но не от префиксных.
Таблица: что работает, а что нет
| Метод | Обход managed challenge | Обход JS challenge | Стоимость |
|-------|------------------------|--------------------|-----------|
| requests + IPv4 DC | нет | нет | низкая |
| requests + IPv6 residential | частично | нет | средняя |
| curl_cffi + IPv6 DC | да | нет | низкая |
| curl_cffi + IPv6 residential | да | иногда | средняя |
| Playwright + IPv6 residential | да | да | высокая |
Цифры приблизительные, зависят от конкретной конфигурации Cloudflare у целевого сайта. Уровень защиты настраивается владельцем — где-то стоит "Essentially Off", где-то "I'm Under Attack".
Реальный пример: парсинг маркетплейса
Сервер на Hetzner, Ubuntu 22.04, IPv6 /64 от провайдера. Цель — сайт на Cloudflare с managed challenge. Первые попытки через `requests` давали 403 с заголовком `cf-mitigated: challenge`.
Причина — JA3-хеш Python. Переписали на curl_cffi с `impersonate="chrome120"`. Задержка выросла с 180 мс до 340 мс (дополнительный TLS handshake с браузерным отпечатком). Но 403 превратились в 200.
Через сутки начались 429. Cloudflare включил rate limiting по /64. Решение — распределить трафик на три /64 от разных провайдеров. Плюс задержка 2-4 секунды между запросами. Пропускная способность упала до 15 запросов в минуту, но стабильность выросла до 99%.
Прокси-слой lexic.ml и IPv6
Собственный IPv6-пул требует ASN, договора с провайдером и настройки BGP. Для большинства задач это избыточно. Готовые IPv6-прокси снимают головную боль — вы получаете /64 или несколько /64 в аренду, с автоматической ротацией адресов внутри префикса.
На lexic.ml IPv6-прокси работают с 2015 года, и за это время пул пережил несколько волн банов Cloudflare. Ключевое — разнообразие ASN. Если все адреса из одного датацентра, толку мало.
Заголовки, которые выдают автоматизацию
Cloudflare смотрит на порядок заголовков. В реальном Chrome он строгий: `Host`, `Connection`, `sec-ch-ua`, `sec-ch-ua-mobile`, `User-Agent`, `Accept`, `Sec-Fetch-Site` и так далее. Python `requests` отправляет их в другом порядке плюс добавляет `Accept-Encoding: gzip, deflate` без `br`.
Проверить свой отпечаток можно на `tls.browserleaks.com` или `httpbin.org/headers`. Если порядок отличается от браузерного — challenge гарантирован.
`curl_cffi` с `impersonate` копирует и порядок заголовков, и их значения. Это одна из причин, почему библиотека обходит то, что `requests` не может.
HTTP/2 и HTTP/3
Cloudflare активно продвигает HTTP/3 поверх QUIC. Некоторые WAF-правила работают только с HTTP/2. Если ваш клиент умеет HTTP/3 — попробуйте, иногда это обходит проверки, заточенные под HTTP/2.
Но есть нюанс: не все прокси пропускают UDP. QUIC требует UDP-транспорт. IPv6-прокси на TCP-only инфраструктуре HTTP/3 не отдадут. Проверяйте поддержку заранее.
Итоговые грабли
Первая — верить, что IPv6 сам по себе решает проблему. Нет. Он лишь один из слоёв. Без правильного TLS-отпечатка и заголовков он бесполезен.
Вторая — ротировать адреса слишком часто. Cloudflare видит паттерн и банит префикс. Умеренность важнее скорости.
Третья — игнорировать `cf_clearance`. Если challenge всё-таки пройден, cookie надо переиспользовать, а не получать заново на каждый запрос. Иначе браузерный фарм выглядит подозрительно.
Четвёртая — забыть про `Retry-After`. При 429 Cloudflare указывает, сколько ждать. Игнорирование приводит к эскалации бана.
Пятая — использовать headless-браузер без маскировки. `navigator.webdriver`, отсутствие плагинов, странный WebGL-рендер — всё это палится. Playwright с `--disable-blink-features=AutomationControlled` и stealth-плагином частично лечит, но не полностью.