feat(22d): domrf_catalog_object scraper — fill ~25 NULL kn_objects cols from SSR __NEXT_DATA__ #335
No reviewers
Labels
No labels
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
Fable 5 ревью
feedback/max
generative
GG-форсайт
needs-discussion
needs-human
observability
pause-bots
performance
priority/p0
priority/p1
priority/p2
priority/p3
scope/backend
scope/db
scope/devops
scope/frontend
scope/qa
scrapers
security
site-finder
stage/1
stage/2
status/blocked
status/done
status/needs-analysis
status/needs-fix
status/qa
status/ready
status/review
status/wip
tech-debt
tradein
ux
week ревью 1
wontfix
вторичка
ИРД
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#335
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/22d-catalog-object-scraper"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
kn-API не возвращает
wall_type,energy_eff,ceiling_height_m,parking_*,playground_*,finishing_variants_count,has_*, scores — все эти поля доступны только на SSR catalog page/сервисы/каталог-новостроек/объект/{id}через__NEXT_DATA__JSON блок (Next.js).Новый scraper:
BrowserSession(WAF bypass через Playwright, тот же pattern чтоdomrf_catalog.py)__NEXT_DATA__JSON (regex + json.loads)pageProps/pageProps.additionalInfo/pageProps.quartography/pageProps.indexesUPDATE domrf_kn_objects SET field = COALESCE(:field, field)— НЕ перетирает kn-API данныеobj_id + snapshot_dateодним запросом — без race с kn-scraper)Mapping (sample obj 65136)
__NEXT_DATA__pathwall_typepageProps.wallMaterialenergy_effpageProps.objEnergyEfficiencyceiling_height_madditionalInfo.ceilingHeight"2,7"parking_total_slotspageProps.parkingCountparking_provision_pctadditionalInfo.parkingAvailabilityPercelevators_cargo_countcargoElevatorsCount + cargoPassengerElevatorCountplayground_kids_countadditionalInfo.playgroundsCountFiles
backend/app/services/scrapers/domrf_catalog_object.py(NEW, ~290 lines)backend/app/workers/tasks/scrape_kn_catalog_objects.py(NEW, ~110 lines)backend/app/workers/beat_schedule.py(MODIFIED, +10 lines)data/sql/118_22d_catalog_object_scraped_at.sql(NEW) —catalog_scraped_at TIMESTAMP+ indexbackend/tests/services/scrapers/test_domrf_catalog_object.py(NEW, 41 tests pass)Not in this PR
obj_checks6 чекбоксов "Проверено на наш.дом.рф" — на странице только общийisCheckedflag, 6 detailed checks требуют отдельной investigation.Test plan
pytest tests/services/scrapers/test_domrf_catalog_object.py)celery -A app.workers call scrape_kn_catalog_objects --kwargs='{"max_objects": 3}'smoke testSELECT obj_id, catalog_scraped_at, wall_type, energy_eff, ceiling_height_m FROM domrf_kn_objects WHERE catalog_scraped_at IS NOT NULL LIMIT 5returns dataReviewer fixups applied (pre-push, 🟡 MEDIUM)
parking_provision_pct_safe_int→_to_numeric_comma(column isnumeric(5,1), 60.5 was truncated)(obj_id, snapshot_date)одним запросом — устраняет race с kn-scraper между двумя SELECT'амиavg_flat_area_m2defensive через_to_numeric_commakn-API не возвращает wall_type, energy_eff, ceiling_height_m, parking_*, playground_*, finishing_variants_count, has_*, scores и др. Эти поля доступны только на SSR catalog странице /сервисы/каталог-новостроек/объект/{id} в __NEXT_DATA__ JSON блоке (Next.js). - backend/app/services/scrapers/domrf_catalog_object.py — fetch HTML через BrowserSession (WAF bypass), extract __NEXT_DATA__, parse 25+ fields, COALESCE UPDATE (не перетирает kn-API данные) - backend/app/workers/tasks/scrape_kn_catalog_objects.py — Celery task, atomic SELECT (obj_id + snapshot_date одним запросом, без race с kn-scraper) - backend/app/workers/beat_schedule.py — Tuesday 04:00 UTC, batch 300/run - data/sql/118_22d_catalog_object_scraped_at.sql — catalog_scraped_at TIMESTAMP + index NULLS FIRST для "missing/stale first" selection - backend/tests/services/scrapers/test_domrf_catalog_object.py — 41 tests pass obj_checks 6 чекбоксов "Проверено на наш.дом.рф" — отдельная investigation (на странице только общий isChecked flag).Deep Code Review — verdict APPROVE
Summary
ef3e0404· mergeable=true · no existing reviewsMigration 118 collision — SAFE
118_22d_catalog_object_scraped_at.sql→ALTER TABLE domrf_kn_objects ADD COLUMN IF NOT EXISTS catalog_scraped_at TIMESTAMP+CREATE INDEX IF NOT EXISTS118_pilot_requests.sql(already merged on main) →CREATE TABLE IF NOT EXISTS pilot_requests(lead-gen, issue #307)IF [NOT] EXISTSidempotent._schema_migrationsPK = full filename. Lex order:118_22d…runs first, then118_pilot…. Replays no-op.domrf_kn_objects—ADD COLUMNбезNOT NULL DEFAULT→ metadata-only operation в Postgres 11+, не блокирует таблицу даже на больших объёмах. CREATE INDEX без CONCURRENTLY: таблица 1.5k objects per region (per beat schedulebatch 300/run → 1532 объекта за 5 нед) — index build instant, locking risk нулевой.Scraper robustness — verified
__NEXT_DATA__: anchored literal prefix + lazy(.+?)+ literal closing tag,re.DOTALL. Tested locally на 1MB no-match body — 0.65 ms; 50k script-tag haystack — 2.56 ms. No catastrophic backtracking (нет nested quantifier / alternation overlap).__NEXT_DATA__missing →ValueError→ caught вscrape_catalog_object→logger.warning+return False→ batch продолжается. WAF block / 404 / fetch err — тот же pattern, никаких uncaught crashes на partial pages.COALESCE(:field, field)— preserves kn-API данные при NULL parse result. Идемпотентно при re-scrape.:x::typeни разу, все binds — обычные:name, типизация на стороне Python (_safe_int,_to_numeric_comma,_to_date_ddmmyyyy).with db.begin_nested():) — соответствует.claude/rules/backend.md, изолирует per-row failures.(obj_id, snapshot_date)одним query сsnapshot_date = (SELECT MAX … WHERE region_cd = :region_code)— устраняет race с kn-scraper между двумя запросами. Грамотно ограничено тем жеregion_cd._page,_sem,_request_count) — consistent сdomrf_catalog.py:99(existing prod pattern).Celery task
time_limit=3600— adequate (batch 300 × ~1.2s jitter = ~6 min realistic).asyncio.run()в sync Celery task — correct sync↔async bridge per project convention.logger.error+raise, либоlogger.warning+ skip — нет silent swallow.Beat schedule
0 4 * * 2(Tue 04:00 UTC) — slot не конфликтует с другими weekly tasks (poi/noise — Monday, ekburg — monthly 1st, pzz — monthly 1st, nspd — Monday 01:00).Tests — substantive
def test_*+ 30 parametrize cases = 41 tests (math confirms author's claim).buildingClass), empty response (no crash), edge cases для всех 4 value helpers,parking_provision_pct=60.5float preservation (M1 regression test),freePlan='Да',first_floor=жилой/нежилой,elevators_cargo_count = cargoElevatorsCount + cargoPassengerElevatorCountsum logic.assert True— реальные ассерты с expected values.Convention compliance
import psycopg2,import requests,print(), hardcoded credentials, f-string SQL.psycopg v3patterns: bind params +text(...)+executemanystyle.сервисы/каталог-новостроек/объект/{obj_id}) — Python str + Playwright fetch handles UTF-8 natively, тот же pattern чтоdomrf_catalog.py.fixes/Fix_BUG22d_CatalogScraper_May17.md+domains/domrf/scraper/domrf-scraper-MOC.md(per project rule #6).Pre-flight
feat/22d-catalog-object-scraper, не main ✅mergeable=true, no conflicts ✅Risk / reversibility
Мержу через
merge_pull_request(squash).