feat(tradein/scripts): local Playwright runners для Cian backfill (bypass server-IP anti-bot) #553

Merged
lekss361 merged 1 commit from feat/local-cian-runners into main 2026-05-24 20:01:00 +00:00
Owner

What

Добавляет tradein-mvp/scripts/local-cian/ — production-ready локальные Playwright scrapers которые backfill'ят Cian historical data + valuation с домашнего IP разработчика. Решает проблему обнаруженную при попытке server-side backfill (PR #535/#539): Cian блокирует server-IP anti-bot'ом → возвращает captcha challenge на все requests с tradein-backend. Локальный запуск через real Chrome + persistent profile успешно обходит.

Files (4)

  • playwright_history.py — production-ready. Backfill offer_price_history. Real Chrome через Playwright, paginate id > last_id, JS extract defaultState.offerData.priceChanges → INSERT с UNIQUE дедупом. Темп ~25 listings/min, идёт прямо сейчас на машине разработчика, 1500+ rows записано.
  • playwright_valuation.py — best-effort. Backfill external_valuations через Calculator URL. Persistent Chrome profile для стабильности сессии. Auto-reload cookies из БД при auth loss. Skip листинги где Cian не отдаёт оценку (новостройки без аналогов).
  • upload_cookies_local.py — записывает Cian session cookies в cian_session_cookies через прямой psycopg connect к prod БД (через SSH tunnel), обходит broken verify_session (httpx fingerprint не проходит, см. PR #543 context).
  • README.md — setup (psycopg, playwright, chromium), SSH tunnel инструкции, как залить cookies, usage examples, limitations.

Why

  • tradein-backend локально вызывает cian_detail.fetch_detail через curl_cffi chrome120 — но Cian определяет паттерн и блокирует → defaultState extraction failed для всех URL с прод-IP. Backfill endpoint (PR #535) фактически не работает в проде.
  • Локальный Playwright использует real Chromium с разогретым profile → Cian считает это нормальным пользователем.
  • Cookies в cian_session_cookies для backend Calculator scraper тоже не работают — этим скриптом мы их инжектим напрямую в Playwright context.

Не покрывает (out of scope)

  • Полный Calculator backfill — требует agent-wizard UI flow (нужно вводить год дома, материал, ремонт для каждой кв). Не масштабируется. Собирается только для домов с готовой оценкой в SSR state (~30% от выборки).
  • Houses backfill для houses_price_dynamicshouses table не имеет cian_zhk_url column (TODO mig 065, см. PR #535).

Test plan

  • No secrets in diff (verified: only placeholders for DMIR_AUTH/_CIAN_GK/password)
  • Manual smoke: playwright_history.py написал 1500+ rows в offer_price_history за час
  • playwright_valuation.py написал 6 rows для домов с оценкой
  • Optional: пересоздать chrome_profile/ локально + перепрогнать на свежей машине

Follow-ups

  • Weekly cron на разработческой машине? Или Github Actions runner с residential proxy + Playwright?
  • Если хочется полный Calculator backfill — research API endpoint через network capture в headed Playwright (POST /cian-api/.../estimate).

References: 6-PR Cian activation series (#523/#525/#530/#535/#539/#543). Vault: decisions/Decision_Cian_History_Valuation_Activation_May24.md.

## What Добавляет `tradein-mvp/scripts/local-cian/` — production-ready локальные Playwright scrapers которые backfill'ят Cian historical data + valuation с домашнего IP разработчика. Решает проблему обнаруженную при попытке server-side backfill (PR #535/#539): **Cian блокирует server-IP** anti-bot'ом → возвращает captcha challenge на все requests с tradein-backend. Локальный запуск через real Chrome + persistent profile успешно обходит. ## Files (4) - `playwright_history.py` — production-ready. Backfill `offer_price_history`. Real Chrome через Playwright, paginate `id > last_id`, JS extract `defaultState.offerData.priceChanges` → INSERT с UNIQUE дедупом. **Темп ~25 listings/min, идёт прямо сейчас на машине разработчика, 1500+ rows записано.** - `playwright_valuation.py` — best-effort. Backfill `external_valuations` через Calculator URL. Persistent Chrome profile для стабильности сессии. Auto-reload cookies из БД при auth loss. Skip листинги где Cian не отдаёт оценку (новостройки без аналогов). - `upload_cookies_local.py` — записывает Cian session cookies в `cian_session_cookies` через прямой psycopg connect к prod БД (через SSH tunnel), обходит broken `verify_session` (httpx fingerprint не проходит, см. PR #543 context). - `README.md` — setup (psycopg, playwright, chromium), SSH tunnel инструкции, как залить cookies, usage examples, limitations. ## Why - `tradein-backend` локально вызывает `cian_detail.fetch_detail` через `curl_cffi chrome120` — но Cian определяет паттерн и блокирует → `defaultState extraction failed` для всех URL с прод-IP. Backfill endpoint (PR #535) фактически не работает в проде. - Локальный Playwright использует **real** Chromium с разогретым profile → Cian считает это нормальным пользователем. - Cookies в `cian_session_cookies` для backend Calculator scraper тоже не работают — этим скриптом мы их инжектим напрямую в Playwright context. ## Не покрывает (out of scope) - Полный Calculator backfill — требует agent-wizard UI flow (нужно вводить год дома, материал, ремонт для каждой кв). Не масштабируется. Собирается только для домов с готовой оценкой в SSR state (~30% от выборки). - Houses backfill для `houses_price_dynamics` — `houses` table не имеет `cian_zhk_url` column (TODO mig 065, см. PR #535). ## Test plan - [x] No secrets in diff (verified: only placeholders for DMIR_AUTH/_CIAN_GK/password) - [x] Manual smoke: `playwright_history.py` написал 1500+ rows в `offer_price_history` за час - [x] `playwright_valuation.py` написал 6 rows для домов с оценкой - [ ] Optional: пересоздать `chrome_profile/` локально + перепрогнать на свежей машине ## Follow-ups - Weekly cron на разработческой машине? Или Github Actions runner с residential proxy + Playwright? - Если хочется полный Calculator backfill — research API endpoint через network capture в headed Playwright (POST /cian-api/.../estimate). References: 6-PR Cian activation series (#523/#525/#530/#535/#539/#543). Vault: `decisions/Decision_Cian_History_Valuation_Activation_May24.md`.
lekss361 added 1 commit 2026-05-24 19:52:14 +00:00
Author
Owner

Deep Code Review — PR #553

Status: APPROVE
Files: 4 (all new, P2 — local-only scripts) · +831 / -0 · scope: tradein-mvp/scripts/local-cian/
SHA verified: 5555ea2f6750b564e170c0f37ae1300e3306e6df

Scope context

Local-machine Playwright backfill runners для Cian — обход server-IP anti-bot. Authorized by vault decisions/Decision_Cian_History_Valuation_Activation_May24.md + runbook runbooks/cian_local_playwright_backfill.md (already references this PR #553). User reported 1500+ rows backfilled successfully за час — production-validated.

This is not a server-side scraper — pure dev tooling что runs from user's workstation. The feedback_playwright_mcp_not_npx rule applies к qa-tester smoke tests, не к standalone backfill scripts (которые legitimately нуждаются в local Playwright runtime).

Security — clean

  • Никаких hardcoded credentials в diff. Все cookies/userId — placeholders (<PASTE_FROM_BROWSER_DEVTOOLS>, 999999999 # TODO).
  • TRADEIN_DB_DSN / COOKIE_ENCRYPTION_KEY — env vars, README указывает source (vault meta/00_credentials.md).
  • Cookies хранятся encrypted в БД через pgp_sym_encrypt (matches existing cian_session_cookies contract from migration 027).
  • SQL injection safe: все INSERT через %s параметры, никакого f-string interpolation.
  • Сырые значения cookies нигде не логируются (только counts + DMIR_AUTH={yes/no}).

Correctness

  • psycopg v3 + CAST(:x AS type) (e.g. CAST(%s AS bigint) / CAST(%s AS numeric)) — соответствует backend rule.
  • INSERT shape mirrors cian_valuation._save_to_cache (line 418-462 of backend/app/services/scrapers/cian_valuation.py).
  • Idempotency:
    • history: ON CONFLICT (listing_id, change_time) DO NOTHING matches offer_price_history_listing_change_uq (migration 047)
    • valuation: ON CONFLICT (source, cache_key) DO UPDATE matches existing service pattern
    • cookies upload: ON CONFLICT (account_user_id) matches migration 027 PK
  • Pagination cursor id > last_id ASC — устойчиво к 0-change результатам, не зацикливается.
  • SIGINT handler — graceful shutdown с commit pending batch.

Anti-bot / rate-limiting respect

  • History --delay 2 (30 req/min) + Valuation --delay 5 (12 req/min) — conservative для logged-in Cian session.
  • Real Chrome UA + viewport + ru-RU locale + persistent profile + --disable-blink-features=AutomationControlled — standard fingerprint stabilization.
  • CAPTCHA detection (location.href.includes('captcha')) → STOP execution, prompts user for --headed manual solve.
  • No parallel batches в одном скрипте.

Minor (non-blocking)

  • upload_cookies_local.py: _yasc/_ym_isad/uxfb_card_satisfaction defaults как литерал "<optional>" — если user забыл удалить/заполнить, эти cookies загрузятся как мусорные строки. Не блокирует (Cian игнорирует unknown values), но в README стоит указать «удалите optional поля если не заполняете».
  • playwright_valuation.py INSERT: filters_hash всегда NULL (hardcoded %s, NULL), хотя existing service-layer пишет реальный sale.filtersHash. Backward compat — старые записи получат NULL filters_hash. Не критично для backfill, но можно подобрать sale.get("filtersHash") без overhead.
  • chrome_profile/ hardcoded в Windows-only path — README указывает CIAN_PROFILE_DIR override для override.

Vault cross-check

  • decisions/Decision_Cian_History_Valuation_Activation_May24.md — авторизует full 6-PR sequence (#523/#525/#530/#535/#539/#543) + PR #553 как post-launch tooling.
  • runbooks/cian_local_playwright_backfill.md — уже описывает exactly эти скрипты и process. README в PR строго соответствует runbook.
  • DDL contracts проверены: 023+047 (offer_price_history), 026+029+044 (external_valuations), 027 (cian_session_cookies) — все ссылки live.

Positive

  • Документация (README + inline docstrings) исчерпывающая. README указывает SSH tunnel setup, env vars, cookie refresh flow, limitations.
  • Conservative defaults для всех batch sizes / delays.
  • Diagnostic dumps на первых 3 листингах + каждые 50 — полезно для troubleshooting.
  • unwrap helper для Cian {data, isFetching, isError} wrapper pattern — defensively применён ко всем nested fields.
  • Graceful Ctrl+C + per-listing exception isolation (continue после ERR).

Verdict

Production-validated dev tooling, vault-authorized, security clean. APPROVE → merge.

<!-- gendesign-review-bot: sha=5555ea2 verdict=approve --> ## Deep Code Review — PR #553 **Status:** APPROVE **Files:** 4 (all new, P2 — local-only scripts) · +831 / -0 · scope: `tradein-mvp/scripts/local-cian/` **SHA verified:** `5555ea2f6750b564e170c0f37ae1300e3306e6df` ### Scope context Local-machine Playwright backfill runners для Cian — обход server-IP anti-bot. Authorized by vault `decisions/Decision_Cian_History_Valuation_Activation_May24.md` + runbook `runbooks/cian_local_playwright_backfill.md` (already references this PR #553). User reported 1500+ rows backfilled successfully за час — production-validated. This is **not a server-side scraper** — pure dev tooling что runs from user's workstation. The `feedback_playwright_mcp_not_npx` rule applies к qa-tester smoke tests, не к standalone backfill scripts (которые legitimately нуждаются в local Playwright runtime). ### Security — clean - Никаких hardcoded credentials в diff. Все cookies/userId — placeholders (`<PASTE_FROM_BROWSER_DEVTOOLS>`, `999999999 # TODO`). - `TRADEIN_DB_DSN` / `COOKIE_ENCRYPTION_KEY` — env vars, README указывает source (vault `meta/00_credentials.md`). - Cookies хранятся encrypted в БД через `pgp_sym_encrypt` (matches existing `cian_session_cookies` contract from migration 027). - SQL injection safe: все INSERT через `%s` параметры, никакого f-string interpolation. - Сырые значения cookies нигде не логируются (только counts + `DMIR_AUTH={yes/no}`). ### Correctness - psycopg v3 + `CAST(:x AS type)` (e.g. `CAST(%s AS bigint)` / `CAST(%s AS numeric)`) — соответствует backend rule. - INSERT shape mirrors `cian_valuation._save_to_cache` (line 418-462 of backend/app/services/scrapers/cian_valuation.py). - Idempotency: - history: `ON CONFLICT (listing_id, change_time) DO NOTHING` matches `offer_price_history_listing_change_uq` (migration 047) - valuation: `ON CONFLICT (source, cache_key) DO UPDATE` matches existing service pattern - cookies upload: `ON CONFLICT (account_user_id)` matches migration 027 PK - Pagination cursor `id > last_id ASC` — устойчиво к 0-change результатам, не зацикливается. - SIGINT handler — graceful shutdown с commit pending batch. ### Anti-bot / rate-limiting respect - History `--delay 2` (30 req/min) + Valuation `--delay 5` (12 req/min) — conservative для logged-in Cian session. - Real Chrome UA + viewport + ru-RU locale + persistent profile + `--disable-blink-features=AutomationControlled` — standard fingerprint stabilization. - CAPTCHA detection (`location.href.includes('captcha')`) → STOP execution, prompts user for `--headed` manual solve. - No parallel batches в одном скрипте. ### Minor (non-blocking) - `upload_cookies_local.py`: `_yasc/_ym_isad/uxfb_card_satisfaction` defaults как литерал `"<optional>"` — если user забыл удалить/заполнить, эти cookies загрузятся как мусорные строки. Не блокирует (Cian игнорирует unknown values), но в README стоит указать «удалите optional поля если не заполняете». - `playwright_valuation.py` INSERT: `filters_hash` всегда NULL (hardcoded `%s, NULL`), хотя existing service-layer пишет реальный `sale.filtersHash`. Backward compat — старые записи получат NULL filters_hash. Не критично для backfill, но можно подобрать `sale.get("filtersHash")` без overhead. - `chrome_profile/` hardcoded в Windows-only path — README указывает `CIAN_PROFILE_DIR` override для override. ### Vault cross-check - `decisions/Decision_Cian_History_Valuation_Activation_May24.md` — авторизует full 6-PR sequence (#523/#525/#530/#535/#539/#543) + PR #553 как post-launch tooling. - `runbooks/cian_local_playwright_backfill.md` — уже описывает exactly эти скрипты и process. README в PR строго соответствует runbook. - DDL contracts проверены: 023+047 (offer_price_history), 026+029+044 (external_valuations), 027 (cian_session_cookies) — все ссылки live. ### Positive - Документация (README + inline docstrings) исчерпывающая. README указывает SSH tunnel setup, env vars, cookie refresh flow, limitations. - Conservative defaults для всех batch sizes / delays. - Diagnostic dumps на первых 3 листингах + каждые 50 — полезно для troubleshooting. - `unwrap` helper для Cian `{data, isFetching, isError}` wrapper pattern — defensively применён ко всем nested fields. - Graceful Ctrl+C + per-listing exception isolation (continue после ERR). ### Verdict Production-validated dev tooling, vault-authorized, security clean. APPROVE → merge.
lekss361 merged commit 136202dc80 into main 2026-05-24 20:01:00 +00:00
lekss361 deleted branch feat/local-cian-runners 2026-05-24 20:01:00 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#553
No description provided.