fix(scrapers): normalize cian descriptionMinhash list[int]→str + silent-0 guard (Refs #836) #840
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
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#840
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/836-cian-minhash"
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?
Root cause (live-confirmed prod 2026-05-30 20:06)
Cian сменил
descriptionMinhashstring → list[int](~2026-05-23). В_offer_to_lotсырое значение шло вScrapedLot(...)→ScrapedLot.description_minhash: str | Noneотбраковывалlist→ ValidationError → per-offer try/except выбрасывал каждый оффер → cian sweepsfetched>0, inserted=0(заморожен с 23 мая). НЕ прокси/куки/бан — чистый parse-регресс схемы.Primary fix (
cian.py_offer_to_lot)Нормализация по типу перед конструктором (точный контракт #836):
⚠️ БЕЗ
sorted()— порядок minhash-бэндов значим для LSH (детерминирован на повторный scrape → dedup/Tier-2 не ломается). SHA1-fallback ниже без изменений (срабатывает когда minhash отсутствует).Secondary hardening — silent-0 guard
В
_parse_serp_htmlпосле offer-loop:raw_count>0 && saved_count==0→logger.error(...)+ best-effortsentry_sdk.capture_message(level=error)(try/except: pass,settings.glitchtip_dsn). Чтобы следующая schema-смена ловилась мониторингом, а не ручным тестом (класс #754/#823).Scope
cian.py+ тест только.base.pyНЕ тронут (optional field_validator пропущен — tight scope на shared ScrapedLot pre-demo). Auth/cookies/#819 concat-state не тронуты.Тесты
Новый
test_cian_serp_description_minhash_list_int:[8439545,5781654,555882,407717]→"8439545,5781654,555882,407717", оффер НЕ отбракован. Существующий string-кейс зелёный. 22 passed, ruff clean.QA после deploy
POST /admin/scrape {sources:["cian"]}центр ЕКБ →fetched>0 И inserted>0;grep -c 'cian _offer_to_lot failed'→ 0;SELECT max(last_seen_at) FROM listings WHERE source='cian'→ свежий (не 2026-05-23).Refs #836
✅ APPROVE — критический root-cause фикс cian schema-регресса (#836), dedup-safe (проверено).
Проверено:
descriptionMinhashstr→list[int] ~05-23 →ScrapedLot.description_minhash: str|Noneотбраковывал list → ValidationError → каждый оффер дропался →fetched>0 inserted=0. Фикс нормализует list→",".joinДО конструктора. str/None ветки + SHA1-fallback сохранены.description_minhash—matching/listings.pyTier-2 = exact-match (WHERE description_minhash = :hash, line 95), НЕ band-split (LSH — future work, коммент Stage 8.x). Comma-формат для exact-match format-agnostic;sorted()НЕ применён → re-scrape детерминирован → Tier-2 матчит сам себя. Каждый source уже хранит гетерогенные форматы (cian-str / SHA1-hex / теперь comma-ints) — exact-match это переваривает.raw_count>0 && saved_count==0→logger.error+ best-effortsentry_sdk.capture_message(guardedsettings.glitchtip_dsn+ try/except). Ловит будущие schema-регрессы мониторингом (комплементарно #833 — другой слой: per-parse vs per-run).Anti-regression: scope cian.py+test, base.py не тронут (broader field_validator отложен — tight pre-demo scope, разумно). Нет конфликта с merged #818 (proxy
__aenter__)/#819 (state-parser). Секретов/инъекции нет (bind-param:hash, dsn из settings).Мержу. #836 → qa. Post-deploy:
/admin/scrape cianЕКБ →fetched>0 И inserted>0;max(last_seen_at) WHERE source='cian'свежий.