refactor(yandex-valuation): DOM-based parser via .OffersArchiveSearchOffers__row #544
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#544
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "refactor/tradein-yandex-dom-based-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
Replaces chunked-text fallback with structured DOM parsing via
.OffersArchiveSearchOffers__rowselectors discovered via live HTML recon.Each history item is a row with 6 positional cells:
48,5 м², 1-комнатная)В продаже) OR removed_dateChunked-text fallback kept as last-resort path with
logger.warningso we notice if Yandex changes class names.Why
Live metrics after PR #542 (chunked-text regex tuned 3x):
3 PRs of regex whack-a-mole (#538/#541/#542) each fixed one bug and broke another. DOM-based parsing eliminates all of them simultaneously:
2,2→ impossibleTests
Followup
yandex_valuationhistory + cache, re-sweep Базовый 52, verifyplausible area≥80% (from 8.7%).Deep Code Review — verdict
Summary
e070f06Architecture assessment
This is the correct response to 3 PRs of regex whack-a-mole (#538/#541/#542). DOM-based per-cell parsing eliminates the entire class of chunked-text ambiguity:
20244,6 m²) impossible — area cell holds only area+rooms2,2from inside52,2) impossible — each cell parsed independentlyRE_ITEM_ROOMS/RE_ITEM_STUDIOFallback path with
logger.warningis the right defensive pattern — if Yandex re-hashes BEM classes, we will see it in logs instead of silent failure.Regression coverage cross-check
All 5 prior parser fixes preserved in the new DOM path:
listing_date(publish_date)parse_dmyremoved_date/total_floorsseparation_parse_house_metaunchangedRE_ITEM_AREAconstant reusedLegacy
_parse_item_text/_parse_items_from_chunked_textkept intact — all 36 prior tests still exercise them through the fallback path.Test coverage
test_dom_parser_extracts_two_rowscovers the critical concatenation scenarios:"5,1 млн ₽105 155 ₽ за м²"price+ppm2 token split via_RE_PRICE_TOKEN/_RE_PPM2_TOKEN,"23.10.2023В экспозиции 945 дней"date+exposure split viaparse_dmy+RE_ITEM_EXPOSURE, both rooms variants, and chronological date ordering (row 2: publish 2026-02-12, removed 2026-07-17)test_dom_parser_skips_malformed_rowexercises thelen(cells) < 5defensive guardtest_dom_parser_fallback_when_no_rowscorrectly under-asserts (onlyisinstance(.., list)) — fallback behavior is intentionally best-effortPerformance (bulk sweep #533)
No concern.
tree.css(".OffersArchiveSearchOffers__row")then.css(".OffersArchiveSearchOffers__cell")per row = O(rows × cells) selectolax calls — selectolax is C-based (lexbor); ~30 rows × 6 selector calls is negligible vs httpx round-trip latency (request_delay_sec = 5.0). For a 1532-obj re-sweep: parser cost is far below the per-request delay floor, no regression.Nits (non-blocking)
_parse_row_cells(cls, row)is decorated@classmethodbut never referencescls— would be cleaner as@staticmethod. Style only.tree.css(".OffersArchiveSearchOffers__cell")is a descendant selector — if Yandex ever nests another.OffersArchiveSearchOffers__cellinside a cell, a child selector>would be more defensive. Fine in practice.logger.warningfires once per parse; for a 1532-obj sweep where Yandex breaks the selector, log volume = 1532 lines. Consider a Sentry/GlitchTip counter metric on top of the warning. Followup, not blocker.len(cells) < 5but per-cell code useslen(cell_texts) > N— minor asymmetry between minimum (5) and full layout (6). Both variants handled correctly; just an asymmetry to note.Vault cross-check
This PR is the climax of the recurring "yandex parser whack-a-mole" pattern. The metrics-driven decision (12.7% area_filled at PR #542 → expected at least 80% post-DOM) is well-documented in the PR body. Recommend a vault
decisions/entry post-merge documenting "yandex_valuation: DOM-based parsing as architectural inflection point" with links to the 5 superseded chunked-text fix PRs.Positive observations
RE_ITEM_AREA(lines 123-127 of head) is exemplary — explains lookbehind, min-digit, and max-digit choices with reference to the 3 prior PRsComplexity / blast radius
Verdict: APPROVE — merge unblocked.