feat(tradein): scrape_pipeline.py — Avito full orchestrator (search → houses → detail) #451
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#451
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/tradein-scrape-pipeline"
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 2e of Avito Scraper v2 — orchestrator модуль координирующий полный pipeline: search (Stage 2a) → group_by_house → enrich houses (Stage 2c) → enrich top-N details (Stage 2b). IMV (Stage 2d) НЕ здесь — он on-demand из estimator (Stage 3).
LOC:
scrape_pipeline.py229 lines,test_scrape_pipeline.py86 lines (3 tests).Changes
tradein-mvp/backend/app/services/scrape_pipeline.pyrun_avito_pipeline(db, lat, lon, radius_m, *, enrich_houses, enrich_detail_top_n)— 5-step async flow:AvitoScraper().fetch_around(uses Stage 2a refactored)save_listings(сhouse_ext_idlinking из Stage 2a)house_ext_id→ unique house URLsfetch_house_catalog+save_house_catalog_enrichmentper unique house (Stage 2c)detail_enriched_at IS NULLв радиусе) →fetch_detail+save_detail_enrichment(Stage 2b)PipelineCountersdataclass: lots/houses/detail counters + errors[]PipelineResultdataclasstradein-mvp/backend/tests/test_scrape_pipeline.py(3 tests, 53/53 passed суммарно с existing)Smoke
Bug fix in spec
Worker поймал SQL bug в моём inline spec:
WHERE ... AND condition1 OR condition2без скобок → AND приоритет выше OR →condition2применялся бы ко всем строкам безsource='avito'filter. Обёрнут в(...) OR (...). Хорошая catch.Tooling adds
pytest-asyncio>=0.24.0добавлен вpyproject.toml(отсутствовал в tradein-mvp) +asyncio_mode = "auto"Test plan
cron-scrape.shили новый admin endpoint вызываетrun_avito_pipelineвместо raw/admin/scrape(Stage 4a)Behavior
Stage 2e of AvitoScraper_v2. - run_avito_pipeline(db, lat, lon, radius_m, *, enrich_houses, enrich_detail_top_n) - 5-step flow: 1. SEARCH: AvitoScraper().fetch_around (uses Stage 2a refactored search) 2. SAVE: save_listings (inserts + tracks house_ext_id from Stage 2a) 3. GROUP: dedupe house path → unique set (path-only для consistency с fetch_house_catalog) 4. ENRICH_HOUSES: fetch_house_catalog + save_house_catalog_enrichment (Stage 2c) per unique house 5. ENRICH_DETAIL: top-N priority listings (detail_enriched_at IS NULL within radius) → fetch_detail + save_detail_enrichment (Stage 2b) - PipelineCounters dataclass: lots_fetched/inserted/updated, unique_houses, houses_enriched/failed, detail_attempted/enriched/failed, errors[] - Graceful degradation: per-house and per-detail try/except — single failure не валит pipeline - IMV ОТСУТСТВУЕТ — он on-demand (Stage 3 estimator integration), не cron - pytest offline smoke (3 tests: counters default + search failure graceful + group_by_house dedupe) - Add pytest-asyncio>=0.24.0 to dev deps + asyncio_mode=auto в pyproject.toml Refs: AvitoScraper_v2_Implementation_Plan Stage 2e.Merged via deep-code-reviewer — verdict APPROVE with MINOR.
What was verified
AND source='avito' AND source_url IS NOT NULL AND ( (radius+price branch) OR (recent branch) ). Neither OR branch can leak past the source filter. All columns (source,source_url,detail_enriched_at,price_rub,geom,scraped_at) exist; partial indexlistings_detail_enrich_idx+ GISTlistings_geom_idxsupport the query well.save_listings/save_house_catalog_enrichment/save_detail_enrichment. Acceptable for orchestrator scope; enables graceful degradation; no long-held transaction across minutes-long enrichment.fetch_house_catalog,save_house_catalog_enrichment,fetch_detail,save_detail_enrichment,save_listings,AvitoScraper.fetch_aroundall match call sites.Follow-up nits (non-blocking, do in next PR)
db.rollback()insideexcept Exceptionfor both per-house loop (step 4) and per-listing loop (step 5) — guards againstPendingRollbackErrorcascade if the underlying failure was aDBAPIError/IntegrityErrorrather than HTTP/parse.counters.errors[]to last ~20 entries + adderror_totalint counter — prevents large payloads whenenrich_detail_top_nis raised for bigger anchors in Stage 4a wire-up.scraped_at > NOW() - INTERVAL '2 hours') has no radius bound — if cron multiplexes anchors in parallel later, addAND ST_DWithin(...)to that branch too.save_detail_enrichmentreturnsFalsewhen listing row not found in DB — currently silently skipped in counters. Considercounters.detail_skippedseparate fromdetail_failedfor observability.fetch_house_catalogopens its ownCffiAsyncSessionand bypassesAvitoScraper'srequest_delay_sec=7.0. For 30 unique houses in a single run that's a burst of 30 reqs → 429 risk. Suggest threadingAvitoScraper's session through, or addingawait asyncio.sleep(7)between houses.enrich_detail_top_n.Post-merge actions
cron-scrape.shor an admin endpoint torun_avito_pipeline— no behavior change in production yet.code/modules/tradein/Scrape_Pipeline_Orchestrator.mdto be created by worker.