feat(tradein): cian.py SERP refactor — state-based extraction (137 fields, no jitter) #450
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#450
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/tradein-cian-serp-refactor"
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 3 / Wave 3 of CianScraper v1. Refactors existing DOM +
__NEXT_DATA__parser в state-based extraction через newly-mergedcian_state_parser(PR #447). Extracts 137 fields per Schema doc sec 3.Diff
cian.py: 484 → 476 lines (full rewrite, +457/-335 net per diff stat)tests/test_cian_serp_scraper.py: new (458 lines, 21 tests)Key changes
__NEXT_DATA__JSONextract_state(html, 'frontend-serp', 'initialState')ekaterinburg.cian.ruekb.cian.ru(per Schema sec 13 closed Q4)offer.geo.coordinates.{lat,lng}curl_cffichrome120New fields populated в ScrapedLot
Tests
test_cian_serp_scraper.py(all sync, no network — synthesized Cian state strings)Deviations from spec
kitchen_area_m2— not inScrapedLot(Stage 2 PR #447 didn't add it; base.py forbidden to touch from this PR). Stored inraw_payload["kitchen_area_m2"]. Future minor PR can promote to top-level if Stage 8 matching needs it._parse_serp_html()is sync method, no@pytest.mark.asyncioneeded.Anti-bot preserved
✅
curl_cffi.AsyncSession(impersonate="chrome120")untouched. Cian blocks plain httpx (TLS fingerprint detection → 403 "Подозрительный трафик").Next stages unblocked (Wave 4 parallel)
Все 3 могут запускаться parallel через isolation:worktree после merge этого PR.
Refs
decisions/CianScraper_v1_Implementation_Plan.mdStage 3decisions/Schema_Cian_SERP_Inventory.mdsec 1-3 (state structure, 137 fields), sec 11 (schema mapping), sec 13 (subdomain decision)Test plan
Stage 3 of CianScraper v1 plan. Replaces DOM+__NEXT_DATA__ parsing with cian_state_parser.extract_state(mfe='frontend-serp', key='initialState'). No anchor jitter — precise coords from offer.geo.coordinates.{lat,lng}. Subdomain: ekb.cian.ru (per Schema sec 13, closed Q4). curl_cffi anti-bot preserved (chrome120 TLS fingerprint). Fields mapped: identity, geo, area, building, cadastral (flat+house), bargainTerms, newbuilding link, seller (phones/is_homeowner/is_pro), description_minhash (Cian native + SHA1 fallback), metro_stations, balconies/loggias/bedrooms, listing_segment (vtorichka/novostroyki). kitchen_area_m2 stored in raw_payload (not in ScrapedLot Stage 2). Tests: 21 new unit tests in test_cian_serp_scraper.py (all passing). Full suite: 71/71 passed.Merged via deep-code-reviewer — verdict APPROVE (with one MEDIUM follow-up noted below).
Deep review summary
Verified:
ekaterinburg.cian.ru→ekb.cian.ruconsistent — productioncron-scrape.shcallsCianScraperclass (not URL strings), zero hardcoded references remain intradein-mvp/backend/app/**. Only stale ref istradein-mvp/docs/ROADMAP.md:68(doc-only, non-blocking).base.py:121compute_dedup_hashusessource_id(cianId), NOTsource_url— existing rows with old subdomain keep the same hash and will UPDATE, not duplicate.curl_cffi.AsyncSession(impersonate="chrome120")preserved (anti-bot bypass intact).__NEXT_DATA__parser fully removed; no orphan tests on deleted code (onlytest_cian_state_parser.pyfrom PR #447 + newtest_cian_serp_scraper.py)._parse_serp_html()pure-sync, fixture-testable;fetch_around()async, network-isolated.nb_id == 0sentinel correctly maps to vtorichka per Schema sec 20.5.MEDIUM follow-up — save_listings persistence gap (Stage 8 prerequisite)
base.pysave_listingsINSERT (lines 215–276) does NOT reference the new Cian-specific columns added toScrapedLot(PR #447) orlistingsDDL (SQL 019):living_area_m2,bedrooms_count,balconies_count,loggias_countdescription_minhash,cadastral_number,building_cadastral_numberphones,is_homeowner,is_pro_seller,bargain_allowed,sale_typemetro_stationsPer intentional scope (
base.py forbidden to touch this PR), but consequence:_offer_to_lotare silently dropped at INSERT.raw_payloadjsonb (raw_payload only carriescian_id,flat_type,kitchen_area_m2, mortgage flags, etc.).cadastral_numbercolumn stays NULL (legacykadastr_numis populated as before from same value).Impact: Stage 8 cross-source matching that depends on
description_minhash/cadastral_number/building_cadastral_numbercannot use these signals until INSERT is extended. Cross-PR plan should add a Stage ~3.5 or ~7.5base.pyPR before Stage 8 ships.Other observations (LOW)
_format_addressalways falls back to "Екатеринбург (Cian)" — fine for ЕКБ-only stage, but hardcoded city name will be incorrect when Cian scraper expands to other regions (future Stage). Worth a TODO comment.metro_stations[*]["mode"]usesu.get("transportType")("walk"/"transport") — verify naming matches downstream consumer expectations (e.g. UI labels).description_minhashSHA1[:32] fallback is fine for short-term dedup, but Cian-native LSH minhash (when present) and SHA1 hex are not compatible for similarity comparison — Stage 8 needs to branch on which kind of hash is stored. Document in Schema sec 11 mapping notes.Post-merge verification steps
deploy-tradein.ymltriggers ontradein-mvp/backend/**push to main.tradein-backendlogs (anti-bot still works).docs/ROADMAP.md:68toekb.cian.ruin a follow-up doc PR.