"""Avito-specific exceptions для anti-bot detection.""" from scraper_kit.proxy_errors import ProxyBanError class AvitoError(Exception): """Base для всех Avito scraper exceptions.""" class AvitoBlockedError(AvitoError, ProxyBanError): """HTTP 403 от Avito — IP-level block detected. Дополнительно наследует `ProxyBanError` (#2600 п.1) — генерик curl-путь (`providers/_proxy.py::curl_proxy_url`) распознаёт это как «площадка забанила прокси» через `isinstance(exc, ProxyBanError)`, не импортируя avito_exceptions напрямую. `except AvitoBlockedError:` во всём остальном коде не меняется — mixin ничего не убирает, только добавляет. """ class AvitoRateLimitedError(AvitoError): """HTTP 429 от Avito — rate limit triggered.""" class AvitoSidecarUnavailableError(AvitoRateLimitedError): """НАШ браузерный сайдкар не отдал страницу — площадка ни при чём (#2686). Поднимается только там, где отказ ПОРОЖДЁН нашей инфраструктурой: sidecar вернул 503 «browser unavailable», упал внутри (Page.goto Timeout, браузер не поднялся) либо соединение до него оборвалось (ReadTimeout/ConnectError). Это отличается от AvitoBlockedError (площадка показала firewall) и от честного HTTP 429 самой площадки, хотя все три раньше давали ОДИН статус 'banned' — 92 прогона из 114 (81%) за 45 суток были нашим сайдкаром, и по этому статусу приняли решение замедлить сбор вдвое (миграция 206). Наследует AvitoRateLimitedError НАМЕРЕННО: всё поведение прогона обязано остаться прежним (`except (AvitoBlockedError, AvitoRateLimitedError)` в pipeline/full-load/backfill ловят его без изменений → mark_banned → done_buckets-чекпоинт сохраняется). Разводятся не пути, а ДИАГНОЗ: pipeline пишет его в scrape_runs.ban_kind ('infra' vs 'platform'). """ class AvitoListingGoneError(AvitoError): """Объявление удалено / вернуло 404 (страница «Ошибка 404» вместо item-view). Отдельный сигнал от AvitoBlockedError/AvitoRateLimitedError: 404 — НЕ анти-бот deflect, а мёртвый листинг (его незачем ротировать/ретраить). Наследует общий AvitoError, но НЕ AvitoBlockedError — чтобы backfill-ловушка `except (AvitoBlockedError, AvitoRateLimitedError)` его НЕ перехватывала и не крутила consecutive-block breaker на мёртвых координатных дырах (run 458: 5 dead → abort). """ class AvitoContentBlockedError(AvitoBlockedError): """HTTP 200 от Avito, но 0 карточек на первой странице — content-block / captcha. Наследует AvitoBlockedError, чтобы существующие except-блоки на AvitoBlockedError продолжали ловить этот сигнал (mark_banned, pipeline abort и т.д.). """ class AvitoWarmupCookiesMissingError(AvitoBlockedError): """Прогрев вернул HTTP 200 без firewall-маркеров, но антибот-cookies (`__zzatw-*`/`cfidsw-*`) НЕ появились в сессии (#3034). Тихий soft-block, не пойманный `_is_firewall_page`/`_is_detail_soft_block`: страница отдалась, но сессия не помечена площадкой как прогретая — детейл-батч на ней сжигал бы прокси на обречённых запросах (403 на каждой карточке). Наследует AvitoBlockedError, чтобы существующие `except (AvitoBlockedError, AvitoRateLimitedError)` (ban_kind, proxy-ротация, consecutive-block breaker в pipeline/backfill) отработали как на обычном блоке площадки, без отдельного except-блока на call-сайтах. """