"""DomClick-specific exceptions для anti-bot detection.""" from scraper_kit.proxy_errors import ProxyBanError # ── Канонический список anti-bot маркеров (QRATOR/DataDome) ────────────────── # Единый источник для Layer A (providers/domclick/serp.py) и Layer B # (providers/domclick/detail.py) — были два расходящихся списка (#2636), теперь # оба слоя импортируют этот. Сравнение case-insensitive: caller лоуеркейзит HTML # перед `marker in html_lower`. DOMCLICK_BLOCK_MARKERS: tuple[str, ...] = ( "qrator", "bot_mitigation", "система защиты", "403 | домклик", "captcha", "access denied", "datadome", ) class DomClickBlockedError(ProxyBanError): """DomClick BFF вернул QRATOR block-страницу (HTTP 200 + block HTML). QRATOR (qrator.net) — WAF/DDoS-защита domclick.ru. Блокирует datacenter-IP и возвращает HTML с маркерами: см. `DOMCLICK_BLOCK_MARKERS` в этом модуле ("qrator", "bot_mitigation", "система защиты", "403 | домклик", "captcha", "access denied", "datadome"). Обходится через shared mobile proxy (BrowserFetcher(source="domclick") → generic provider → мобильный egress). Наследует `ProxyBanError` (#2600 п.1, а не голый `Exception`) — ProxyBanError САМ является `Exception`-подклассом (см. proxy_errors.py), так что `except DomClickBlockedError:`/`isinstance(exc, Exception)` по всему коду не меняется; это ДОПОЛНИТЕЛЬНО делает `isinstance(exc, ProxyBanError)` истинным для generic curl-слоя (см. avito_exceptions.AvitoBlockedError для того же паттерна). Прямая двойная база `(Exception, ProxyBanError)` даёт MRO-конфликт (ProxyBanError уже сам наследует Exception) — единственная база решает это чище. `status` (#3196) — HTTP-код ответа площадки, если он известен (`BrowserFetcher.last_response_status`). Одно и то же исключение поднимается и на распознанном маркере, и на сбое браузерного фетча — по счётчикам они были неразличимы, и прогон получал ban_kind='unknown'. Статус разводит их без размножения типов: 403/429 — отказ площадки, 5xx — инфраструктура, None — правда «не знаем» (сайдкар статус не отдал либо фетч не состоялся). """ def __init__(self, *args: object, status: int | None = None) -> None: super().__init__(*args) self.status = status class DomClickParseError(Exception): """DomClick detail-карточка получена (HTTP 200, не block), но SSR-стейт не извлекается / не парсится. Возникает в Layer B (domclick_detail.py) когда: - `window.__SSR_STATE__` не найден в HTML (но это и не challenge-страница); - object-литерал не сбалансирован по скобкам; - json.loads падает после санитайза bare `undefined` → `null`. В отличие от DomClickBlockedError (anti-bot), это сигнал дрейфа схемы карточки или повреждённого ответа — оркестратор считает его parse-failure, а не блоком. """