Выделяет МЕРДЖАБЕЛЬНУЮ часть DomClick-тулинга из запаркованного PR #2005 (BLOCKED #2000 — прод Layer-B скрейпер через прокси режет QRATOR-403): - scripts/domclick_local_runner.py — ручной offline-сборщик с домашнего IP: SERP-enumerate (фронт, BFF-JSON режет QRATOR) + card __SSR_STATE__ + offer-card v3 (AVM price_prediction + sold_similar). --enum-cache (enumerate 1 раз → файл с sentinel → следующий запуск грузит кэш и пропускает enumerate), wait_for_selector (SERP-карточки догидрируются), resume по id. Self-contained. - scripts/ingest_domclick_jsonl.py — JSONL → trade-in БД (save_listings + house-match в SAVEPOINT + save_detail_enrichment, идемпотентно, --limit/--dry-run). - app/services/scrapers/domclick_detail.py + domclick_exceptions.py — card-SSR parse + save_detail_enrichment (нужны ингесту). Импортятся ТОЛЬКО ингестом (operator-run); прод-runtime их не дёргает (dormant в образе). - tests/scrapers/test_domclick_detail.py + обновлённый scripts/README.md. НЕ включает прод Layer-B (tasks/domclick_detail_backfill.py, scheduler-расписание, data/sql/139_*) — остаётся запаркован на PR #2005 до чистого прокси (#2000). БД-схема не меняется (detail-колонки уже в проде, миграции нет). Live-validated: раннер качает ЕКБ-вторичку прямо сейчас (600+ карт, 0 блоков), ингест залил их в прод-БД (house_matched, идемпотентно).
27 lines
1.4 KiB
Python
27 lines
1.4 KiB
Python
"""DomClick-specific exceptions для anti-bot detection."""
|
||
|
||
|
||
class DomClickBlockedError(Exception):
|
||
"""DomClick BFF вернул QRATOR block-страницу (HTTP 200 + block HTML).
|
||
|
||
QRATOR (qrator.net) — WAF/DDoS-защита domclick.ru. Блокирует datacenter-IP
|
||
и возвращает HTML с маркерами: "qrator", "bot_mitigation", "система защиты",
|
||
"403 | домклик", "captcha", "access denied".
|
||
|
||
Обходится через shared mobile proxy (BrowserFetcher(source="domclick") →
|
||
generic provider → мобильный egress).
|
||
"""
|
||
|
||
|
||
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, а не блоком.
|
||
"""
|