- Как прокси-пул ведёт себя при параллельном парсинге одной площадки: где начинаются баны
Содержание
- Анатомия параллельного парсинга
- Как выглядит пул в коде
- Где начинаются баны: триггеры на стороне площадки
- Распределение по подсетям: цифры и реальность
- Кейс: парсинг маркетплейса с 5000 товаров
- Адаптивная ротация: код с обратной связью
- выбираем прокси с наименьшим числом ошибок
- Заголовки и TLS fingerprint
- IPv6: где он помогает и где мешает
- Кейс: IPv6 vs IPv4 на одном сайте
- Кейс: когда пул не спасает
- Мониторинг пула
- Итоги без итогов
Анатомия параллельного парсинга
Прокси-пул — это не просто список IP. Это система распределения запросов, где каждый узел имеет свою историю, репутацию и лимиты. Когда вы запускаете 50 потоков через 20 прокси, вы создаёте динамическую нагрузку на каждую точку выхода. Поведение этой системы зависит от трёх факторов: скорость ротации, разнообразие подсетей и поведенческие паттерны запросов.
Площадки не банят за сам факт прокси. Они банят за аномалии. Если с одного IP за минуту приходит 200 запросов к разным товарам на маркетплейсе — это сигнал. Если с 20 IP приходит по 10 запросов с одинаковыми заголовками и интервалами — это тоже сигнал. Разница в том, что во втором случае срабатывает корреляционный анализ.
Ключевая метрика — не количество запросов в секунду, а энтропия распределения. Чем равномернее нагрузка на прокси и чем разнообразнее поведение, тем дольше живёт пул.
Как выглядит пул в коде
Типичная реализация на Python с ротацией через round-robin:
```python
import asyncio
import aiohttp
from itertools import cycle
PROXIES = [
"http://user:pass@10.0.0.1:8080",
"http://user:pass@10.0.0.2:8080",
"http://user:pass@10.0.0.3:8080",
]
async def fetch(session, url, proxy):
try:
async with session.get(url, proxy=proxy, timeout=10) as resp:
return resp.status, await resp.text()
except Exception as e:
return None, str(e)
async def worker(queue, proxy_pool):
proxy = next(proxy_pool)
async with aiohttp.ClientSession() as session:
while not queue.empty():
url = await queue.get()
status, body = await fetch(session, url, proxy)
queue.task_done()
async def main(urls, concurrency=20):
queue = asyncio.Queue()
for u in urls:
queue.put_nowait(u)
pool = cycle(PROXIES)
tasks = [asyncio.create_task(worker(queue, pool)) for _ in range(concurrency)]
await asyncio.gather(*tasks)
```
Этот код работает, но он наивен. Round-robin не учитывает состояние прокси. Если один узел уже получил 429, следующий запрос через него снова вернёт 429. Нужна обратная связь.
Где начинаются баны: триггеры на стороне площадки
Площадки используют несколько уровней защиты. Первый — rate limit по IP. Второй — rate limit по подсети /24. Третий — поведенческий анализ.
Rate limit по IP срабатывает предсказуемо. У большинства крупных сайтов порог — 60-120 запросов в минуту с одного адреса. Превышение даёт 429 с заголовком `Retry-After`. Если игнорировать этот заголовок и продолжать долбить — временный бан на 5-30 минут.
Rate limit по подсети опаснее. Если ваши 20 прокси находятся в одной /24, площадка видит 20 IP, но считает их как один сегмент. Суммарный лимит делится на всех. 20 прокси × 100 запросов = 2000 запросов с одной /24. Это уже аномалия.
Поведенческий анализ — самый коварный. Он смотрит на:
- Порядок запросов (последовательный обход каталога vs случайный)
- Заголовки (User-Agent, Accept-Language, Referer)
- Тайминги между запросами (слишком ровные — бот)
- Наличие cookies и их согласованность
Пример: сервер на nginx 1.24 с модулем limit_req и limit_conn. Конфиг:
```nginx
limit_req_zone \$binary_remote_addr zone=perip:10m rate=30r/m;
limit_req_zone \$binary_remote_addr zone=persubnet:10m rate=200r/m;
limit_conn_zone \$binary_remote_addr zone=connperip:10m;
server {
listen 80;
location /api/ {
limit_req zone=perip burst=10 nodelay;
limit_conn connperip 5;
proxy_pass http://backend;
}
}
```
Здесь 30 запросов в минуту на IP, burst 10. При 50 потоках через 20 прокси каждый IP получает 2.5 запроса в секунду — это 150 в минуту. Порог превышен в 5 раз. Баны начнутся через 20-30 секунд после старта.
Распределение по подсетям: цифры и реальность
Провайдеры прокси продают пакеты по 10, 50, 100 IP. Проблема в том, что дешёвые пулы часто имеют IP в двух-трёх /24. Проверить это просто:
```bash
curl -s "http://api.example.com/proxies" | jq -r '.[].ip' | \
awk -F. '{print \$1"."\$2"."\$3".0/24"}' | sort | uniq -c | sort -rn
```
Если вывод показывает, что 80 из 100 IP лежат в одной /24 — пул бесполезен для высоконагруженного парсинга. Площадка увидит один сегмент и забанит его целиком.
Хороший пул: минимум 5-7 разных /24, желательно из разных ASN. Провайдеры вроде Bright Data или Oxylabs дают такую ротацию, но цена кусается. Дешёвые пулы — это лотерея.
Кейс: парсинг маркетплейса с 5000 товаров
Задача: собрать цены на 5000 товаров с площадки, которая использует Cloudflare.
Первая попытка: 100 потоков, 50 прокси из одного пула за \$20. Старт в 10:00. Через 40 секунд — 60% запросов возвращают 403 с Cloudflare challenge. Через 2 минуты — все прокси в бане. Причина: все 50 IP в трёх /24, суммарная нагрузка 100 запросов в секунду. Cloudflare зафиксировал аномалию на уровне ASN.
Вторая попытка: те же 50 прокси, но 10 потоков и задержка 1-2 секунды между запросами. Скорость упала до 5 товаров в секунду. 5000 товаров — 16 минут. Банов нет, но время неприемлемо.
Третья попытка: 30 прокси из трёх разных провайдеров (10 + 10 + 10), 20 потоков, ротация с учётом ответов. Прокси, получивший 429 или 403, уходит в cooldown на 5 минут. Скорость — 15 товаров в секунду. 5000 товаров за 5.5 минут. Потери — 3% запросов, которые ушли в retry.
Разница в результате — не в количестве прокси, а в их разнообразии и адаптивной ротации.
Адаптивная ротация: код с обратной связью
Пул должен реагировать на ответы. Простая реализация с состоянием:
```python
import time
from collections import defaultdict
class ProxyPool:
def __init__(self, proxies, cooldown=300):
self.proxies = proxies
self.cooldown = cooldown
self.banned_until = defaultdict(float)
self.stats = defaultdict(lambda: {"ok": 0, "fail": 0})
def get(self):
now = time.time()
available = [p for p in self.proxies if self.banned_until[p] < now]
if not available:
return None
выбираем прокси с наименьшим числом ошибок
return min(available, key=lambda p: self.stats[p]["fail"])
def report(self, proxy, status):
if status in (429, 403, 503):
self.banned_until[proxy] = time.time() + self.cooldown
self.stats[proxy]["fail"] += 1
elif status == 200:
self.stats[proxy]["ok"] += 1
```
Этот код не идеален, но он уже спасает от каскадных банов. Прокси, получивший 429, не используется 5 минут. За это время лимит на стороне площадки сбрасывается.
Дополнительно стоит добавлять jitter к задержкам. Если все запросы идут с интервалом ровно 100 мс — это палево. Разброс 50-200 мс выглядит естественнее.
Заголовки и TLS fingerprint
Современные площадки смотрят не только на IP. TLS fingerprint (JA3) выдаёт клиента. Python requests имеет свой JA3, aiohttp — другой, curl — третий. Если все запросы идут с одинаковым JA3, а User-Agent меняется — это несостыковка.
Проверить свой JA3 можно через `curl -v https://tls.peet.ws/api/all`. Если вы используете разные User-Agent, но один JA3 — площадка может связать запросы.
Решение: использовать библиотеки с рандомизацией TLS, например `curl_cffi`:
```python
from curl_cffi import requests
resp = requests.get(
"https://example.com/api/items",
impersonate="chrome110",
proxies={"https": "http://user:pass@10.0.0.1:8080"}
)
```
`impersonate="chrome110"` подделывает TLS fingerprint под Chrome 110. Это снижает вероятность детекта на уровне handshake.
IPv6: где он помогает и где мешает
IPv6 даёт огромное адресное пространство. Провайдер может выдать /64 — это 18 квинтиллионов адресов. Площадки не могут банить по /64 так же агрессивно, как по /24 в IPv4, потому что это отрежет слишком много легитимных пользователей.
Пример: сервер с IPv6-пулом из /64. Каждый запрос идёт с нового адреса. Rate limit по IP не срабатывает, потому что каждый адрес используется один раз. Но поведенческий анализ всё ещё работает: если 1000 запросов приходят с одной /64 за минуту — это аномалия.
На практике IPv6-пулы дают прирост скорости в 2-3 раза при том же уровне банов, если правильно распределять нагрузку. Провайдер lexic.ml, например, выдаёт /64 на клиента, что позволяет ротировать адреса внутри сегмента без потери репутации.
Кейс: IPv6 vs IPv4 на одном сайте
Сайт с защитой на уровне nginx + fail2ban. IPv4-пул из 50 адресов, 30 потоков. Через 3 минуты — 12 IP в бане fail2ban (порог: 100 запросов за 10 минут с одного IP). Скорость упала на 24%.
Тот же сайт, IPv6-пул из /64, 30 потоков. Каждый запрос с нового адреса. Fail2ban не успевает банить, потому что каждый IP видит 1-2 запроса. Через 10 минут — 0 банов. Скорость стабильная.
Но есть нюанс: некоторые сайты блокируют IPv6 целиком. Если сайт не поддерживает IPv6, запросы просто не пройдут. Проверять нужно заранее:
```bash
curl -6 -I https://example.com
```
Если ответ 200 — IPv6 работает. Если timeout — сайт только на IPv4.
Кейс: когда пул не спасает
Сайт с агрессивным поведенческим анализом. 100 прокси, 50 потоков, рандомные задержки, разные User-Agent. Через 5 минут — все прокси в бане.
Причина: сайт использовал JS-челлендж, который проверял наличие `navigator.webdriver`, `window.chrome` и других свойств браузера. Прокси тут ни при чём — проблема в клиенте. Запросы шли через requests, который не выполняет JS.
Решение: headless-браузер с прокси. Но это уже другая история и другие ресурсы. Прокси-пул решает проблему IP, но не решает проблему fingerprint.
Мониторинг пула
Без мониторинга пул деградирует незаметно. Нужно отслеживать:
- Процент успешных запросов по каждому прокси
- Среднее время ответа
- Количество 429/403 за последние 5 минут
- Распределение по /24
Простой дашборд на Python с выводом в консоль:
```python
def print_stats(pool):
for proxy in pool.proxies:
s = pool.stats[proxy]
total = s["ok"] + s["fail"]
if total == 0:
continue
rate = s["ok"] / total * 100
print(f"{proxy}: {rate:.1f}% ok ({total} req)")
```
Если прокси показывает меньше 80% успеха — его нужно временно исключить. Если меньше 50% — заменить.
Итоги без итогов
Прокси-пул — это живая система. Она требует обратной связи, разнообразия и мониторинга. Баны начинаются не тогда, когда вы превысили лимит, а тогда, когда ваше поведение стало предсказуемым. Разнообразие подсетей, адаптивная ротация, реалистичные заголовки и TLS fingerprint — вот что держит пул в строю. Количество прокси — вторично.