feat(tradein/domclick): local Layer-B runner — home-IP + new-headless chromium, paced

Self-contained script (playwright chromium, NO app imports) для сбора DomClick EKB
detail с домашнего residential IP (обходит QRATOR — verified live: 2 карточки ok,
repair/areas/priceHistory/egrn/owners собраны). Enumerate via bff (Layer A) + card
__SSR_STATE__ parse (Layer B) → JSONL (resume/idempotent).

KEY: дифференциатор — РЕЖИМ HEADLESS, не только IP. С того же IP старый headless=True
→ 403, new-headless → 200 + SSR. Раннер дефолтит на new-headless.

Пейсинг: рандом 12-30с между карточками, render-jitter 6-10с, human-break каждые
25-40 (60-180с), 1 retry на 403. ~100-150 карточек/час, full ~6400 ≈ 45-65ч.
JSONL ингестится в tradein DB отдельным шагом. НЕ deploy — локальный ops-инструмент.
This commit is contained in:
bot-backend 2026-06-27 18:24:08 +03:00
parent eef23071c5
commit 480ecc90e5
2 changed files with 1239 additions and 0 deletions

View file

@ -271,3 +271,48 @@ Caveats (also printed): CURRENT listings vs PAST deals (not point-in-time —
needs `listing_source_snapshots` #570); asking-median + IQR core only; ДКП = needs `listing_source_snapshots` #570); asking-median + IQR core only; ДКП =
registered price. Pure metric/ratio helpers are unit-tested in registered price. Pure metric/ratio helpers are unit-tested in
`tests/test_backtest_estimator.py` (no DB). `tests/test_backtest_estimator.py` (no DB).
---
## `domclick_local_runner.py` — DomClick EKB вторичка с домашнего IP (offline)
**SELF-CONTAINED** (без `app.*` импортов, stdlib + `playwright.async_api`).
Запускается ОПЕРАТОРОМ на его машине с домашнего/резидентного IP — DomClick
фронтит QRATOR, который банит datacenter/mobile-proxy, но пропускает домашний.
Пишет JSONL (НЕ в БД); заливка в trade-in БД — отдельным ingest-шагом.
Собирает Layer A (BFF search: `/api/offers/v1` + `/api/offers/count/v1`,
room×price-бакетинг под cap offset<2000) + Layer B (detail-карточка,
`window.__SSR_STATE__`: renovation, living/kitchen, priceHistory, egrnData
owners/collateral, sale_type, views/calls, wall/floor type, AVM). Логика
скопирована инлайн из `app/services/scrapers/domclick.py` + `domclick_detail.py`.
### Режим окна (verified live 2026-06-27 на домашнем IP)
QRATOR ловит СТАРЫЙ headless (`launch(headless=True)`) → `403 | Домклик` даже
с домашнего IP. NEW-headless (`--headless=new`) И headed → `200` + полный
`__SSR_STATE__`. Поэтому дефолт = new-headless (окно скрыто, детект проходит);
`--headed` = видимое окно (debug). BFF-enumerate проходит в любом режиме.
### Темп (явное требование — НЕ спалить домашний IP)
- пауза между карточками `--min-delay` 12с / `--max-delay` 30с (`random.uniform`);
- render-wait после goto `--render-min` 6с / `--render-max` 10с;
- «человеческий перерыв» каждые 25-40 карточек → 60-180с;
- на 403 — один ретрай через 45-90с, потом `status="blocked"` (не долбит).
Дефолтный темп ≈ 100-150 карточек/час → полный свод ЕКБ (~6400) ≈ 45-65 ч
(несколько ночей). Скрипт РЕЗЮМИРУЕМ: при старте читает JSONL и пропускает
id с терминальным статусом (ok/blocked/parse_fail).
```bash
cd tradein-mvp/backend
# боевой прогон (дефолтный медленный темп):
python scripts/domclick_local_runner.py --out domclick_ekb.jsonl
# быстрый smoke (маленькие задержки ТОЛЬКО для теста):
python scripts/domclick_local_runner.py --limit 2 --min-delay 3 --max-delay 5 --out dc_test.jsonl
# только перечислить (без detail):
python scripts/domclick_local_runner.py --enumerate-only --out enum.jsonl
```
Запускать python'ом, где установлен playwright (`python -c "import playwright"`).

File diff suppressed because it is too large Load diff