Если парсер Wildberries или Ozon внезапно начал отдавать пустые страницы, таймауты и 403-е ошибки — проблема не всегда в самой площадке. Чаще всего часть IP-адресов в пуле прокси уже «протухла»: их забанили, замедлили или посадили под капчу. Разбираем, по каким метрикам отличить рабочий прокси от мёртвого и как автоматизировать отбраковку без ручного контроля.
Почему пул прокси деградирует со временем
Любой пул прокси — даже купленный у надёжного провайдера — со временем теряет часть рабочих адресов. Причины разные: антифрод-системы Wildberries, Ozon и Авито накапливают репутационные метки на IP, часть адресов попадает в чёрные списки после массовых запросов других клиентов провайдера (если прокси shared), датацентровые диапазоны банятся пачками по подсетям, а резидентные IP иногда просто уходят из пула, потому что реальный пользователь отключил устройство.
Если не чистить пул, доля «мёртвых» прокси растёт нелинейно: сначала 5%, через неделю активного парсинга — 20-30%. Парсер начинает тратить ресурсы на повторные попытки через нерабочие IP, растёт среднее время сбора одной страницы, а в логах накапливаются ошибки, которые маскируют реальные проблемы (например, изменение вёрстки сайта). Без системы отбраковки вы либо держите пул раздутым «про запас», либо регулярно получаете провалы в сборе данных.
Задача — не просто фиксировать факт ошибки, а классифицировать её: временная перегрузка сервера, бан конкретного IP, региональная блокировка или проблема на стороне парсера (неверные заголовки, устаревшие куки). От типа ошибки зависит, нужно ли выкидывать прокси из пула навсегда, ставить его в «карантин» на время или просто повторить запрос.
Ключевые метрики ошибок для отбраковки
Чтобы отличить временный сбой от действительно мёртвого прокси, нужно собирать несколько метрик по каждому IP, а не ориентироваться на единичный HTTP-статус. Вот базовый набор, который стоит логировать при каждом запросе:
| Метрика | Что сигнализирует | Действие |
|---|---|---|
| HTTP 403 / 429 | Бан или rate-limit на IP | Карантин 30-60 минут, при повторе — удаление |
| Таймаут соединения | Прокси перегружен или мёртв | 3 таймаута подряд — удаление из пула |
| CAPTCHA в ответе | IP попал под подозрение антибота | Снизить частоту запросов или заменить IP |
| Время ответа > 5 сек | Деградация скорости на стороне прокси | Понижение приоритета в пуле |
| Размер ответа ниже среднего | Отдана заглушка/страница блокировки | Доп. проверка содержимого перед принятием |
| Доля успешных запросов (success rate) | Интегральная оценка качества IP | Ниже 70% за 50 запросов — удаление |
Важно не путать коды ошибок сайта с сетевыми ошибками. Например, 404 — это не проблема прокси, а отсутствующий товар на Wildberries. А вот если один и тот же IP регулярно получает 403 там, где другие IP получают 200 — это явный признак точечного бана именно этого адреса.
Алгоритм скоринга прокси
Вместо бинарного «рабочий / нерабочий» удобнее считать для каждого IP скор от 0 до 100, который постепенно снижается при ошибках и восстанавливается при успешных запросах. Это позволяет не выкидывать прокси после одного случайного сбоя (сервер маркетплейса мог просто подвиснуть на секунду), но быстро убирать те IP, что систематически отдают мусор.
Логика простая: успешный запрос с кодом 200 и валидным содержимым добавляет +2 к скору (но не выше 100). Таймаут — минус 15. Код 403/429 — минус 25. CAPTCHA в ответе — минус 20. Если скор падает ниже порога (например, 30), прокси уходит в карантин на фиксированное время, а если после возврата из карантина скор снова падает ниже 10 — IP удаляется из пула окончательно и логируется для анализа.
Такой подход похож на систему репутации в антидетект-браузерах вроде Dolphin Anty или AdsPower, где аккаунты имеют «здоровье» в зависимости от активности. Та же механика отлично работает и для IP-адресов в парсинге.
Код: ProxyPool с автоматической отбраковкой на Python
Ниже — рабочий каркас класса для управления пулом прокси с учётом метрик ошибок. Его можно подключить к любому парсеру на requests, httpx или Scrapy, заменив логику получения прокси на вызов pool.get_proxy().
import time
import random
from dataclasses import dataclass, field
from typing import Optional
@dataclass
class ProxyStats:
address: str
score: int = 100
quarantine_until: float = 0.0
total_requests: int = 0
total_success: int = 0
consecutive_timeouts: int = 0
is_dead: bool = False
@property
def success_rate(self) -> float:
if self.total_requests == 0:
return 1.0
return self.total_success / self.total_requests
class ProxyPool:
def __init__(self, proxies: list[str],
min_score: int = 30,
dead_score: int = 10,
quarantine_seconds: int = 1800):
self.stats = {p: ProxyStats(address=p) for p in proxies}
self.min_score = min_score
self.dead_score = dead_score
self.quarantine_seconds = quarantine_seconds
def _is_available(self, stats: ProxyStats) -> bool:
if stats.is_dead:
return False
if stats.quarantine_until > time.time():
return False
return True
def get_proxy(self) -> Optional[str]:
available = [s for s in self.stats.values() if self._is_available(s)]
if not available:
return None
# сортируем по скору, берём случайный из топ-30%, чтобы не долбить один IP
available.sort(key=lambda s: s.score, reverse=True)
top_pool = available[:max(1, len(available) // 3)]
return random.choice(top_pool).address
def report_success(self, proxy: str):
s = self.stats[proxy]
s.total_requests += 1
s.total_success += 1
s.consecutive_timeouts = 0
s.score = min(100, s.score + 2)
def report_timeout(self, proxy: str):
s = self.stats[proxy]
s.total_requests += 1
s.consecutive_timeouts += 1
s.score -= 15
if s.consecutive_timeouts >= 3:
self._quarantine(s)
self._check_dead(s)
def report_blocked(self, proxy: str, captcha: bool = False):
s = self.stats[proxy]
s.total_requests += 1
s.score -= 20 if captcha else 25
self._quarantine(s)
self._check_dead(s)
def _quarantine(self, s: ProxyStats):
if s.score < self.min_score:
s.quarantine_until = time.time() + self.quarantine_seconds
def _check_dead(self, s: ProxyStats):
if s.score <= self.dead_score:
s.is_dead = True
def stats_report(self) -> list[dict]:
return [
{
"proxy": s.address,
"score": s.score,
"success_rate": round(s.success_rate, 2),
"dead": s.is_dead,
}
for s in self.stats.values()
]
В реальном парсере вызовы report_success, report_timeout и report_blocked вставляются прямо в обработчик ответа после каждого HTTP-запроса. Пример интеграции с requests:
import requests
def fetch(url: str, pool: ProxyPool):
proxy = pool.get_proxy()
if not proxy:
raise RuntimeError("Нет доступных прокси в пуле")
proxies = {"http": proxy, "https": proxy}
try:
resp = requests.get(url, proxies=proxies, timeout=8)
except requests.exceptions.Timeout:
pool.report_timeout(proxy)
return None
if resp.status_code in (403, 429):
pool.report_blocked(proxy)
return None
if "captcha" in resp.text.lower():
pool.report_blocked(proxy, captcha=True)
return None
pool.report_success(proxy)
return resp
Такой подход снимает нагрузку с ручного мониторинга: вы один раз настраиваете пороги, а дальше пул сам «выздоравливает», выталкивая мёртвые адреса и возвращая из карантина те, что временно подвисли.
Пороговые значения для бана IP
Универсальных цифр не существует — пороги зависят от площадки и агрессивности её антибота. Но есть ориентиры, которые хорошо работают на практике при парсинге Wildberries, Ozon и Авито:
- Success rate ниже 70% на выборке от 30-50 запросов — повод удалить IP, даже если текущий скор ещё не критичный.
- 3 таймаута подряд — почти всегда означает, что прокси физически недоступен или перегружен, нет смысла держать его в активном пуле.
- CAPTCHA чаще 1 раза на 20 запросов — IP уже под подозрением у антибота, лучше снизить частоту обращений или заменить адрес.
- Среднее время ответа выше 2x от медианы пула — сигнал деградации, не критичный, но повод понизить приоритет при выборе.
Карантин на 30-60 минут обычно достаточен для датацентровых и резидентных прокси, чтобы антибот «забыл» о временной подозрительной активности. Для мобильных прокси ситуация другая: там IP меняется при смене соты оператором, поэтому карантин можно делать короче — 10-15 минут, так как после ротации это физически уже другой адрес.
Healthcheck и ротация в фоне
Помимо реактивной отбраковки (по факту ошибок в основном потоке парсинга), полезно гонять фоновый healthcheck — отдельный процесс, который раз в несколько минут простукивает прокси лёгкими запросами на нейтральный URL (например, httpbin.org/ip) и обновляет статистику пула до того, как прокси попадёт в боевой запрос к Wildberries или Ozon.
import threading
def healthcheck_loop(pool: ProxyPool, interval: int = 300):
while True:
for proxy in list(pool.stats.keys()):
try:
r = requests.get(
"https://httpbin.org/ip",
proxies={"http": proxy, "https": proxy},
timeout=5
)
if r.status_code == 200:
pool.report_success(proxy)
else:
pool.report_blocked(proxy)
except requests.exceptions.RequestException:
pool.report_timeout(proxy)
time.sleep(interval)
threading.Thread(target=healthcheck_loop, args=(pool,), daemon=True).start()
Такой фоновый процесс особенно важен, если пул большой (сотни IP) и парсинг идёт не непрерывно, а пачками — без healthcheck часть прокси просто успевает «протухнуть» между запусками, и первые минуты парсинга тратятся на отбраковку вживую, вместо того чтобы сразу работать с проверенными адресами.
Какой тип прокси выбрать для парсинга маркетплейсов
Выбор типа прокси напрямую влияет на то, как часто придётся запускать отбраковку и насколько агрессивными будут пороги. Для стабильного парсинга Wildberries и Ozon, где антибот анализирует поведенческие паттерны и репутацию подсети, лучше всего показывают себя резидентные прокси — у них IP принадлежат реальным провайдерам домашнего интернета, и антибот значительно реже блокирует их пачками по подсети.
Для более простых и менее защищённых источников (например, сбор публичных данных с Авито или мониторинг цен на несложных витринах) часто достаточно прокси дата-центров — они быстрее и дешевле, что критично при больших объёмах запросов, но и банятся чуть чаще, поэтому пороги отбраковки для них стоит делать строже.
| Тип прокси | Скорость отбраковки | Когда использовать |
|---|---|---|
| Дата-центр | Высокая, подсети банятся пачками | Простые витрины, большие объёмы, невысокая защита |
| Резидентные | Средняя, IP банятся точечно | Wildberries, Ozon, сайты с антиботом |
| Мобильные | Низкая, IP меняется автоматически оператором | Жёстко защищённые источники, высокочастотный парсинг |
На практике многие команды используют гибридный пул: основная масса запросов идёт через резидентные IP, а при массовых банах подключаются мобильные прокси как «аварийный» резерв, поскольку их смена IP происходит естественным образом через ротацию сотовой сети и не требует ручного вмешательства.
Заключение
Пул прокси без системы отбраковки неизбежно деградирует, и чем дольше он работает без чистки, тем больше ресурсов парсер тратит впустую на мёртвые IP. Скоринговая модель с учётом HTTP-кодов, таймаутов, CAPTCHA и success rate позволяет автоматически выявлять проблемные адреса и держать пул в рабочем состоянии без ручного контроля. Код из этой статьи можно адаптировать под любой стек — requests, httpx, Scrapy или собственный асинхронный парсер.
Если вы только собираете пул для парсинга маркетплейсов и хотите снизить частоту банов ещё на старте, рекомендуем присмотреться к резидентным прокси — они дают заметно более высокий success rate на защищённых площадках вроде Wildberries и Ozon по сравнению с обычными датацентровыми адресами.