fix(scraper-kit): yandex _leaf под CAP не пишется в done-леджер как complete — capped_buckets отдельным счётчиком #3373

Merged
bot-backend merged 2 commits from fix/3368-yandex-leaf-complete-flag into main 2026-09-05 20:22:50 +00:00
Collaborator

Closes #3368 (follow-up #3362). _leaf yandex, обрезанный max_pages_per_bucket, писался как complete → с containment интервал зачитывался целиком, хвост крупного бакета терялся при резюме.

Почему leaf переполняется (разбор, не догадка)

walk_price_range уходит в leaf при not can_split; расщепляемый leaf при cap=500 даёт ≤25 страниц — переполниться не может. pages_needed > max_pages (>1000 офферов) возможен ровно там, где делить нечем: размах < _YANDEX_SPLIT_MIN_BRACKET=500_000 (финальные окна ~250к), открытый брекет 250М+, depth>=8 (недостижим). То есть бисекция уже отработала — исход «неполон по построению». По сетке: 0-2 бакета на прогон (плотная середина 2-к 3.5-4.5М) ≈ 50 лишних запросов на резюме; хвост они не доберут ни в одном прогоне — настоящее лечение в порогах (min_bracket ниже / max_pages выше), поэтому счётчик отдельный: capped_bucketspartial_buckets.

_mark_bucket не тронут — путь тот же, что у _degraded (#3362).

Тесты

test_3368_yandex_leaf_cap_complete.py: scoped 31 passed, полный 5465 passed, 35 skipped, ruff чист. Фальсификация (откат исходников): бакеты […] прочитаны на 1 страницу из 100 (totalItems=2000), но помечены complete, резюм не сделал ни одного запроса … assert 0 > 0 — по значению (первый вариант падал AttributeError — заменён на getattr(…, 0)).

Найдено по пути (не в этом PR)

yandex _leaf/_degraded зовут on_bucket(key, len(seen), …) (int), а run_yandex_full_load._on_bucket типизирован lots: list и передаёт его в save_listings — расхождение пред-существующее; отдельный issue.

Прод

counters.partial_buckets теперь двигают и capped-бакеты — ожидание 0-2 на прогон yandex full-load.

Closes #3368 (follow-up #3362). `_leaf` yandex, обрезанный `max_pages_per_bucket`, писался как complete → с containment интервал зачитывался целиком, хвост крупного бакета терялся при резюме. ## Почему leaf переполняется (разбор, не догадка) `walk_price_range` уходит в leaf при `not can_split`; расщепляемый leaf при `cap=500` даёт ≤25 страниц — переполниться не может. `pages_needed > max_pages` (>1000 офферов) возможен ровно там, где делить нечем: размах < `_YANDEX_SPLIT_MIN_BRACKET=500_000` (финальные окна ~250к), открытый брекет `250М+`, `depth>=8` (недостижим). То есть бисекция уже отработала — исход «неполон по построению». По сетке: 0-2 бакета на прогон (плотная середина 2-к 3.5-4.5М) ≈ 50 лишних запросов на резюме; хвост они не доберут ни в одном прогоне — настоящее лечение в порогах (`min_bracket` ниже / `max_pages` выше), поэтому счётчик отдельный: `capped_buckets` ⊂ `partial_buckets`. `_mark_bucket` не тронут — путь тот же, что у `_degraded` (#3362). ## Тесты `test_3368_yandex_leaf_cap_complete.py`: scoped `31 passed`, **полный `5465 passed, 35 skipped`**, ruff чист. Фальсификация (откат исходников): `бакеты […] прочитаны на 1 страницу из 100 (totalItems=2000), но помечены complete`, `резюм не сделал ни одного запроса … assert 0 > 0` — по значению (первый вариант падал AttributeError — заменён на `getattr(…, 0)`). ## Найдено по пути (не в этом PR) yandex `_leaf/_degraded` зовут `on_bucket(key, len(seen), …)` (int), а `run_yandex_full_load._on_bucket` типизирован `lots: list` и передаёт его в `save_listings` — расхождение пред-существующее; отдельный issue. ## Прод `counters.partial_buckets` теперь двигают и capped-бакеты — ожидание 0-2 на прогон yandex full-load.
bot-backend added 1 commit 2026-09-05 19:59:35 +00:00
fix(scraper-kit/yandex): обрезанный потолком страниц leaf не идёт в чекпоинт
All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI / changes (pull_request) Successful in 9s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 4m54s
5ad0d1a304
После #3362 degraded-ветка отмечается complete=False, а `_leaf` писал бакет как
полный, даже когда его пагинация упиралась в max_pages_per_bucket/_GATE_MAX_PAGES_CAP.
С containment-гейтом (#3358/#3359) такой ключ покрывает свой интервал целиком, и
резюм больше не заходит в полосу, чей хвост не читали ни разу.

Флаг полноты — как у cian: complete = pages_needed <= max_pages, передаётся в
on_bucket на обоих выходах leaf'а (_mark_bucket кладёт в done только complete).
Переполненный leaf возможен только там, где бисекции дробить нечем (размах <
min_bracket, открытый верхний брекет, потолок глубины) — это честный исход
«бакет неполон по построению», поэтому он ещё и считается отдельно
(scraper.capped_buckets → counters.capped_buckets): лечится не повтором прогона,
а порогами бисекции.

Closes #3368
Light1YT added 1 commit 2026-09-05 20:16:31 +00:00
fix(scraper-kit/yandex): выпавшая страница leaf'а делает бакет неполным; capped_buckets переживает дрейн
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 12s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m5s
9519c89d92
Ревью #3373, два minor:
1. `_leaf` считал полноту только по потолку страниц — упавшая страница
   (`payload=None` → `[]`) молча уходила в чекпоинт как собранная. Теперь
   паритет с cian по-настоящему: `complete = not capped and dropped_pages == 0`.
2. `counters.capped_buckets` присваивался ПОСЛЕ await — cancel/shutdown
   (RuntimeError из `_on_bucket`) уносил управление мимо строки. Перенесено в
   `finally`, счётчик виден в финальном payload дрейна.

Нит: `ceil(total / 20)` → `_GATE_PAGE_SIZE`.

Тесты по значению: бакет с 1 выпавшей страницей из 3 — не в done-леджере
(+ контроль: 3 успешных страницы по-прежнему complete); дрейн после обрезанного
бакета → `capped_buckets == 1` в финальных counters. Фейк `_DrainAtFirstBucket`
(#3355) теперь объявляет `capped_buckets` явно: его catch-all `__getattr__`
отдавал корутину на любое имя и ронял сериализацию counters.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
bot-backend merged commit f129d52cbe into main 2026-09-05 20:22:50 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#3373
No description provided.