fix(tradein/scraper): статус «забанен» перестаёт выдавать наш сбой за чужой (#2686) #2711

Merged
bot-backend merged 1 commit from fix/2686-ban-split-and-alerts into main 2026-08-06 08:18:27 +00:00
Collaborator

Summary

Из 115 avito-прогонов со статусом banned (прод, 2026-08-06):

Что произошло на самом деле Прогонов Период
наш сайдкар (browser unavailable / browser-sidecar error) 92 04.07 – 03.08
площадка (Avito SERP firewall … IP banned) 10 16.06 – 06.08
HTTP 429/403 11 30.05 – 19.06
прочее 2 21.06, 23.06

Оба исхода писали разный текст ошибки и получали один статус. Классификатору не нужен новый источник знания — надо перестать схлопывать.

Механизм разведения — поле причины, не новый статус

  • AvitoSidecarUnavailableErrorподтип AvitoRateLimitedError, поднимается в единственном месте, где отказ порождён нашей инфраструктурой (providers/avito/serp.py, ветка except (HTTPStatusError, TimeoutException, TransportError) вокруг browser.fetch). Подтип, а не отдельный класс: все except (AvitoBlockedError, AvitoRateLimitedError) продолжают ловить его без правок — иначе прогон ушёл бы в mark_failed и потерял чекпоинт.
  • scrape_runs.ban_kind (platform | infra, миграция 218) — рядом со status='banned', а не вместо него.

Почему не новый статус:

  1. Побочная функция banned — сохранение done_buckets-чекпоинта — нужна обоим исходам; оставив статус, получаем её даром.
  2. Новое значение статуса пришлось бы доучить пяти местам, каждое из которых молча даёт неверный ответ, если про него забыть: CHECK-констрейнт, IN-списки обоих сторожей, Literal-фильтр admin API, хардкод-список статусов во фронте. Это ровно тот класс оборванной проводки, из-за которого задача и появилась.
  3. Прогон в обоих случаях требует одного обращения (оборвать, сохранить частичное); различается только диагноз — метаданное, не состояние.

Чекпоинт

run_avito_full_load пишет {**counters, "done_buckets": sorted(done)} в mark_banned — одинаково при ban_kind='infra' и ban_kind='platform'. Два теста стерегут оба исхода.

Признак несётся от места порождения

Рантайм классифицирует по типу исключения, не по подстроке. Разбор текста есть только в миграции — разовая ретро-классификация 115 накопленных строк, помеченная как таковая.

Чего тут НЕТ

Возврат такта Авито — вынесен в #2687 на данные 9–10.08.

Test plan

  • tests/test_2686_ban_kind_split.py — 12 тестов: место порождения (sidecar → подтип, firewall → не подтип), перевод типа в диагноз, доставка диагноза до mark_banned + сохранность done_buckets у обоих исходов, запись ban_kind в SQL обеими копиями runs-модуля.
  • Фальсификация: на коде origin/main файл не импортируется (нет ни типа, ни хелпера); при точечном снятии ban_kind= из run_avito_full_load падает test_full_load_sidecar_ban_is_infra_and_keeps_checkpoint (platform вместо infra).
  • Полный прогон backend-сьюта: 3587 passed, 1 failed — test_search_cache_hit, pre-existing order-dependent, deselect'ится в CI.
  • Правленые пути входят в scraper-allowlist deploy-tradein.yml (tradein-mvp/packages/scraper-kit/**) → правка доедет до tradein-scraper, который её исполняет.

Refs #2686

## Summary Из 115 avito-прогонов со статусом `banned` (прод, 2026-08-06): | Что произошло на самом деле | Прогонов | Период | |---|---:|---| | наш сайдкар (`browser unavailable` / `browser-sidecar error`) | **92** | 04.07 – 03.08 | | площадка (`Avito SERP firewall … IP banned`) | 10 | 16.06 – 06.08 | | HTTP 429/403 | 11 | 30.05 – 19.06 | | прочее | 2 | 21.06, 23.06 | Оба исхода писали **разный текст ошибки** и получали **один статус**. Классификатору не нужен новый источник знания — надо перестать схлопывать. ### Механизм разведения — поле причины, не новый статус - `AvitoSidecarUnavailableError` — **подтип** `AvitoRateLimitedError`, поднимается в единственном месте, где отказ порождён нашей инфраструктурой (`providers/avito/serp.py`, ветка `except (HTTPStatusError, TimeoutException, TransportError)` вокруг `browser.fetch`). Подтип, а не отдельный класс: все `except (AvitoBlockedError, AvitoRateLimitedError)` продолжают ловить его без правок — иначе прогон ушёл бы в `mark_failed` и **потерял чекпоинт**. - `scrape_runs.ban_kind` (`platform` | `infra`, миграция **218**) — **рядом** со `status='banned'`, а не вместо него. Почему не новый статус: 1. Побочная функция `banned` — сохранение `done_buckets`-чекпоинта — нужна **обоим** исходам; оставив статус, получаем её даром. 2. Новое значение статуса пришлось бы доучить пяти местам, каждое из которых молча даёт неверный ответ, если про него забыть: CHECK-констрейнт, IN-списки обоих сторожей, `Literal`-фильтр admin API, хардкод-список статусов во фронте. Это ровно тот класс оборванной проводки, из-за которого задача и появилась. 3. Прогон в обоих случаях требует одного обращения (оборвать, сохранить частичное); различается только диагноз — метаданное, не состояние. ### Чекпоинт `run_avito_full_load` пишет `{**counters, "done_buckets": sorted(done)}` в `mark_banned` — одинаково при `ban_kind='infra'` и `ban_kind='platform'`. Два теста стерегут оба исхода. ### Признак несётся от места порождения Рантайм классифицирует по **типу исключения**, не по подстроке. Разбор текста есть только в миграции — разовая ретро-классификация 115 накопленных строк, помеченная как таковая. ### Чего тут НЕТ Возврат такта Авито — вынесен в #2687 на данные 9–10.08. ## Test plan - [x] `tests/test_2686_ban_kind_split.py` — 12 тестов: место порождения (sidecar → подтип, firewall → не подтип), перевод типа в диагноз, доставка диагноза до `mark_banned` + сохранность `done_buckets` у обоих исходов, запись `ban_kind` в SQL обеими копиями runs-модуля. - [x] Фальсификация: на коде `origin/main` файл не импортируется (нет ни типа, ни хелпера); при точечном снятии `ban_kind=` из `run_avito_full_load` падает `test_full_load_sidecar_ban_is_infra_and_keeps_checkpoint` (`platform` вместо `infra`). - [x] Полный прогон backend-сьюта: 3587 passed, 1 failed — `test_search_cache_hit`, pre-existing order-dependent, deselect'ится в CI. - [x] Правленые пути входят в scraper-allowlist `deploy-tradein.yml` (`tradein-mvp/packages/scraper-kit/**`) → правка доедет до `tradein-scraper`, который её исполняет. Refs #2686
bot-backend added 1 commit 2026-08-06 08:14:35 +00:00
fix(tradein/scraper): статус «забанен» перестаёт выдавать наш сбой за чужой (#2686)
All checks were successful
CI / changes (pull_request) Successful in 8s
CI Trade-In / changes (pull_request) Successful in 9s
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 3m4s
f18440021c
Из 115 avito-прогонов со статусом 'banned' 92 (80%) — отказ НАШЕГО браузерного
сайдкара («browser unavailable (proxy may be down)»), 10 — реальный firewall
площадки, 11 — HTTP 429/403. Оба исхода писали разный текст ошибки и получали
один статус: различитель лежал в данных и терялся в момент присвоения. По этому
статусу миграция 206 замедлила avito_full_load_exhaustive более чем вдвое —
основание было ложным.

Разводим не пути, а диагноз:
- AvitoSidecarUnavailableError — подтип AvitoRateLimitedError, поднимается ровно
  там, где отказ порождён нашей инфраструктурой. Подтип, а не отдельный класс,
  чтобы все `except (AvitoBlockedError, AvitoRateLimitedError)` продолжали ловить
  его без изменений — иначе прогон ушёл бы в mark_failed и потерял чекпоинт;
- scrape_runs.ban_kind ('platform' | 'infra', миграция 218) рядом со status,
  а не вместо него. Новое значение статуса пришлось бы доучить пяти местам
  (CHECK, IN-списки обоих сторожей, Literal admin API, список статусов во
  фронте), каждое из которых молча врёт, если про него забыть;
- побочная функция 'banned' — сохранение done_buckets-чекпоинта — остаётся у
  ОБОИХ исходов: следующий прогон не начинает с нуля ни при нашем отказе, ни при
  блокировке площадкой. Тест стережёт оба.

Признак несётся от места порождения (тип исключения), а не разбирается из текста
постфактум; разбор текста в миграции — разовая ретро-классификация истории.

Возврат такта Авито сюда НЕ входит — он вынесен в #2687 на данные 9-10.08.

Refs #2686
bot-backend merged commit 9e5e9fca08 into main 2026-08-06 08:18:27 +00:00
bot-backend deleted branch fix/2686-ban-split-and-alerts 2026-08-06 08:18:27 +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#2711
No description provided.