Как IPv6-прокси обходят rate limiting в Twitter/X при массовом сборе твитов через API v2
Содержание
- Как работает rate limiting в API v2 на самом деле
- Почему IPv4-пул быстро выдыхается
- Что даёт IPv6: адресное пространство и репутация
- Архитектура пула: /64 как единица ротации
- Ротация внутри /64: source address selection
- Реальный кейс: 5000 запросов в минуту с /48
- Кейс с прокси-сервисом: когда /64 шарится
- Кейс: SNI и TLS fingerprint
- Ограничения и подводные камни
- Мониторинг и что смотреть в первую очередь
- Итог: что реально работает
Rate limiting в Twitter API v2 устроен так, что привязка идёт не только к токену, но и к IP. Это ломает любые схемы «один токен — много запросов». Разберём, как IPv6 меняет правила игры, где грабли и какие цифры реально получаются на выходе.
Как работает rate limiting в API v2 на самом деле
Twitter API v2 считает лимиты по трём осям одновременно. Первая — bearer token или OAuth 2.0 user context. Вторая — IP-адрес отправителя. Третья — эндпоинт с его собственным окном.
Заголовки ответа говорят сами за себя:
```
x-rate-limit-limit: 900
x-rate-limit-remaining: 899
x-rate-limit-reset: 1717430400
```
Для `/2/tweets/search/recent` на app-only auth лимит исторически 450 запросов за 15 минут на приложение. Но при превышении с одного IP прилетает 429 раньше, чем кончится токен. Это и есть точка, где в игру вступает адрес отправителя.
Окно скользящее, но не совсем. Twitter использует фиксированные 15-минутные бакеты, синхронизированные по UTC. Сброс в 00, 15, 30, 45 минут. Если начать долбить в 14:59, получишь 429 через несколько секунд и будешь ждать до 15:00. Планировщик под это надо подстраивать.
Почему IPv4-пул быстро выдыхается
Классический подход — купить /24 у хостера и раскидать запросы по 256 адресам. На практике это работает плохо. Twitter (как и большинство крупных CDN) держит репутационные базы по ASN и подсетям. 256 адресов из одного /24 — это один ASN, одна подсеть, один владелец. Антифрод видит паттерн за пару часов.
Плюс сам пул IPv4 дорогой. /24 у приличного провайдера в 2024 году — это 40-60 долларов в месяц минимум, а часто и с аукциона по 30-50 долларов за адрес. Для задачи, где нужно 5000+ параллельных источников, экономика не сходится.
Есть ещё нюанс с NAT. Многие хостеры продают «выделенный IPv4», но по факту адрес шарится между клиентами через NAT на границе. Если сосед по NAT уже словил бан — вы его наследуете.
Что даёт IPv6: адресное пространство и репутация
IPv6 даёт /64 на клиента — это 18 квинтиллионов адресов. Даже /48 от провайдера — 65536 подсетей /64. Для rate limiting это означает, что каждый запрос может уходить с уникального адреса, и Twitter технически не может привязать его к общему пулу так же быстро, как IPv4.
Ключевой момент: Twitter считает лимит по /64, а не по отдельному адресу. Это официально не задокументировано, но эмпирически подтверждается. Если гонять запросы с 1000 адресов внутри одного /64, лимит общий. Нужно раскидывать по разным /64.
Средний IPv6-прокси-пул даёт /48 или /56. Из /48 получается 65536 подсетей /64, каждая со своим лимитом. Даже с поправкой на то, что Twitter режет по /64, это в 65536 раз больше ёмкости, чем один IPv4.
Архитектура пула: /64 как единица ротации
Правильная схема — не «один адрес = один запрос», а «одна подсеть /64 = один бакет лимита». Внутри /64 можно гонять сколько угодно адресов, но лимит общий. Значит, ротация должна идти по /64.
Вот как это выглядит в конфиге HAProxy с IPv6-бэкендами:
```
global
maxconn 50000
defaults
mode http
timeout connect 5s
timeout client 30s
timeout server 30s
frontend api_in
bind *:8080
default_backend twitter_pool
backend twitter_pool
balance roundrobin
option httpchk GET /2/tweets/search/recent
server node1 [2001:db8:1::1]:3128 check
server node2 [2001:db8:2::1]:3128 check
server node3 [2001:db8:3::1]:3128 check
```
Каждый `node` — отдельная /64. HAProxy раскидывает запросы round-robin, а внутри каждой /64 прокси-сервер сам выбирает адрес из своей подсети. Это даёт двойную ротацию: по подсети на уровне балансировщика и по адресу на уровне прокси.
Ротация внутри /64: source address selection
Linux по умолчанию выбирает source address по алгоритму RFC 6724. Для исходящих соединений берётся первый адрес из интерфейса, помеченный как preferred. Если не управлять этим — все запросы уйдут с одного адреса, и /64 превратится в тыкву.
Управлять можно через `ip -6 addr add` и явное указание source в сокете. В Python это делается через `socket.bind()` до `connect()`:
```python
import socket
import random
import ipaddress
def make_request_via_ipv6(target_host, target_port, source_ip):
sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
sock.bind((source_ip, 0, 0, 0))
sock.settimeout(10)
sock.connect((target_host, target_port))
return sock
subnet = ipaddress.IPv6Network("2001:db8:1::/64")
for i in range(100):
random_ip = str(subnet.network_address + random.randint(1, 2**64 - 2))
try:
s = make_request_via_ipv6("api.twitter.com", 443, random_ip)
print(f"connected from {random_ip}")
s.close()
except OSError as e:
print(f"failed {random_ip}: {e}")
```
Работает, но медленно: `bind()` на каждый запрос добавляет 1-3 мс. Для 5000 rps это уже 15 секунд оверхеда в секунду. На проде лучше держать пул предварительно забинденных сокетов и переиспользовать их через connection pool.
Реальный кейс: 5000 запросов в минуту с /48
Сервер на Debian 12, HAProxy 2.8, пул из 512 /64 внутри одного /48 от провайдера. Задача — собрать последние твиты по 200 ключевым словам через `/2/tweets/search/recent`, лимит 450 запросов на 15 минут на токен.
Проблема: с одним токеном и одним IPv4 получалось 450 запросов за 15 минут, потом 429 на 13 минут. Пропускная способность — 30 запросов в минуту, сбор 200 слов занимал 6+ часов.
Причина: лимит по токену и по IP срабатывал одновременно, а окно сброса не совпадало с реальным временем начала задачи.
Решение: 10 bearer-токенов (разные app) × 512 /64 = 5120 независимых бакетов. Ротация по /64 с интервалом 15 минут, синхронизированная с UTC-бакетами Twitter. На выходе — 450 × 10 × 512 / 15 = 153600 запросов в минуту теоретически, на практике упирались в 5000 rps из-за пропускной способности апстрима и стоимости токенов.
Реальные цифры после недели работы: 4800-5200 rps стабильно, 429 приходит в 0.3% случаев (в основном при синхронизации окна), средняя задержка ответа 180 мс, p99 — 720 мс.
Кейс с прокси-сервисом: когда /64 шарится
Купили пул у провайдера, который обещал «выделенные /64». По факту оказалось, что /64 выделены, но все они внутри одного /48, и провайдер сам режет трафик через общий шейпер на /48. Twitter это увидел за сутки: все 512 /64 попали в один репутационный бакет по ASN.
Симптом: 429 приходит на 50-м запросе вместо 450-го, независимо от того, с какой /64 идёт запрос. Заголовок `x-rate-limit-remaining` показывает 0 при фактических 400 использованных.
Причина: Twitter (или его upstream, скорее всего Cloudflare) агрегирует репутацию по /48 или по ASN, а не по /64. Это недокументированное поведение, но воспроизводится стабильно.
Решение: сменили провайдера на того, кто выдаёт /48 из разных ASN (у крупных хостеров это делается через BGP anycast). Разнесли пул по 4 ASN, по 128 /64 в каждом. Лимиты восстановились до ожидаемых 450 на /64.
Кейс: SNI и TLS fingerprint
Прокси на IPv6 работает, лимиты не превышаются, но 429 всё равно прилетает на 200-м запросе. Заголовки чистые, токены свежие.
Проблема оказалась в TLS fingerprint. Python `requests` с дефолтным `urllib3` даёт JA3-хеш, который Twitter (через Cloudflare) считает подозрительным при массовых запросах с одного ASN. Смена IP не помогала — fingerprint оставался.
Решение: перешли на `curl_cffi` с имперсонацией Chrome 120:
```python
from curl_cffi import requests
response = requests.get(
"https://api.twitter.com/2/tweets/search/recent",
params={"query": "python", "max_results": 100},
headers={"Authorization": f"Bearer {token}"},
impersonate="chrome120",
proxies={"https": f"http://[{ipv6_addr}]:3128"}
)
```
После смены JA3 с дефолтного Python на Chrome 120 частота 429 упала с 40% до 2%. Это уже не про IPv6, но без этого IPv6-пул бесполезен: репутация адреса не спасает от блокировки по отпечатку TLS.
Ограничения и подводные камни
IPv6-прокси не панацея. Первое: не все провайдеры дают настоящий /48 — многие продают «IPv6-прокси» с /64, внутри которого 256 адресов, и это не то же самое. Второе: Twitter может в любой момент ужесточить агрегацию репутации до /48 или ASN, и тогда схема развалится.
Третье: стоимость. Нормальный /48 от приличного провайдера с чистой репутацией — 200-400 долларов в месяц. Дешёвые пулы по 20 долларов обычно уже в чёрных списках у Cloudflare.
Четвёртое: юридическая сторона. Массовый сбор через API с обходом лимитов нарушает Twitter Developer Agreement. Аккаунты и токены банятся, а при агрессивном обходе можно получить иск. Это не техническая проблема, но игнорировать её нельзя.
Мониторинг и что смотреть в первую очередь
Без мониторинга схема развалится за сутки. Смотреть надо на три метрики: доля 429 по каждой /64, распределение `x-rate-limit-remaining` по времени, и JA3-хеши исходящих соединений.
Простой дашборд на Prometheus + Grafana: экспортёр считает 429 по /64 и рисует heatmap. Если какая-то /64 начала отдавать 429 раньше остальных — она в репутационном бакете, пора менять. Нормальный пул даёт 429 в пределах 1-3% от общего числа запросов, всё что выше — сигнал.
Ещё одна метрика — TTFB по каждой /64. Если одна подсеть стабильно отвечает на 200-300 мс медленнее остальных, скорее всего она уже под rate limit или под шейпером. Раннее обнаружение экономит часы отладки.
Итог: что реально работает
Связка, которая даёт результат: /48 от провайдера с чистой репутацией, ротация по /64 с синхронизацией на 15-минутные UTC-бакеты, TLS-имперсонация Chrome, мониторинг 429 по каждой подсети. Без любого из этих элементов схема либо не масштабируется, либо быстро банится.
IPv6 даёт адресное пространство, но не даёт автоматически репутацию. Twitter и Cloudflare видят ASN, /48, TLS fingerprint и поведенческие паттерны — и режут по совокупности. IPv6-прокси решает только одну из пяти задач. Остальные четыре — токены, отпечатки, тайминги и мониторинг — нужно закрывать отдельно.