fix(tradein-avito): strip Emotion CSS from listings.address #502
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#502
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/tradein-avito-clean-address-css"
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?
Problem
Avito scraper leaks
<style>.css-XXX{...}</style>content intolistings.addressvia recursive.text(strip=True). Production example (estimatea0a0b820-e8a8-4eee-aa73-0ab3b98ac233analog #4):Fix
_clean_address()helper inavito.py— strips Emotion CSS rules + post-address noise (Площадь N года, от N мин.)avito.py(SERP card address) and inavito_detail.py(address_fullfrom[itemprop="address"])avito_detail.pyfromavito.py— no copy-paste062_clean_avito_addresses.sqlfor historical rowsVerify
Avito DOM leaks inline <style>.css-XXX{...}</style> inside the location <p>, which gets pulled by .text(strip=True). Add _clean_address() helper that strips CSS rules + post-address noise (Площадь N года, от N мин.), apply at extraction site. Also applied to address_full in avito_detail.py via import from avito.py. Backfill migration cleans the ~hundreds of historical rows. Source: estimate a0a0b820-e8a8-4eee-aa73-0ab3b98ac233 analog #4.Deep Code Review — verdict
Summary
9e1c7d5Decision_TradeIn_DataQuality_8PR_RoadmapPR 1✅ ready for human merge
Findings
No BLOCKERS. Two LOW notes worth a follow-up but not blocking.
Low-1 — false-positive on legit
Площадь N…addressesavito.py:271regex\s*(Площадь \d|от \d+\s?мин\.|css-[a-z0-9_-]+)— tokenПлощадь \dwill truncate any real ЕКБ address that legitimately containsПлощадь+ digit (e.g. listing facingПлощадь 1905 года, 5). In practice Avito puts metro/landmark hints as suffix to street, so impact is small, but the pattern is a heuristic, not a clean delimiter. Consider tightening (requirecss-to follow within N chars, or use a small allow-list of landmark names likeПлощадь 1905 года,Площадь Революции). Low priority — current incidence inlistingsis small.Low-2 — backfill SQL slight divergence from Python helper
062_clean_avito_addresses.sql:regexp_replace(address, '\.?css-…\{[^}]*\}', '', 'gi')matches Python_CSS_NOISE_RE✓regexp_replace(…, '\s*(Площадь \d|от \d+\s?мин\.|css-[a-z0-9_-]+).*$', '', 'i')mirrors_NOT_ADDRESS_TAIL_RE.split(...)[0]✓trim(both ' ,.' FROM …)is narrower than Python.strip(' ,. ')— newlines/tabs in DB rows will not be trimmed. Likely a no-op in practice (noin stored addresses), but for parity could addE' 'to the trim set.address ~* 'css-[a-z0-9_-]+'(no leading\.?) is strictly broader than Python helper — fine for the cleanup pass.Note —
avito_houses.pycorrectly excludedVault roadmap mentioned
avito_houses.pyas a third candidate. Verified: that file pulls address from__preloadedState__JSON viadd.get("address")/dd.get("fullAddress")(structured data, no.text()from HTML), so CSS noise cannot leak there. Excluding it is the right call.Note — cross-module private import
avito_detail.py:35imports_clean_address(leading underscore = module-private) fromavito.py. Trade-off explicitly chosen to avoid copy-paste. Acceptable in scope; if a third caller emerges, promote toapp/services/scrapers/_text_utils.py.Correctness walkthrough
Dirty example from PR description traces correctly through helper:
_CSS_NOISE_RE.substrips.css-39hgr0{fill:…box-sizing:border-box;}and trailing.css-39hgr0{...}(leaves:focus{…}variants — they don't match[a-z0-9_-]+\s*\{because of the:). OK because step 2 catches them._NOT_ADDRESS_TAIL_RE.splitcuts at firstПлощадь 1→ returns prefix"ул. Токарей, 56к1"..strip(" ,. ")→"ул. Токарей, 56к1". Matches assertion intest_strips_css_noise. ✓Security
062_*.sqlis static migration with no parameters; all regex literals.[^}]*inside\{…\}and bounded character classes; input is short address strings (<500 chars).Idempotency
BEGIN/COMMITwraps singleUPDATE— atomic. ✓css-left), so over-strip is one-shot. Acceptable.Performance
UPDATE listings WHERE source='avito' AND address ~* …: sequential scan over avito rows. Acceptable for one-shot backfill; not a hot path. No new index needed.Test coverage
NoneПлощадь Революции(no digit) → not stripped"Площадь 1905 года, 5"→ currently WOULD be stripped to""thenNone— would document the limitationProject conventions
str | None),from __future__ import annotationsalready in module — compliant.062follows061_drop_legacy_cad_buildings.sql— correct.BEGIN/COMMITper.claude/rules/sql.md✓.Vault cross-check
decisions/Decision_TradeIn_DataQuality_8PR_Roadmap.md→ PR 1 acceptance: "unit-test на грязной строке analog #4 → 'улица Токарей, 56к1'". Met. ✓SELECT count(*) … address ~ '\.css-'= 0. ✓Recommended next steps
SELECT count(*) FROM listings WHERE source='avito' AND address ~ 'css-'→ 0.a0a0b820-e8a8-4eee-aa73-0ab3b98ac233analog #4 → confirm address is clean.[[avito_scraper]]with deployed helper signature + migration062reference.Complexity / blast radius
Bot review · sha
9e1c7d5· automated deep-code-reviewer