feat(tradein): secondary_only — параметр расписания, выброшенное считается (#1781) #3008

Merged
bot-backend merged 1 commit from fix/1781-secondary-only-param into main 2026-08-20 20:30:01 +00:00
Collaborator

Готовит решение по #1781 / #2994, не принимая его: дефолт остаётся прежним, поведение прода не меняется ни на строку.

Что было

# pipeline.py:3328 — внутри run_cian_full_load
await scraper.fetch_all_secondary(
    ...
    secondary_only=True,    хардкод
)

Включить новостройки в полный обход можно было только деплоем. Теперь это параметр с тем же дефолтом True, проброшенный до CianFullLoadRequest — то есть до scrape_schedules.default_params. Включение и откат становятся правкой одной ячейки в БД.

Почему это дешевле, чем кажется

Новостройки не пропускаются при запросе. Они скачиваются, разбираются и выбрасываются последним шагом:

# cian/serp.py — внутри _paginate_leaf_bucket
if secondary_only:
    filtered = [lot for lot in bucket_lots if lot.listing_segment != "novostroyki"]
    dropped_nb = collected_this_bucket - len(filtered)

Так сделано намеренно: SERP-параметр object_type=1 у Cian ненадёжен (~5 % выдачи), поэтому фильтруют по authoritative listing_segment из offer.newbuilding.id — уже после парсинга.

Проверил, что «ноль лишних запросов» — не догадка, а следствие кода: проба бакета берёт totalOffers из Redux-состояния SERP, дробление считается как pages_needed = ceil(totalOffers / offers_per_page), и totalOffers включает обе категории. Страницы с новостройками уже скачаны — и лимитом страниц, и антибан-бюджетом за них уже заплачено. Выбрасывается только результат разбора.

Второе: выброшенное теперь считается

dropped_nb логировался, но в scrape_runs.counters не попадал. Из-за этого на вопрос «сколько инвентаря выбрасывает полный обход» я не смог ответить задним числом: логи за 17.08 (последний cian_full_load) уже ротировались — docker logs --since 120h не находит ни одной строки «cian:» ни в одном контейнере.

Добавлен dropped_novostroyki в CianFullLoadCounters — по тому же доводу, что записан рядом у partial_buckets: «видно только грепом логов, которые теряются при редеплое».

Копится в атрибуте инстанса, а не аргументом on_bucket: у колбэка есть внешние реализации, менять его сигнатуру ради счётчика нельзя. Сброс на каждый прогон — инстанс переиспользуется, иначе второй прогон унаследовал бы число первого.

Замер, ради которого это делается

Прод, 21.08.2026 — доля активного инвентаря, которую свип подтверждает своим приходом:

источник / сегмент за неделю за месяц
cian / vtorichka 75.4 % 100.0 %
avito / novostroyki 57.0 % 100.0 %
cian / novostroyki 5.1 % 11.7 %

11 993 активные строки cian/novostroyki, медианный возраст 81 сутки, 10 585 старше 30 суток. Именно этот пробел даёт большую часть «26 203 фантомов» из #2994 — и он же не даёт расширить туда деактивацию: при охвате 11.7 % TTL=30 снёс бы ≈88 % инвентаря, ровно механизм #2659.

Как проверено

  • Двусторонне: против origin/main пять тестов красные, и краснота везде по значению, а не по отсутствию символа — ни одного KeyError. Сообщения перечисляют фактическое состояние: параметра нет в сигнатуре; параметры: ['db', 'run_id', ...], поля нет в запросе; поля: [...].
  • Контроли зелёные с обеих сторон: дефолт остаётся True (без этого контроля правка «сделать параметром» могла бы тихо включить сбор новостроек на проде); фильтр при secondary_only=True на месте и по-прежнему зависит от флага.
  • pytest tradein-mvp/backend4644 passed, 23 skipped.

Что дальше — решение владельца

Включать ли secondary_only=false для cian_full_load. Первый же прогон с ним даст точное число новых строк, а dropped_novostroyki в counters — цену вопроса и без включения, на ближайшем обычном прогоне.

Готовит решение по #1781 / #2994, **не принимая его**: дефолт остаётся прежним, поведение прода не меняется ни на строку. ## Что было ```python # pipeline.py:3328 — внутри run_cian_full_load await scraper.fetch_all_secondary( ... secondary_only=True, ← хардкод ) ``` Включить новостройки в полный обход можно было только деплоем. Теперь это параметр с тем же дефолтом `True`, проброшенный до `CianFullLoadRequest` — то есть до `scrape_schedules.default_params`. Включение и откат становятся правкой одной ячейки в БД. ## Почему это дешевле, чем кажется Новостройки **не пропускаются при запросе**. Они скачиваются, разбираются и выбрасываются последним шагом: ```python # cian/serp.py — внутри _paginate_leaf_bucket if secondary_only: filtered = [lot for lot in bucket_lots if lot.listing_segment != "novostroyki"] dropped_nb = collected_this_bucket - len(filtered) ``` Так сделано намеренно: SERP-параметр `object_type=1` у Cian ненадёжен (~5 % выдачи), поэтому фильтруют по authoritative `listing_segment` из `offer.newbuilding.id` — уже после парсинга. Проверил, что «ноль лишних запросов» — не догадка, а следствие кода: проба бакета берёт `totalOffers` из Redux-состояния SERP, дробление считается как `pages_needed = ceil(totalOffers / offers_per_page)`, и `totalOffers` включает **обе** категории. Страницы с новостройками уже скачаны — и лимитом страниц, и антибан-бюджетом за них уже заплачено. Выбрасывается только результат разбора. ## Второе: выброшенное теперь считается `dropped_nb` **логировался**, но в `scrape_runs.counters` не попадал. Из-за этого на вопрос «сколько инвентаря выбрасывает полный обход» я не смог ответить задним числом: логи за 17.08 (последний `cian_full_load`) уже ротировались — `docker logs --since 120h` не находит ни одной строки «cian:» ни в одном контейнере. Добавлен `dropped_novostroyki` в `CianFullLoadCounters` — по тому же доводу, что записан рядом у `partial_buckets`: «видно только грепом логов, которые теряются при редеплое». Копится в атрибуте инстанса, а не аргументом `on_bucket`: у колбэка есть внешние реализации, менять его сигнатуру ради счётчика нельзя. Сброс на каждый прогон — инстанс переиспользуется, иначе второй прогон унаследовал бы число первого. ## Замер, ради которого это делается Прод, 21.08.2026 — доля активного инвентаря, которую свип подтверждает своим приходом: | источник / сегмент | за неделю | **за месяц** | |---|---|---| | cian / vtorichka | 75.4 % | **100.0 %** | | avito / novostroyki | 57.0 % | **100.0 %** | | **cian / novostroyki** | **5.1 %** | **11.7 %** | 11 993 активные строки cian/novostroyki, медианный возраст **81 сутки**, 10 585 старше 30 суток. Именно этот пробел даёт большую часть «26 203 фантомов» из #2994 — и он же не даёт расширить туда деактивацию: при охвате 11.7 % TTL=30 снёс бы ≈88 % инвентаря, ровно механизм #2659. ## Как проверено - **Двусторонне:** против `origin/main` пять тестов красные, и краснота везде **по значению**, а не по отсутствию символа — ни одного `KeyError`. Сообщения перечисляют фактическое состояние: `параметра нет в сигнатуре; параметры: ['db', 'run_id', ...]`, `поля нет в запросе; поля: [...]`. - **Контроли зелёные с обеих сторон:** дефолт остаётся `True` (без этого контроля правка «сделать параметром» могла бы тихо включить сбор новостроек на проде); фильтр при `secondary_only=True` на месте и по-прежнему зависит от флага. - `pytest tradein-mvp/backend` — **4644 passed**, 23 skipped. ## Что дальше — решение владельца Включать ли `secondary_only=false` для `cian_full_load`. Первый же прогон с ним даст точное число новых строк, а `dropped_novostroyki` в counters — цену вопроса и без включения, на ближайшем обычном прогоне.
bot-backend added 1 commit 2026-08-20 20:08:56 +00:00
feat(tradein): secondary_only — параметр расписания, выброшенное считается (#1781)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 9s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI Trade-In / browser-tests (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 4m27s
b9cfdaa040
`run_cian_full_load` передавал `secondary_only=True` жёстко, поэтому
включить новостройки в полный обход можно было только деплоем. Теперь это
параметр с ТЕМ ЖЕ дефолтом `True` — поведение прода не меняется ни на
строку, но решение становится правкой одной ячейки
`scrape_schedules.default_params`, а не выкаткой кода. Откат — тем же
движением.

Почему это важно именно здесь. Новостройки НЕ пропускаются при запросе:
они скачиваются, разбираются и выбрасываются последним шагом
(`cian/serp.py`), потому что SERP-параметр `object_type=1` у Cian
ненадёжен (~5 % выдачи) и фильтруют по authoritative `listing_segment`
после парсинга. Проба бакета берёт `totalOffers` из Redux-состояния SERP
и считает `pages_needed = ceil(totalOffers / offers_per_page)`, а
`totalOffers` включает ОБЕ категории — то есть страницы с новостройками
уже скачаны, лимит страниц и антибан-бюджет за них уже заплачены.
Включение стоит ноль дополнительных запросов.

Заодно `dropped_novostroyki` сохраняется в counters прогона. Счётчик
логировался (`dropped_nb=`), но не персистился, и ответить «сколько
инвентаря выбрасывает полный обход» задним числом было нечем: логи за
17.08 уже ротировались — `docker logs --since 120h` не находит ни строки
«cian:» ни в одном контейнере. Тот же довод, по которому рядом заведён
`partial_buckets`. Копится в атрибуте инстанса, а не аргументом
`on_bucket`: у колбэка есть внешние реализации, менять его сигнатуру
ради счётчика нельзя. Сброс на каждый прогон — инстанс переиспользуется.

Замер, ради которого это делается (прод 21.08): месячный охват свипа
cian/novostroyki — 11.7 % против 100 % у cian/vtorichka и
avito/novostroyki; 11 993 активные строки, медианный возраст 81 сутки,
10 585 старше 30 суток. Подробности и оговорки — в #1781 и #2994.

Двусторонне: против origin/main пять тестов красные, и краснота везде по
значению, а не по отсутствию символа — ни одного KeyError. Сообщения
перечисляют фактическое состояние («параметра нет в сигнатуре; параметры:
[...]», «поля нет в запросе; поля: [...]»).

Контроли зелёные с обеих сторон: дефолт остаётся True (иначе правка тихо
включила бы сбор новостроек на проде — это отдельное решение с замером);
фильтр при `secondary_only=True` остаётся на месте и по-прежнему зависит
от флага.

pytest tradein-mvp/backend — 4644 passed, 23 skipped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
bot-backend merged commit 172a36a202 into main 2026-08-20 20:30:01 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
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#3008
No description provided.