Общая тема: скрапер тихо возвращал пустоту вместо сигнала об ошибке, продукт
считал оценку по остаткам данных, не зная что источник отвалился.
1. avito serp.py (medium): citywide/byrooms/exhaustive sweep больше не путает
DOM-drift с "объявлений нет". _is_unexpected_empty_page() перепроверяет
независимый сигнал (_extract_total_count — счётчик page-title/count либо
no-results маркер): page=1 c 0 карточками и БЕЗ no-results маркера →
AvitoContentBlockedError (переиспользует существующий механизм #754/#779).
Exhaustive-бакеты (_paginate_leaf_bucket) получают expected_total из probe —
total>0 но 0 карточек после пагинации всех страниц тоже поднимает ошибку.
page>1 c 0 карточками остаётся graceful end-of-pagination (не regressed).
2. cian serp.py (low): _parse_serp_html сравнивает totalOffers vs
results.offers из ОДНОГО state-блоба. totalOffers>0 при пустом offers —
report как schema regression (elevated до ERROR + Glitchtip через новый
_report_schema_regression(), общий с существующей offer-level охраной).
Порог 0-vs->0 выбран т.к. это единственный позиционно-независимый сигнал:
функция не знает номер страницы, дробный порог ложно сработал бы на
легитимной последней частичной странице exhaustive-пагинации.
3. avito detail.py (low): publish_date больше не завышает свежесть лота на
границе года. Если "текущий год + месяц/день" даёт дату в будущем
относительно момента парсинга (декабрьское объявление читается в январе) —
откатываем на год назад.
4. cian_session.py (low): verify_session больше не конфлирует expired/
source-down/markup-changed. Новые VERIFY_SOURCE_UNAVAILABLE_SENTINEL (5xx,
network/timeout exception) и VERIFY_MARKUP_CHANGED_SENTINEL (HTTP 200 но
header-frontend auth-state не распарсился) отделены от None (единственный
сигнал реального логаута: 401 или isAuthenticated=false). Callers
(product_handlers.py::_cian_pre_claim, admin.py) НЕ обновлены — вне
разрешённого scope этого PR (только cian_session.py + scraper-kit),
отдельный follow-up.
Тесты: 19 новых (8 avito sweep drift, 6 cian totalOffers mismatch, 5 date
rollover) + обновлены 2 существующих (cian_session markup-changed вместо
None) под новый, более точный контракт. Полный backend suite:
2649 passed, 8 skipped, 1 deselected (без регрессий).
Удаляет весь `app/services/scrapers/` (16 файлов, ~7100 строк) — Part D (D1-D5)
убрал всех внешних вызывающих, 0 runtime importers подтверждено grep'ом на main.
Заодно:
- удалены 7 осиротевших локальных probe/sweep-скриптов (tradein-mvp/scripts/),
импортировавших уже-удалённые или удаляемые сейчас legacy-модули
- тест-хирургия по 65+ файлам: DELETE прямых legacy-юнит-тестов, RETARGET
тестов, тестирующих ещё живую бизнес-логику (переключены на scraper_kit.*
эквиваленты, включая quality-gate #781/#753/#754/#755/#773/#740), partial-delete
golden-parity тестов, потерявших legacy-оракл
- kit save_listings/AvitoScraper/etc. требуют инжектируемые matcher/config —
ретаргетированные тесты обновлены под новую сигнатуру (RealScraperConfig(),
MagicMock HouseMatcher, region_code=66)
Полный pytest suite зелёный (2255 passed, 6 skipped) кроме известного флейка
#2208 (test_search_cache_hit, не связан со scrapers).
Group B of the scraper_kit migration epic (#2277): switch the three files'
internal dependency imports from legacy app/services/scrapers/* to their
byte/structurally-identical scraper_kit equivalents, proven via the new
parity harness (tests/support/parity.assert_parity, #2304). Public API of
all three files is unchanged, so no callers needed updating.
- cian_price_history.py: fetch_detail/save_detail_enrichment now from
scraper_kit.providers.cian.detail. Wires config=RealScraperConfig() at
the call site — kit's fetch_detail only reads cian_proxy_url when an
explicit config is passed, unlike legacy's always-on settings read;
without this the admin-triggered backfill would silently drop its
datacenter-IP proxy (#806) after the import swap.
- cian_session.py: extract_state now from scraper_kit.cian_state_parser
(byte-identical to legacy).
- yandex_price_history.py: ScrapedLot now from scraper_kit.base
(byte-identical fields/methods; record_yandex_price_history only reads
lot.* via duck-typing, no isinstance checks).
New tests/test_scraper_kit_pricehistory_session_parity.py proves the
migration delta specifically (extract_state, ScrapedLot.model_dump(),
fetch_detail+DetailEnrichment with config injection, and the own-session
proxy-kwarg wiring). Full backend suite passes (3145 passed, 6 skipped,
1 pre-existing unrelated failure confirmed on unmodified forgejo/main).
Updated CIAN_REQUIRED_COOKIES filter в cian_session.py по реальному DevTools-дампу logged-in cian.ru сессии (2026-05-23). Добавлены critical auth cookies: DMIR_AUTH (httpOnly), _CIAN_GK, _yasc, _ym_visorc, uxfb_card_satisfaction. Legacy entries сохранены как backward-compat fallback. Regression test test_required_cookies_includes_real_auth гарантирует что все реальные cookies проходят filter.
Fixes Stage 4 PR #457 (filter guessed без real probe — DMIR_AUTH был dropped → verify_session falsey).