feat(22d): domrf_catalog.py scraper for per-flat price/status from SSR HTML #309
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#309
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/22d-catalog-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
Issue #297 sub-task 22d (2d, Phase 5). NEW module
backend/app/services/scrapers/domrf_catalog.py(563 lines) — scraper для DOM.РФ catalog-квартир SSR HTML (price/status/finishing per-flat).kn-API не возвращает price для 91.5% квартир. Прайсы + finishing/ceiling/section живут на
/сервисы/каталог-квартир/квартира/{hash}(SSR HTML).Module API
fetch_catalog_html(session, catalog_url_hash) -> str— in-browser fetch черезBrowserSession(тот же WAF bypass чтоget_json, returns text/html через_FETCH_HTML_JSsnippet,auth=Noneдля public pages)parse_catalog_flat(html) -> dict— 2-level extraction: regex на raw HTML + stdlibhtml.parserblocks fallback. No new deps (beautifulsoup4TODO в vault если structured parsing later)upsert_catalog_data(db, ods_id, data)—UPDATE domrf_kn_flats SET ... WHERE ods_id = :ods_id, COALESCE guards, SAVEPOINT per row, НЕ trog kn-API metadatascrape_catalog_batch(db, flats)— batch orchestrator over single shared BrowserSessionFields extracted
price_rub,price_per_m2,status,finishing_type,ceiling_height_m,section_no,catalog_updated_atТестовый flat
d8c7a8103f26c52e427ace5a996706ba(Малевич obj=65136): 7 890 000 ₽, Предчистовая, 2.7 м, подъезд 1.Implementation notes
_FETCH_HTML_JS— variant of stealth.py_FETCH_JSдля HTML вместо JSONauth=None— catalog pages public, не нуженAuthorization: Basichtml.parserдостаточен для current extractionDepends on (already merged)
section_no,finishing_type,ceiling_height_m,catalog_updated_at,catalog_url_hashвdomrf_kn_flats— необходимы для UPSERTWiring TODO (separate PR)
backend/app/workers/tasks/scrape_catalog.py— Celery taskbackend/app/workers/beat_schedule.py— beat entry (~30 days interval)Test plan
ruff check backend/app/services/scrapers/domrf_catalog.py— passesscrape_catalog_batch(db, [{"ods_id": "65136/1/1.4.3", "catalog_url_hash": "d8c7a8103f26c52e427ace5a996706ba"}])Related: issue #297 sub-task 22d · Phase 5 main feature · companion для 22c (flat_plans plan_image_url extraction)
Deep code review — verdict APPROVE, merged (
4bed58b4).Findings:
_FETCH_HTML_JS— точная HTML-вариация_FETCH_JSизstealth.py(тот жеcredentials: 'include', тот же error envelope{ok,status,body,contentType}). Используетsession._page.evaluate()черезsession._sem(concurrency 3) — идентичноget_json.2**attempt, корректно ретраит на429/5xx/0, fail-fast на404.404→RuntimeError, не silent skip (правильно для batch — caller увидит fail вoutcome)._TextCollectorfallback) + sanity-bounds (1M ≤ price ≤ 500M,2.0 ≤ ceiling ≤ 6.0) — best-effort, errors swallowed (logger.warning) → пустой dict вместо raise. Поля extraction defensive.text(...)+ named binds (:ods_id, etc.), без f-string, без::typecast trap. SAVEPOINT черезdb.begin_nested()per-row, COALESCE-guards (повторный scrape не затирает заполненные поля).auth=Noneдля catalog (public pages) — корректно.fixes/Fix_BUG22d_CatalogScraper_May17.mdприсутствует.Nits (не блокирующее):
[\d][\d\s]{3,12}[\d]может матчить телефоны/площадь+₽-неподалёку — sanity-bounds спасают, но при дрейфе HTML возможны false positives. Recommend unit-тесты с fixture-HTML.WafBlockedErrorимпортирован паттерн в docstring, но fetch его не raise'ит — non-html content-type только warning logger. Может стоить strict-mode raise при<htmlот ServicePipe.Follow-ups (отдельные PR):
parse_catalog_flat(html)на тестовом flatd8c7a8103f26c52e427ace5a996706ba(Малевич) — verify все 7 полей extracted.backend/app/workers/tasks/scrape_catalog.py+ beat schedule (~30 days interval).