Свип ДомКлика сохраняет лоты по корзинам, а не одним махом в конце #3592

Merged
lekss361 merged 1 commit from fix/domclick-incremental-save into main 2026-09-17 13:37:49 +00:00

1 commit

Author SHA1 Message Date
bot-backend
b4e0618025 fix(trade-in): свип ДомКлика сохраняет лоты по корзинам, а не одним махом в конце
All checks were successful
CI Trade-In / changes (pull_request) Successful in 17s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 23s
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 6m42s
На большой выдаче свип не отдавал НИЧЕГО. Замер: прогон 7344 (Москва, одиночный,
прокси 13) за два часа прошёл 2 корзины из 6 — 'st' 34 минуты, '1' 86+ минут.
Причина структурная: ДомКлик режет offset на 2000, поэтому диапазон делится
бисекцией по цене и каждый лист пагинируется отдельно. У Москвы ≈23 690 лотов
вторички против ≈6 300 у ЕКБ. Формула watchdog'а считает фетчи как 6 * pages и
объём выдачи не учитывает вовсе, так что снятие по таймауту было гарантировано,
а вместе с ним терялось всё собранное: сохранение было ОДНО, после всех корзин.

Теперь save_listings зовётся из колбэка on_bucket сразу после каждой успешной
корзины, и туда же переехал чекпоинт: done_buckets означает «собрано И
сохранено». Раньше он писался из scraper.completed_buckets уже после except
TimeoutError, то есть помечал пройденными корзины, у которых в БД ноль строк, —
следующий прогон пропускал их через skip_buckets, и за два-три цикла чекпоинт
закрывался целиком. Гард _saved (#2406) закрывал это «всё или ничего»; с
инкрементальным сохранением он не нужен и снят.

Колбэк в serp.py стоит ВНЕ try/except конкретной корзины — иначе generic
обработчик проглотил бы исключение из него. Это же даёт кооперативную отмену по
корзинам, которой у ДомКлика не было вовсе: is_cancelled проверялся только перед
SERP-фазой, и повисший свип нельзя было снять до watchdog'а, все три часа держа
один из двух узлов affinity='any'. Sentinel'ы RuntimeError("cancelled") и
("shutdown") и разбор ветки — тот же приём, что в run_cian_full_load.

watchdog_sec — явный override формулы, читается из default_params в ОБОИХ
хендлерах (kit-native и продуктовом, который перекрывает его ради кук Sber ID).
None сохраняет прежнюю формулу байт-в-байт, как и on_bucket=None.

Отдельно поправлены пять тестов, ломавшихся на переезде: их стабы подменяли
fetch_city и не звали on_bucket, поэтому после правки проверяли мёртвую ветку —
save_listings не вызывался вовсе. Теперь стабы вызывают колбэк, и проверяется
реальный путь.

Прогон: полный бэкенд-набор 6469 passed, 70 skipped. ruff check и format чисто.

Claude-Session: https://claude.ai/code/session_01NQb6WeJtagZwZnUsSjDizs
2026-09-17 16:23:32 +03:00