feat(tradein): avito_detail.py — detail page parser (30+ fields incl. domoteka) #443
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#443
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/tradein-avito-detail-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 2b of Avito Scraper v2. NEW module
avito_detail.py— парсер detail page Avito с extraction 30+ полей (apartment params, location, domoteka/Росреестр verification badges, gallery).Closes гэп: search lots (Stage 2a) теперь enriched через detail page → точные lat/lon + kitchen_area + owners_count + encumbrances_clean + 25+ других полей.
LOC:
avito_detail.py~737 lines,test_avito_detail_parse.py175 lines (10 tests).Changes
tradein-mvp/backend/app/services/scrapers/avito_detail.pyfetch_detail(item_url)— async HTTP viacurl_cffi.AsyncSession(impersonate=chrome120)parse_detail_html(html, source_url)— pure selectolax parser (fixture-testable, no network)DetailEnrichmentdataclass — 30+ fields:owners_count+owners_at_least+last_owner_change_date+encumbrances_clean+registry_match) — Росреестр verification badgessave_detail_enrichment(db, e)— UPDATE listings SET 25+ cols WHERE source='avito' AND source_id, setsdetail_enriched_at=NOW()tradein-mvp/backend/tests/test_avito_detail_parse.py(10 tests, all pass)tradein-mvp/backend/tests/__init__.py(needed для pytest package resolution)Smoke
Behavior
Detail enrichment работает в pull mode — cron will call
fetch_detailper listing (queue =listings WHERE detail_enriched_at IS NULL). Уже добавлен partial indexlistings_detail_enrich_idxв Stage 1.Test plan
detail_enriched_at IS NOT NULL+owners_count IS NOT NULL+kitchen_area_m2 IS NOT NULLNotes
_VIEWS_TODAY_REадаптирован: spec был\+(\d+)\s+сегодня, fixture HTML содержит(+ 12 сегодня)с пробелом → regex обновлён на\+\s*(\d+)\s+сегодня(покрывает оба формата)Deep code review verdict: APPROVE — merged via squash.
Scope
Cross-file impact
011_listings_alter.sql(kitchen_area_m2,owners_count,owners_at_least,metro_stations jsonb,detail_enriched_at timestamptz,encumbrances_clean,registry_match, ...). ✅passenger_elevators,has_concierge,closed_yard,total_floors_house,house_type) собираются в dataclass но НЕ в UPDATE — корректное разделение с Stage 2c Houses Catalog. ✅avito.pyинтенциально пишетlat=lon=Noneдля search lots (C-5 fix) — Stage 2b detail UPDATE заполняет. Pipeline coherent. ✅__init__.py— оба файла empty (hashe69de29), git auto-merge dedupe → no conflict.Security
:paramdict (psycopg v3 safe). ✅photo_urlsсобираются но НЕ сохраняются в БД (UPDATE clause безphoto_urls) — sidesteps PR #435 follow-up XSS concern для photo URLs.house_catalog_urlсобирается в dataclass но НЕ в save — нет SSRF surface черезurljoin(AVITO_BASE, untrusted_href)(защита нужна когда Stage 2c будет писать).Correctness
parse_detail_html) отделён от network (fetch_detail) — fixture-testable, 10/10 pytest pass per PR описанию. ✅_VIEWS_TODAY_REflex на whitespace покрывает+12,+ 12,(+12 сегодня),(+ 12 сегодня). ✅_textreturns None,_try_float/intreturns None on parse fail). ✅data-markerатрибутах (Avito-stable) — anti-bot resilient (НЕ хрупкие CSS classes).Minor non-blocking (follow-up идеи, не блокируют merge)
db.commit()внутриsave_detail_enrichment— не позволяет batching по N listings (caller заинтересован в per-listing commit, OK для MVP, но в Stage 2e orchestrator может потребоваться overrride).import datetimeлокально внутри_extract_metaдублирует top-levelfrom datetime import date— cosmetic refactor.listings(source, source_id)(dedup viadedup_hash). UPDATE WHERE source AND source_id теоретически может match >1 row если дубликаты — на практике Avito source_id уникален. Future invariant constraint = good hygiene.publish_dateyear =today().year— некорректно на Jan-1 boundary для Dec-31 объявлений. Acceptable для MVP.Pre-merge checks
Post-merge
deploy-tradein.ymlтриггернётся на push в main (pathstradein-mvp/**) → build + SSH deploy.SELECT detail_enriched_at, owners_count, kitchen_area_m2 FROM listings WHERE source='avito' ORDER BY detail_enriched_at DESC NULLS LAST LIMIT 1.Coordination
avito_imv.py) — оба добавляют emptytests/__init__.py. Merge order безразличен — git считает empty blob = одинаковый, auto-merge тривиальный.