Парсинг Wildberries и Ozon через IPv6 прокси: как обойти лимиты по регионам
Содержание
- Почему маркетплейсы вообще упираются в IP
- Что именно ограничивают WB и Ozon
- Как IPv6 обходит лимиты
- Проверяем, что у вас есть IPv6 префикс
- Пример: /64 префикс 2a01:4f8:1c1c:abcd::/64
- Генерируем случайный адрес из префикса
- Реальный кейс: парсинг цен WB по регионам
- Ozon и его трюки с TLS
- Сравнение подходов
- Проблемы, на которые наступают все
- Кейс: Ozon и лимит по сессии
- Кейс: WB и региональные цены
- Кейс: бан по ASN
- Практика: прокси-сервер на IPv6
- 3proxy.cfg
- Внешние IPv6 из префикса
- SOCKS5 на localhost
- Что делать с капчей
- Итоги по инфраструктуре
Почему маркетплейсы вообще упираются в IP
Wildberries и Ozon — это не просто сайты с товарами. Это API-платформы с жёстким rate limiting, региональной привязкой цен и антифродом, который смотрит на IP-адрес клиента. Один и тот же товар в Москве и Новосибирске может стоить по-разному, а иногда и вовсе отсутствовать в выдаче для конкретного региона.
Когда вы парсите каталог с одного IP, вы упираетесь в три стены одновременно. Первая — лимит запросов с адреса. Вторая — геолокация, по которой маркетплейс подставляет цены и склад. Третья — бан за аномальное поведение, если с одного IP идёт 50 тысяч запросов в час.
IPv6 здесь даёт то, чего не может IPv4: у вас не один адрес, а пул из миллиардов. Провайдер выдаёт /64 или /48 — это 18 квинтиллионов адресов в одном префиксе. Каждый запрос можно делать с нового адреса, и для маркетплейса это выглядит как трафик из разных сетей.
Что именно ограничивают WB и Ozon
У Wildberries публичный API на `suppliers-api.wildberries.ru` и `card.wb.ru`. Rate limit по документации — около 300 запросов в минуту на один ключ, но по факту при парсинге каталога через `card.wb.ru` вы упираетесь в лимит по IP задолго до этого. Ответ приходит с кодом 429 и заголовком `X-Ratelimit-Retry: 60`.
Ozon жёстче. Их `api-seller.ozon.ru` требует OAuth-токен, а публичный поиск на `ozon.ru/api/composer-api.bx` отдаёт 403 после 200-300 запросов с одного адреса за короткое время. Плюс есть отдельный слой защиты — они смотрят на TLS-фингерпринт и заголовки.
Региональная привязка работает через заголовок `X-Forwarded-For` и геобазу по IP. Если вы делаете запрос с московского адреса, WB вернёт цены для Москвы и склады Коледино, Электросталь. С новосибирского — цены с учётом локальной логистики, которые выше на 5-15% для ряда категорий.
Как IPv6 обходит лимиты
Ключевая идея: маркетплейс видит не «один клиент с 50 тысячами запросов», а «50 тысяч клиентов с одним запросом каждый». Если у вас /64 префикс, вы можете генерировать адреса программно и ротировать их между запросами.
```bash
Проверяем, что у вас есть IPv6 префикс
ip -6 addr show dev eth0 | grep inet6
Пример: /64 префикс 2a01:4f8:1c1c:abcd::/64
Генерируем случайный адрес из префикса
python3 -c "
import random
prefix = '2a01:4f8:1c1c:abcd'
suffix = ':'.join(f'{random.randint(0, 0xffff):x}' for _ in range(4))
print(f'{prefix}:{suffix}')
"
```
Ротация на уровне сокета в Python выглядит так:
```python
import socket
import random
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.connection import create_connection
PREFIX = "2a01:4f8:1c1c:abcd"
def random_ipv6():
suffix = ":".join(f"{random.randint(0, 0xffff):x}" for _ in range(4))
return f"{PREFIX}:{suffix}"
_original_create_connection = create_connection
def patched_create_connection(address, *args, **kwargs):
host, port = address
source = (random_ipv6(), 0)
return _original_create_connection(address, *args, source_address=source, **kwargs)
urllib3.util.connection.create_connection = patched_create_connection
session = requests.Session()
session.mount("https://", HTTPAdapter(max_retries=3))
for page in range(1, 100):
r = session.get(
"https://card.wb.ru/cards/detail",
params={"nm": 12345678, "dest": -1257786},
timeout=10,
)
print(page, r.status_code, len(r.content))
```
Здесь `source_address` заставляет ядро Linux использовать конкретный исходящий IPv6. Провайдер должен маршрутизировать весь /64 на ваш сервер — обычно это делается через `ip -6 route add local 2a01:4f8:1c1c:abcd::/64 dev lo`.
Реальный кейс: парсинг цен WB по регионам
Сервер на Hetzner CX22, Ubuntu 22.04, префикс /64. Задача — собрать цены по 47 регионам для 12 тысяч SKU. Наивный подход с одного IPv4 дал 429 на 340-м запросе и бан через 20 минут.
Причина — WB считает запросы по IP с окном в 60 секунд. При 300 запросах в минуту с одного адреса включается мягкий лимит, при 500 — жёсткий бан на час.
Решение: ротация IPv6 каждые 10 запросов. Скрипт поднимал 8 параллельных воркеров, каждый со своим пулом адресов. Пропускная способность выросла до 2400 запросов в минуту без единого 429. Полный обход 12 тысяч SKU по 47 регионам занял 4 часа 20 минут против расчётных 30+ часов на IPv4 с прокси-пулом.
Технические детали: `dest` параметр в API WB отвечает за регион. Для Москвы `dest=-1257786`, для Новосибирска `dest=-1235806`. Без правильного `dest` вы получите цены по умолчанию, а не по региону.
Ozon и его трюки с TLS
Ozon смотрит не только на IP, но и на JA3-фингерпринт TLS-хендшейка. Если вы используете `requests` с дефолтным OpenSSL, фингерпринт совпадает у миллионов клиентов. Это палево.
Для обхода нужен либо `curl_cffi` с эмуляцией Chrome, либо ротация TLS-стека. Пример с `curl_cffi`:
```python
from curl_cffi import requests as cffi_requests
import random
PREFIX = "2a01:4f8:1c1c:abcd"
def random_ipv6():
return f"{PREFIX}:" + ":".join(f"{random.randint(0,0xffff):x}" for _ in range(4))
session = cffi_requests.Session(impersonate="chrome124")
for query in ["iphone 15", "samsung s24", "xiaomi 14"]:
r = session.get(
"https://www.ozon.ru/api/composer-api.bx/page/json/v2",
params={"url": f"/search/?text={query}"},
headers={"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"},
timeout=15,
)
print(query, r.status_code, len(r.text))
```
`impersonate="chrome124"` подменяет весь TLS-стек на тот, что использует Chrome 124. JA3 совпадает с реальным браузером, Ozon не видит разницы на уровне хендшейка.
Привязка source address в `curl_cffi` делается через `interface` параметр, но проще ротировать на уровне сети через `ip6tables` SNAT или использовать прокси-сервер на том же хосте.
Сравнение подходов
| Метод | Запросов/мин | Стоимость | Бан-риск |
|-------|--------------|-----------|----------|
| IPv4 + residential прокси | 200-400 | \$3-8 за GB | Низкий |
| IPv4 datacenter пул | 100-200 | \$0.5-1 за IP | Высокий |
| IPv6 /64 ротация | 2000+ | \$5/мес за VPS | Низкий |
| IPv6 /48 ротация | 10000+ | \$15-30/мес | Очень низкий |
Цифры для WB при 10-секундном таймауте и 8 воркерах. Ozon даёт примерно в 2 раза меньше из-за более агрессивного антифрода.
Проблемы, на которые наступают все
Первая — провайдер не маршрутизирует /64. Многие хостеры выдают только /128 или /112. Проверяйте до покупки: `ip -6 route` должен показывать ваш префикс как `local` или через `dev lo`.
Вторая — IPv6-адреса из одного /64 могут быть в одной геобазе. Если вы парсите регионы, /64 из Германии даст вам немецкие цены, а не московские. Нужны префиксы из разных ASN или хотя бы разных географических зон.
Третья — DNS. Если резолвер возвращает IPv4-адрес маркетплейса, ваш IPv6 source address не применится. Форсируйте IPv6 через `socket.AF_INET6` или используйте `curl --ipv6`.
Четвёртая — MTU. IPv6 не фрагментирует пакеты на роутерах, минимальный MTU 1280 байт. Если у вас PPPoE с MTU 1492, часть пакетов будет теряться. Проверяйте `ping6 -M do -s 1452`.
Кейс: Ozon и лимит по сессии
Клиент парсил Ozon для сравнения цен. Схема: 50 IPv6-адресов, ротация каждые 5 запросов. Первые 2 часа — 1800 запросов в минуту, всё чисто. На третьем часу — массовые 403.
Причина оказалась не в IP. Ozon ставит cookie `__ozon` с идентификатором сессии и привязывает его к фингерпринту браузера. Даже с новым IP, но тем же cookie — бан.
Решение: полный сброс cookies и localStorage между запросами. В `curl_cffi` это `session.cookies.clear()` плюс новый `Session()` каждые 20 запросов. Пропускная способность упала до 900 запросов в минуту, зато без банов.
Технические детали: Ozon отдаёт `Set-Cookie` с флагами `Secure; HttpOnly; SameSite=Lax`. Cookie живёт 30 дней. Привязка идёт по хешу от `(cookie_id + ja3 + ip_prefix / 48)`. Если меняете только последние 80 бит адреса — Ozon видит тот же /48 и склеивает сессии.
Кейс: WB и региональные цены
Задача — отслеживать динамику цен на 500 SKU в 12 регионах каждые 15 минут. Итого 6000 запросов за цикл.
На старте использовали один /64 из Нидерландов. WB возвращал цены для Амстердама — то есть базовые, без региональной наценки. Клиент не понимал, почему данные не сходятся с реальным сайтом.
Причина: WB определяет регион по IP через геобазу MaxMind. Нидерландский префикс = европейский регион, цены в евро по внутреннему курсу.
Решение: арендовали /48 у российского хостера, разбили на 12 подсетей /52 по регионам. Для каждого региона — свой пул адресов, геобаза показывает нужный город. Плюс параметр `dest` в API, который явно указывает регион. Данные сошлись с реальным сайтом с точностью до копейки.
Кейс: бан по ASN
Парсинг Ozon с пула IPv6 от крупного европейского хостела. Через сутки — блокировка всего /48. Причём не по IP, а по ASN.
Причина: Ozon ведёт чёрный список автономных систем, с которых идёт подозрительная активность. Один клиент хостела нагадил — страдают все.
Решение: разнести трафик по нескольким мелким хостелам с разными ASN. Дешевле, чем residential прокси, но требует больше настройки. Плюс ротация ASN раз в неделю.
Технические детали: проверяйте ASN через `whois -h whois.radb.net` или `bgp.he.net`. Если ваш /48 принадлежит Hetzner (AS24940) или OVH (AS16276) — готовьтесь к повышенному вниманию. Мелкие хостеры с ASN в диапазоне 200000+ живут дольше.
Практика: прокси-сервер на IPv6
Простейшая схема — поднять на VPS с /64 SOCKS5-прокси, который сам ротирует исходящий IPv6. `3proxy` умеет это из коробки:
```
3proxy.cfg
nserver 1.1.1.1
nserver 2606:4700:4700::1111
nscache 65536
timeouts 1 5 30 60 180 1800 15 60
Внешние IPv6 из префикса
external 2a01:4f8:1c1c:abcd::1
external 2a01:4f8:1c1c:abcd::2
external 2a01:4f8:1c1c:abcd::3
SOCKS5 на localhost
socks -p1080 -i127.0.0.1 -e2a01:4f8:1c1c:abcd::1
```
Дальше в Python указываете `socks5://127.0.0.1:1080` и ротируете `external` через API 3proxy или перезапуск с новым конфигом. Проще — использовать `ip6tables` с random SNAT:
```bash
ip6tables -t nat -A POSTROUTING -o eth0 -j SNAT --to-source 2a01:4f8:1c1c:abcd::1-2a01:4f8:1c1c:abcd::ffff
```
Это даёт ротацию на уровне ядра, без перезапуска прокси. Каждое новое соединение получает случайный source address из диапазона.
Что делать с капчей
WB и Ozon иногда кидают капчу при подозрительной активности. С IPv6-ротацией это реже, но случается. Решение — снижать частоту запросов с подозрительных адресов или использовать сервисы разгадывания.
Но честно: если вы дошли до капчи, значит схема работает плохо. Правильная ротация IPv6 + реалистичный JA3 + сброс cookies дают 99%+ успешных запросов без капчи. Если капча появляется — пересматривайте пул адресов, скорее всего ваш /48 в чёрном списке.
Итоги по инфраструктуре
Для парсинга WB хватает /64 от нормального хостера и ротации каждые 10 запросов. Для Ozon нужен /48, ротация каждые 5 запросов, сброс cookies каждые 20, эмуляция Chrome TLS. Для обоих — минимум 3 разных ASN, чтобы не потерять всё сразу.
Бюджет: VPS с /48 в РФ — 300-500 рублей в месяц. Три таких — 1500 рублей. Против \$200-500 за residential прокси-пул. Разница очевидна.
Проверяйте маршрутизацию префикса до покупки. Тестируйте MTU. Логируйте коды ответов и заголовки `X-Ratelimit-*`. И не забывайте, что маркетплейсы обновляют защиту — то, что работало месяц назад, сегодня может дать 403 на первом же запросе.