feat(tradein): avito_houses.py — Houses Catalog parser (state + reviews + history + recs) #449
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#449
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/tradein-avito-houses-parser"
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
Stage 2c of Avito Scraper v2. NEW module
avito_houses.py— парсер Houses Catalog Avito (/catalog/houses/<slug>/<id>). Извлекает из embeddedwindow.__preloadedState__JSON state: HouseInfo (24 fields), reviews, active listings + sellers, placement history, recommendations.Canonical source для house-level полей (canonical vs Stage 2b detail page, которое house-level fields игнорирует во избежание конфликтов).
LOC:
avito_houses.py~1141 lines,test_avito_houses_parse.py397 lines (18 tests).Changes
tradein-mvp/backend/app/services/scrapers/avito_houses.pyfetch_house_catalog(house_url)— async HTTP viacurl_cffi.AsyncSession(chrome120) → preloadedState extract → delegate toparse_houses_stateparse_houses_state(state, house_url)— pure parser (testable без сети)w['type'](stable;w['name']deprecated):developmentData.*→ HouseInfoentries[](rating/pages mixed) → HouseReview[]items[](active listings + seller) → MiniSerpListing[]items[]→ PlacementHistoryItem[] (removed_date=None всегда — widget не отдаёт; точная дата только из IMV API в Stage 2d)items[]→ RecommendationItem[]raw_characteristics jsonbдля retrofitsave_house_catalog_enrichment):upsert_house→houses(UNIQUE source='avito', ext_house_id) + raw_characteristics jsonbsave_house_reviews→house_reviews(UNIQUE source, ext_review_id), без imagessave_sellers→sellers(UNIQUE source, ext_seller_id), без logoslink_listings_to_seller→ UPDATElistings.seller_id_fkby source_id matchsave_placement_history→house_placement_historyс source='avito_widget', removed_date=NULLtradein-mvp/backend/tests/test_avito_houses_parse.py(18 tests)DDL alignment (verified против Stage 1 migrations)
Worker сверил с actual DDL и сделал adjustments:
ext_id(int)ext_house_id(text)str(h.ext_id)ext_id_hashavito_id_hashshort_address→addressaddress(009) +short_address(010)HouseInfo.short_addresstitleraw_characteristicsjsonbrating_score(float)ratingnumeric(2,1) +rating_scorenumeric(4,2)Smoke
Out-of-scope (для Stage 3)
save_recommendations→ append вlistingsс flag — отложеноTest plan
fetch_house_catalog('/catalog/houses/ekaterinburg/ul_akademika_postovskogo_17a/3171365')→ verifyhousesrow + reviews + miniSerp listings + placement_history (сremoved_date IS NULLдля source='avito_widget')Recovery note
Worker accidentally сделал extra commit с unrelated Cian shared parser (
7f03a5b) на wrong branch. Recovery: empty remote branch deleted, local moved to clean879f761, fresh push. Этот PR содержит только avito_houses.py + tests.Merged via deep-code-reviewer — verdict APPROVE.
Status: squash →
5258a8f0on main.5-file breakdown (recovery clean despite stale base):
tradein-mvp/backend/app/services/scrapers/avito_houses.py(+1141) — Stage 2c canonicaltradein-mvp/backend/tests/test_avito_houses_parse.py(+397) — 18 offline teststradein-mvp/backend/app/services/scrapers/cian_state_parser.py(+93) — identical to PR #447 content already on main, git resolved as no-op mergetradein-mvp/backend/tests/test_cian_state_parser.py(+46) — same as abovetradein-mvp/backend/app/services/scrapers/base.py(+15) — Cian ScrapedLot fields, identical to PR #447 content already on mainBranch was based on
23e6cbc(pre-#447/#448). Forgejo 3-way merge correctly preserved SQL 029 (added on main side only) + auto-resolved Cian files (identical both sides). No code duplication, no SQL regression.DDL alignment validated against 009/010/012/013/014/017:
ext_house_id textviastr(h.ext_id)✅avito_id_hashrenamed correctly ✅address(009) +short_address(010) both filled fromHouseInfo.short_address✅rating numeric(2,1)rounded +rating_score numeric(4,2)precise ✅house_placement_history.source='avito_widget'correct (DDL has no CHECK, comment in 017 is stale — see follow-up #2 below)link_listings_to_sellersafety:source='avito'+source_id(canonical Avito-issued ID) → no cross-source collision ✅IS DISTINCT FROMpredicate avoids no-op writes ✅miniSerpitems belong to the parsed house → no overwrite riskCross-PR coordination: PR #447 (Cian parser) merged 12:35:02Z, PR #448 (SQL 029) merged 12:35:04Z. Stage 2e (orchestrator) now unblocked.
Minor follow-ups (non-blocking, can be batched with Stage 2e PR):
upsert_house()convenience function (lines 669-790 ofavito_houses.py) passes"url": ""empty string —houses.urlisNOT NULL. Direct callers (if any added later) would store empty URL silently. Recommend either deprecating it or makinghouse_urlrequired.data/sql/017_house_placement_history.sqlcomment sayssource = 'avito_imv' / 'avito'— code uses'avito_widget'. No constraint, no breakage, but DDL comment is stale. Update in a future migration.EXPAND_PARAMS_MAPlacksmaterial_wallsdespite DDL columnhouses.material_walls(010 line 24). Stage 2c-followup or retrofit viaraw_characteristicslater.houses.raw_characteristics— add when querying lands.save_house_catalog_enrichmentorchestration (only pure-parser unit tests). Deploy smoke from PR test plan covers it.Post-merge:
deploy-tradein.ymlshould trigger. After Stage 2e orchestrator merge, verify via: