← Назад в базу знаний

Как IPv6-прокси обходят rate limiting в Twitter/X при массовом сборе твитов через API v2

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-прокси решает только одну из пяти задач. Остальные четыре — токены, отпечатки, тайминги и мониторинг — нужно закрывать отдельно.

✔️Купить прокси