[EPIC] scraper-kit — внутренний код-пакет монорепо (консолидация скрапперов) #2126

Closed
opened 2026-07-02 13:39:10 +00:00 by lekss361 · 3 comments
Owner

Цель

Выделить trade-in скрапперы в внутренний пакет монорепо tradein-mvp/packages/scraper-kit (uv workspace). Пакет потребляется ТОЛЬКО скраппер-контуром (tradein-backend + tradein-scraper, оба из одного образа). Другие проекты его не импортируют — им нужны данные (см. Epic B), не код.

При переносе — консолидировать дублирование из аудита 2026-07-02 (не тащить copy-paste в пакет).

Контекст / решение

ADR 2026-07-02 (Anton): движок скрейпинга живёт в одном пакете монорепо (НЕ отдельный репо); другим продуктам нужны данные через data-контракт. Отдельный репо отклонён — скрапперы меняются часто (анти-бот), кросс-репо трение постоянное.

Coupling scrapers/*app.* (развязывать через Protocol)

Зацепление Где Развязка
matching.match_or_create_house, upsert_listing_source base.py:35,721,752 Protocol HouseMatcher (инжект)
core.config.settings avito.py:41, cian.py:39, browser_fetcher.py:58, scrape_pipeline.py:43, scheduler.py:1008, … ScraperConfig dataclass (инжект)
core.db.SessionLocal scheduler.py:142, scraper_settings.py:64 SessionFactory (инжект)
geocoder.is_within_ekb_bbox_wide avito_detail.py:36 переместить в kit (чистая математика)
cian_session.* cian_valuation.py:35, scheduler.py:463 переместить в kit
enrichment-джобы (imv_backfill, dedup_merge, address_backfill) scrape_pipeline.py:709,1519; scheduler.py:1632,1701 EnrichmentJobs-реестр callback'ов (джобы остаются в продукте)
region_code=66 хардкод base.py:404 параметр (kit не знает про регион)

Механика

uv workspace scoped на tradein-mvp/ (не корень — потребитель кода один). src/-layout (избежать flat-layout discovery, см. backend/pyproject.toml:56). Docker context ./backend. (tradein-mvp). Один uv.lock.

Под-issues (dependency-ordered)

  • #A Скаффолд пакета + workspace-подключение — status/ready
  • #B Protocol-границы (HouseMatcher / ScraperConfig / SessionFactory / EnrichmentJobs) + adapters — depends #A
  • #C Перенос core-слоя (base/browser_fetcher/snapshot_writer/settings/price_brackets/state-parsers/helpers) — depends #B
  • #D Перенос провайдеров (avito/cian/yandex/domclick + detail/nb/valuation/houses/imv) — depends #C
  • #E Консолидация бисекции цены (4 копии → pricing/bisection.py) — depends #D
  • #F Перенос pipeline (scrape_pipeline) — depends #D
  • #G scheduler + registry-dispatch (25+ trigger_* + 27-веточный if/elif → dict-реестр) — depends #F
  • #H Docker/build wiring (context, Dockerfile, deploy path-фильтры) — depends #A, финал после #G
  • #K (быстрый баг-фикс, независимо) fix _unix_to_date TZ-inconsistency (3 копии MSK/UTC/naive) — status/ready

Источники

  • Аудит: vault audits/Scrapers_Architecture_Audit_0702.md
  • ADR: vault decisions/ (scraper-kit + market contract, 2026-07-02)
  • Связано: Epic B (market data contract)

Что НЕ делать

  • Отдельный репозиторий (только когда потребитель вне монорепо/VPS)
  • Workspace на корне монорепо (scope на tradein-mvp/)
  • Тащить copy-paste в пакет как есть — консолидировать при переносе
  • Сливать 4 реализации бисекции наугад — они разошлись (константы/TZ); diff семантику → per-provider config
  • Трогать browser-server (отдельный образ) в этой волне
## Цель Выделить trade-in скрапперы в **внутренний** пакет монорепо `tradein-mvp/packages/scraper-kit` (uv workspace). Пакет потребляется ТОЛЬКО скраппер-контуром (`tradein-backend` + `tradein-scraper`, оба из одного образа). Другие проекты его **не импортируют** — им нужны данные (см. Epic B), не код. При переносе — **консолидировать дублирование** из аудита 2026-07-02 (не тащить copy-paste в пакет). ## Контекст / решение ADR 2026-07-02 (Anton): движок скрейпинга живёт в одном пакете монорепо (НЕ отдельный репо); другим продуктам нужны данные через data-контракт. Отдельный репо отклонён — скрапперы меняются часто (анти-бот), кросс-репо трение постоянное. ## Coupling `scrapers/*` → `app.*` (развязывать через Protocol) | Зацепление | Где | Развязка | |---|---|---| | `matching.match_or_create_house`, `upsert_listing_source` | `base.py:35,721,752` | Protocol `HouseMatcher` (инжект) | | `core.config.settings` | avito.py:41, cian.py:39, browser_fetcher.py:58, scrape_pipeline.py:43, scheduler.py:1008, … | `ScraperConfig` dataclass (инжект) | | `core.db.SessionLocal` | scheduler.py:142, scraper_settings.py:64 | `SessionFactory` (инжект) | | `geocoder.is_within_ekb_bbox_wide` | avito_detail.py:36 | переместить в kit (чистая математика) | | `cian_session.*` | cian_valuation.py:35, scheduler.py:463 | переместить в kit | | enrichment-джобы (imv_backfill, dedup_merge, address_backfill) | scrape_pipeline.py:709,1519; scheduler.py:1632,1701 | `EnrichmentJobs`-реестр callback'ов (джобы остаются в продукте) | | `region_code=66` хардкод | base.py:404 | параметр (kit не знает про регион) | ## Механика uv workspace scoped на `tradein-mvp/` (не корень — потребитель кода один). `src/`-layout (избежать flat-layout discovery, см. backend/pyproject.toml:56). Docker context `./backend` → `.` (tradein-mvp). Один `uv.lock`. ## Под-issues (dependency-ordered) - [ ] #A Скаффолд пакета + workspace-подключение — **status/ready** - [ ] #B Protocol-границы (HouseMatcher / ScraperConfig / SessionFactory / EnrichmentJobs) + adapters — depends #A - [ ] #C Перенос core-слоя (base/browser_fetcher/snapshot_writer/settings/price_brackets/state-parsers/helpers) — depends #B - [ ] #D Перенос провайдеров (avito/cian/yandex/domclick + detail/nb/valuation/houses/imv) — depends #C - [ ] #E Консолидация бисекции цены (4 копии → `pricing/bisection.py`) — depends #D - [ ] #F Перенос pipeline (scrape_pipeline) — depends #D - [ ] #G scheduler + registry-dispatch (25+ trigger_* + 27-веточный if/elif → dict-реестр) — depends #F - [ ] #H Docker/build wiring (context, Dockerfile, deploy path-фильтры) — depends #A, финал после #G - [ ] #K (быстрый баг-фикс, независимо) fix `_unix_to_date` TZ-inconsistency (3 копии MSK/UTC/naive) — **status/ready** ## Источники - Аудит: vault `audits/Scrapers_Architecture_Audit_0702.md` - ADR: vault `decisions/` (scraper-kit + market contract, 2026-07-02) - Связано: Epic B (market data contract) ## Что НЕ делать - ❌ Отдельный репозиторий (только когда потребитель вне монорепо/VPS) - ❌ Workspace на корне монорепо (scope на tradein-mvp/) - ❌ Тащить copy-paste в пакет как есть — консолидировать при переносе - ❌ Сливать 4 реализации бисекции наугад — они разошлись (константы/TZ); diff семантику → per-provider config - ❌ Трогать browser-server (отдельный образ) в этой волне
lekss361 added the
enhancement
scope/backend
scrapers
tech-debt
tradein
labels 2026-07-02 13:41:47 +00:00
Author
Owner

Волна 1 смержена + QA PASS (2026-07-02):

  • #2128 (шаг A, scaffold) — PR #2139, build-safe (hard-dep убран до docker-wiring)
  • #2129 (шаг K, TZ-фикс) — PR #2142, _unix_to_date = 2025-08-25 MSK на проде
  • Deploy verified: backend health 200, образ свежий, 0 новых ошибок GlitchTip.

Разблокирован шаг B (#2131, protocols). ВАЖНО — уточнение порядка: scraper_adapters.py (backend) будет импортировать scraper_kit, но backend сейчас НЕ зависит от пакета (build-safe). Поэтому реальное подключение зависимости + docker-context wiring (#2137) должно идти ПЕРЕД тем как backend начнёт импортировать scraper_kit в рантайме. Пересматриваю порядок: #2137 (docker-wiring) поднимается раньше — сразу после A, а не «финал после G».

**Волна 1 смержена + QA PASS (2026-07-02):** - ✅ #2128 (шаг A, scaffold) — PR #2139, build-safe (hard-dep убран до docker-wiring) - ✅ #2129 (шаг K, TZ-фикс) — PR #2142, `_unix_to_date` = 2025-08-25 MSK на проде - Deploy verified: backend health 200, образ свежий, 0 новых ошибок GlitchTip. **Разблокирован шаг B (#2131, protocols).** ВАЖНО — уточнение порядка: `scraper_adapters.py` (backend) будет импортировать `scraper_kit`, но backend сейчас НЕ зависит от пакета (build-safe). Поэтому реальное подключение зависимости + docker-context wiring (#2137) должно идти ПЕРЕД тем как backend начнёт импортировать scraper_kit в рантайме. Пересматриваю порядок: #2137 (docker-wiring) поднимается раньше — сразу после A, а не «финал после G».
Author
Owner

Волна 2 смержена + QA PASS (2026-07-02):

  • #2137 (шаг H, docker-context wiring) — PR #2143. Свежий образ (собран 14:24, после мержа) несёт packages/scraper-kit; рантайм import scraper_kit = 0.1.0 + app.main ok в живом backend-контейнере; health 200; 0 новых ошибок.
  • Нюанс: tradein-scraper остался на прежнем образе (by design — PR не трогал scraper-пути, SCRAPER_CHANGED=false, чтобы не убить running sweep); тот же валидированный образ подхватится при следующем рестарте.

Фундамент готов: workspace + docker + editable-dep backend→scraper-kit работают на проде. Разблокированы шаги B→C→D→E→F→G — перенос боевого кода скрапперов в пакет. Эта фаза = рефактор работающих avito/cian/yandex с риском регресса скрейпинга → идёт отдельным контролируемым заходом (не в общей автономной волне).

**Волна 2 смержена + QA PASS (2026-07-02):** - ✅ #2137 (шаг H, docker-context wiring) — PR #2143. Свежий образ (собран 14:24, после мержа) несёт `packages/scraper-kit`; рантайм `import scraper_kit` = 0.1.0 + `app.main ok` в живом backend-контейнере; health 200; 0 новых ошибок. - Нюанс: tradein-scraper остался на прежнем образе (by design — PR не трогал scraper-пути, `SCRAPER_CHANGED=false`, чтобы не убить running sweep); тот же валидированный образ подхватится при следующем рестарте. **Фундамент готов: workspace + docker + editable-dep backend→scraper-kit работают на проде.** Разблокированы шаги B→C→D→E→F→G — перенос боевого кода скрапперов в пакет. Эта фаза = рефактор работающих avito/cian/yandex с риском регресса скрейпинга → идёт отдельным контролируемым заходом (не в общей автономной волне).
Author
Owner

Закрыто: боевая активация завершена (2026-07-03)

Итог: scraper-kit — единственный активный путь скрейпинга на проде. USE_KIT_SCHEDULER=true постоянно в docker-compose.prod.yml (PR #2268), зависимость от временного override снята.

Прод-QA (живые раны):

  • kit-scheduler: registry 27 handlers, claim/dispatch — yandex #578/#582 (2508 lots, 0 ошибок), cian #586 (anchor: fetched=335, ins=107), avito #585/#589 (66 lots + рестарт), product-handlers deactivate_stale_n1 #584, deactivate_stale_yandex #587 — done.
  • Найденный при активации пробел (фиксированный cian anchor watchdog 240s гильотинил якоря через пул) закрыт PR #2272 — масштабируемый таймаут по прецеденту avito/yandex/domclick.

Под-issues A-H, K: все смержены (~30 PR, strangler golden-parity).

Follow-up (отдельная волна): cleanup дублей app/services/scrapers/* + legacy sweep-функций после ~недели стабильности — см. отдельный issue (создан следом), там же аудит importers (detail/imv/valuation/newbuilding/cadastral backfills живут на старых app-модулях).

## Закрыто: боевая активация завершена (2026-07-03) **Итог:** scraper-kit — единственный активный путь скрейпинга на проде. `USE_KIT_SCHEDULER=true` постоянно в `docker-compose.prod.yml` (PR #2268), зависимость от временного override снята. **Прод-QA (живые раны):** - kit-scheduler: registry 27 handlers, claim/dispatch — yandex #578/#582 (2508 lots, 0 ошибок), cian #586 (anchor: fetched=335, ins=107), avito #585/#589 (66 lots + рестарт), product-handlers deactivate_stale_n1 #584, deactivate_stale_yandex #587 — done. - Найденный при активации пробел (фиксированный cian anchor watchdog 240s гильотинил якоря через пул) закрыт PR #2272 — масштабируемый таймаут по прецеденту avito/yandex/domclick. **Под-issues A-H, K:** все смержены (~30 PR, strangler golden-parity). **Follow-up (отдельная волна):** cleanup дублей `app/services/scrapers/*` + legacy sweep-функций после ~недели стабильности — см. отдельный issue (создан следом), там же аудит importers (detail/imv/valuation/newbuilding/cadastral backfills живут на старых app-модулях).
Sign in to join this conversation.
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#2126
No description provided.