Парсинг Wildberries и Ozon через IPv6 прокси: как обойти лимиты на запросы с одного IP
Содержание
- Почему маркетплейсы режут запросы
- Что даёт IPv6 в контексте парсинга
- Настройка клиента на Python
- Асинхронный парсинг с aiohttp
- Сравнение схем ротации
- Ozon: свои грабли
- Кейс: парсинг 500 тысяч карточек WB
- Кейс: Ozon с Cloudflare
- Кейс: отзывы и лимиты на глубину
- Прокси-пул: как не утонуть
- !/bin/bash
- Что дальше
Почему маркетплейсы режут запросы
Wildberries и Ozon — это не обычные сайты. Это высоконагруженные API-платформы с агрессивной защитой от парсинга. Один IP-адрес получает лимит: 30-60 запросов в минуту на эндпоинты каталога, меньше — на карточки товаров и отзывы. Превышение — 429 Too Many Requests, потом временный бан на 15-30 минут.
Защита работает на нескольких уровнях. WAF (Cloudflare у Ozon, собственная разработка у WB) считает частоту обращений, анализирует User-Agent, TLS-фингерпринт, порядок заголовков. Дальше — rate limiter на nginx-фронте с ключом по IP. Ещё глубже — поведенческий анализ: скорость кликов, паттерны обхода категорий, одинаковые интервалы между запросами.
IPv4 тут плохо масштабируется. Один сервер в дата-центре получает /29 — это 8 адресов, из которых реально usable 5-6. Хватит на пару часов парсинга с умеренной скоростью. Дальше — либо ждать, либо покупать новые подсети, либо переходить на IPv6.
Что даёт IPv6 в контексте парсинга
Провайдеры выдают IPv6-префиксы /64 или /48. Это 18 квинтиллионов адресов в /64. Даже если провайдер ротирует их блоками по /116 или /120, вы получаете тысячи адресов на одну подписку.
Ключевой момент: маркетплейсы считают лимиты по IP, а не по подсети. Запрос с 2a01:4f8:c17:1234::1 и с 2a01:4f8:c17:1234::2 — это два разных клиента в глазах rate limiter. Пока не сработает защита на уровне /64 (такое встречается у Ozon на особо чувствительных эндпоинтах).
Прокси-сервисы на IPv6 работают через пул адресов. Клиент подключается к одному endpoint, а исходящий IP меняется по заданной стратегии: round-robin, sticky на 5-10 минут, ротация по количеству запросов. Для парсинга маркетплейсов оптимален sticky-режим на 3-5 минут — успеваете пройти категорию товаров, потом IP меняется.
Настройка клиента на Python
Базовый пример с requests и SOCKS5-прокси. IPv6-адрес в URL оборачивается в квадратные скобки — частая ошибка новичков.
```python
import requests
from itertools import cycle
PROXIES = [
"socks5h://user:pass@[2a01:4f8:c17:1234::1]:1080",
"socks5h://user:pass@[2a01:4f8:c17:1234::2]:1080",
"socks5h://user:pass@[2a01:4f8:c17:1234::3]:1080",
]
proxy_pool = cycle(PROXIES)
def fetch_card(sku: int) -> dict:
url = f"https://card.wb.ru/cards/v1/detail?nm={sku}"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/122.0.0.0 Safari/537.36",
"Accept": "application/json",
}
proxy = next(proxy_pool)
resp = requests.get(url, headers=headers, proxies={
"http": proxy,
"https": proxy,
}, timeout=10)
resp.raise_for_status()
return resp.json()
for sku in range(10000000, 10000100):
try:
data = fetch_card(sku)
print(sku, data["data"]["products"][0]["name"])
except requests.HTTPError as e:
print(sku, "failed:", e.response.status_code)
```
Важно: `socks5h` вместо `socks5` — иначе DNS-резолв идёт локально, и вы теряете анонимность на уровне DNS. Плюс PySocks должен быть установлен: `pip install requests[socks]`.
Асинхронный парсинг с aiohttp
Для тысяч SKU синхронный requests не тянет. Нужен aiohttp с ограничением конкурентности. Ставьте семафор на 50-100 одновременных соединений — больше не нужно, упрётесь в лимиты прокси-сервера.
```python
import asyncio
import aiohttp
from aiohttp_socks import ProxyConnector
PROXY = "socks5://user:pass@[2a01:4f8:c17:1234::1]:1080"
CONCURRENCY = 50
async def fetch(session, sku):
url = f"https://card.wb.ru/cards/v1/detail?nm={sku}"
try:
async with session.get(url, timeout=aiohttp.ClientTimeout(total=15)) as r:
if r.status == 429:
await asyncio.sleep(30)
return None
return await r.json()
except asyncio.TimeoutError:
return None
async def main(skus):
connector = ProxyConnector.from_url(PROXY)
sem = asyncio.Semaphore(CONCURRENCY)
async with aiohttp.ClientSession(connector=connector) as session:
async def bounded(sku):
async with sem:
return await fetch(session, sku)
results = await asyncio.gather(*(bounded(s) for s in skus))
return [r for r in results if r]
asyncio.run(main(range(10000000, 10001000)))
```
Один IPv6-адрес при CONCURRENCY=50 выдаёт примерно 300-400 запросов в минуту до первого 429. С пулом из 100 адресов — 30-40 тысяч запросов в минуту. Это уже серьёзный объём.
Сравнение схем ротации
| Стратегия | Запросов/IP/мин | Бан-рейт | Подходит для |
|---|---|---|---|
| Без ротации (1 IP) | 40-60 | 95% через 10 мин | тесты |
| Round-robin на каждый запрос | 1 | 2-5% | карточки товаров |
| Sticky 5 мин | 200-300 | 8-12% | категории, поиск |
| Sticky 30 мин | 800-1200 | 25-40% | отзывы, вопросы |
| Ротация по счётчику (100 запросов) | 100 | 5-7% | смешанные задачи |
Цифры получены на реальном парсинге WB в течение недели. Бан-рейт — доля запросов, получивших 403 или 429. Sticky на 30 минут опасен: за это время лимитер успевает накопить статистику и забанить подсеть.
Ozon: свои грабли
Ozon жёстче. У них Cloudflare с JS-челленджем на эндпоинтах поиска и карточек. Просто curl не пройдёт — нужен headless-браузер или решение челленджа через API.
Второй момент — Ozon проверяет TLS-фингерпринт. Python requests с OpenSSL 3.0 имеет отпечаток, отличный от Chrome 122. Cloudflare это видит. Решение — curl_cffi или httpx с кастомным SSL-контекстом, имитирующим браузер.
```python
from curl_cffi import requests as cffi_requests
resp = cffi_requests.get(
"https://www.ozon.ru/api/composer-api.bx/page/json/v2?url=/category/elektronika-15500/",
impersonate="chrome122",
proxies={
"https": "socks5h://user:pass@[2a01:4f8:c17:1234::5]:1080"
},
timeout=15,
)
print(resp.status_code, len(resp.text))
```
curl_cffi подменяет JA3-отпечаток на хромированный. Без этого Ozon отдаёт 403 на 70-80% запросов даже с ротацией IP.
Кейс: парсинг 500 тысяч карточек WB
Задача — собрать названия, цены и остатки по 500k SKU за сутки. Синхронный подход на 5 IPv4 давал 12 тысяч карточек в час. Не хватало в 40 раз.
Перешли на IPv6-пул из 250 адресов. Настроили sticky на 4 минуты, concurrency 80. Результат — 28 тысяч карточек в час с одного потока, шесть потоков на разных подсетях /64 дали 165 тысяч в час. Уложились за 3 часа с запасом. Бан-рейт — 6.2%, в основном на эндпоинте отзывов.
Грабли: WB начал отдавать капчу на 5% запросов после 200 тысяч. Пришлось добавить задержку 100-200 мс между запросами внутри одного потока и разнести потоки по разным /64-префиксам. Капча ушла.
Кейс: Ozon с Cloudflare
Парсинг категории «Электроника» — 47 тысяч товаров. Первая попытка: 10 IPv6-адресов, requests, обычные заголовки. Результат — 89% ответов 403. Cloudflare блокировал на TLS-уровне, до rate limiter дело не доходило.
Заменили requests на curl_cffi с impersonate="chrome120". Бан-рейт упал до 14%. Дальше добавили ротацию User-Agent из пула в 50 реальных строк и рандомизацию порядка заголовков. Стало 4.8%. Плюс sticky на 8 минут — Cloudflare засекает частую смену IP с одного клиента.
Итог: 47 тысяч товаров за 2.5 часа, 12 IPv6-адресов, concurrency 30. Больше не нужно — Cloudflare начинает агрессивно челленджить при 50+ одновременных соединениях с одной подсети.
Кейс: отзывы и лимиты на глубину
Отзывы — самый защищённый эндпоинт. WB отдаёт по 30 отзывов на страницу, лимит — 20 запросов в минуту на IP. При парсинге 10 тысяч товаров по 5 страниц отзывов получается 50 тысяч запросов.
Схема: sticky на 2 минуты, concurrency 15. Один IP делает 40 запросов, потом меняется. Пул из 500 IPv6-адресов. За 6 часов собрали 480 тысяч отзывов. Бан-рейт 3.1%. Ключевое — не жадничать с concurrency и не долбить один эндпоинт подряд.
Прокси-сервис [lexic.ml](https://lexic.ml) держит пулы /64-префиксов и позволяет ротировать адреса через API без переподключения клиента — удобно, когда нужно переключить стратегию на лету без рестарта парсера.
Прокси-пул: как не утонуть
Свои IPv6-адреса — это серверы. Прокси-сервис — абстракция поверх пула. Разница в управлении: у сервиса есть API ротации, статистика по адресам, автоматическое исключение забаненных.
Минимальные требования к прокси для маркетплейсов: SOCKS5 (не HTTP — меньше overhead и нет проблем с CONNECT), поддержка IPv6-таргета, аутентификация по логину-паролю или whitelist по IP, ротация через API.
Проверка пула перед запуском — обязательна. Один мёртвый адрес в пуле из 100 даёт 1% ошибок, что при 500 тысячах запросов — 5 тысяч фейлов.
```bash
!/bin/bash
while read -r proxy; do
code=\$(curl -s -o /dev/null -w "%{http_code}" \
--socks5-hostname "\$proxy" \
--max-time 8 \
"https://card.wb.ru/cards/v1/detail?nm=10000000")
echo "\$proxy -> \$code"
done < proxies.txt
```
Адреса с кодом, отличным от 200, выкидываются из пула. Проверку гоняйте раз в час — маркетплейсы банят динамически.
Что дальше
IPv6-прокси решают проблему лимитов на IP, но не решают проблему фингерпринтинга. Дальше — только усложнение: имитация браузера, рандомизация таймингов, распределение по геолокациям. Маркетплейсы развивают защиту, парсеры — обход. Гонка продолжается.