fix(tradein/scraper): блок QRATOR у Домклика больше не помечает прогон успешным (#2657) #2667

Merged
bot-backend merged 1 commit from fix/2657-domclick-block-not-done into main 2026-08-05 18:49:21 +00:00
Collaborator

Что было

run_domclick_city_sweep имел honest-status гейт (#1968), но он требовал одновременно блок И ноль лотов:

if counters.lots_fetched == 0 and (counters.blocked or counters.errors_count > 0):
    mark_failed(...)
else:
    mark_done(...)

QRATOR срабатывает не с первого запроса — к моменту блока часть комнатных бакетов уже собрана, lots_fetched > 0, и распознанный блок уходил в done.

Прод (scrape_runs, source=domclick_city_sweep, все прогоны с ключом blocked в counters):

status blocked прогонов
done 1 13
failed 0 20

То есть 13 из 13 прогонов, где блок был распознан, получили done — ни один распознанный блок ни разу не дал не-done статус. При этом они забирали 39-464 лота вместо ~6300 по ЕКБ. А 20 failed — это другой путь (blocked=0, errors_count=1), там блок до DomClickBlockedError не доехал.

Что стало

Распознанный блок НИКОГДА не даёт done:

if counters.blocked:
    mark_banned(...)          # блок = прогон оборван
elif counters.lots_fetched == 0 and counters.errors_count > 0:
    mark_failed(...)          # без изменений
else:
    mark_done(...)            # честная пустота — без изменений

Чем отличается от #2642

#2642 для Циана/Яндекса ввёл правило attempts > 0 and failures == attemptsbanned, с явным анти-флапом: провал одного anchor'а среди успешных баном не считается.

У Домклика anchor'ов нет — sweep линейный по ROOM_BUCKETS, и первый же DomClickBlockedError делает break в fetch_city: оставшиеся бакеты не пробуются вовсе. Считать это «частичным провалом, который не бан» нельзя — прогон физически не доделал работу. Поэтому порог здесь не «все попытки провалились», а «блок был». Анти-флап-риска нет: счётчик blocked растёт только из DomClickBlockedError, то есть только по маркерам DOMCLICK_BLOCK_MARKERS.

Статус выбран banned, а не failed — симметрично #2642: это external constraint, а не наш баг, и это тот статус, который доступен как триггер ротации IP (#2611). Сама ротация из кода не вызывается (ASOCKS_API_TOKEN на владельце) — тот же скоуп-гард, что в #2642.

Побочно: случай «блок + 0 лотов» тоже переехал failedbanned — теперь у распознанного блока одно правило, а не два разных статуса. Для алерта это нейтрально: _alert_if_consecutive_failures считает failed и banned одинаково.

Путь до прокси-пула не тронут

fetcher.report_ban(...) (#2600 п.1) живёт в providers/domclick/serp.py::fetch_city, срабатывает внутри живого lease и раньше финализации прогона. Правка только в pipeline.py, в serp.py ни строки. Статус прогона и пометка прокси — разные вещи, обе на месте; регрессию стережёт существующий test_fetch_city_reports_ban_on_qrator_block.

Test plan

  • test_domclick_city_sweep_blocked_with_lots_marks_banned — новый, ловит ровно прод-случай (блок после части лотов)
  • test_domclick_city_sweep_blocked_banned — блок + 0 лотов → banned (был blocked_failed)
  • test_domclick_city_sweep_fetch_errors_without_block_stays_failed — анти-оверрич: ошибки без блока остаются failed
  • test_domclick_city_sweep_honest_empty_stays_done — анти-оверрич: честная пустота остаётся done
  • Фальсификация (патч-методом, откат только реализации): 2 теста краснеют — mark_done вместо mark_banned и mark_failed вместо mark_banned. Два анти-оверрич-теста зелёные и без фикса — это гарды от регрессии в другую сторону, не доказательство фикса; говорю честно.
  • Полный сьют tradein: 3394 passed, 1 failed — test_search_api.py::test_search_cache_hit (401 vs 200), красный и на чистом origin/main, к этой правке отношения не имеет.
  • Post-deploy: ближайший domclick_city_sweep должен дать banned вместо done (блокировки там ежедневные — верификация ожидается в течение суток, в отличие от Циана/Яндекса, где живого эпизода с #2642 так и не было).

Что НЕ входит и почему

  • п.1 issue (восстановить охват свипа) — упирается в прокси (#2638), решение владельца. Этот PR делает статус честным, но лотов не добавляет: после деплоя Домклик станет выглядеть в мониторинге хуже, потому что он и есть хуже.
  • п.3 issue (судьба scripts/ingest_domclick_jsonl.py) — отдельное решение (прошить через run_id либо убрать).
  • Ротация IP по banned — статус теперь достижим как триггер, вызов не подключён (тот же гард, что в #2642).
  • 20 прогонов с blocked=0, errors=1 — блок, не доехавший до DomClickBlockedError (исключение из fetcher'а вместо HTML с маркерами). Они и сейчас честно failed, не done, поэтому в скоуп «блок не должен быть done» не попадают. Отдельный вопрос — стоит ли их классифицировать как блок; прод-логи за тот период уже ротировались (контейнер перезапущен), диагностировать сейчас не на чем.

Refs #2657

## Что было `run_domclick_city_sweep` имел honest-status гейт (#1968), но он требовал **одновременно** блок И ноль лотов: ```python if counters.lots_fetched == 0 and (counters.blocked or counters.errors_count > 0): mark_failed(...) else: mark_done(...) ``` QRATOR срабатывает не с первого запроса — к моменту блока часть комнатных бакетов уже собрана, `lots_fetched > 0`, и распознанный блок уходил в `done`. Прод (`scrape_runs`, source=`domclick_city_sweep`, все прогоны с ключом `blocked` в counters): | status | blocked | прогонов | |---|---|---| | `done` | 1 | **13** | | `failed` | 0 | 20 | То есть **13 из 13** прогонов, где блок был распознан, получили `done` — ни один распознанный блок ни разу не дал не-`done` статус. При этом они забирали 39-464 лота вместо ~6300 по ЕКБ. А 20 `failed` — это другой путь (`blocked=0`, `errors_count=1`), там блок до `DomClickBlockedError` не доехал. ## Что стало Распознанный блок НИКОГДА не даёт `done`: ```python if counters.blocked: mark_banned(...) # блок = прогон оборван elif counters.lots_fetched == 0 and counters.errors_count > 0: mark_failed(...) # без изменений else: mark_done(...) # честная пустота — без изменений ``` ## Чем отличается от #2642 #2642 для Циана/Яндекса ввёл правило `attempts > 0 and failures == attempts` → `banned`, с явным анти-флапом: провал одного anchor'а среди успешных баном не считается. У Домклика anchor'ов нет — sweep линейный по `ROOM_BUCKETS`, и первый же `DomClickBlockedError` делает `break` в `fetch_city`: **оставшиеся бакеты не пробуются вовсе**. Считать это «частичным провалом, который не бан» нельзя — прогон физически не доделал работу. Поэтому порог здесь не «все попытки провалились», а «блок был». Анти-флап-риска нет: счётчик `blocked` растёт только из `DomClickBlockedError`, то есть только по маркерам `DOMCLICK_BLOCK_MARKERS`. Статус выбран `banned`, а не `failed` — симметрично #2642: это external constraint, а не наш баг, и это тот статус, который доступен как триггер ротации IP (#2611). Сама ротация из кода не вызывается (`ASOCKS_API_TOKEN` на владельце) — тот же скоуп-гард, что в #2642. Побочно: случай «блок + 0 лотов» тоже переехал `failed` → `banned` — теперь у распознанного блока одно правило, а не два разных статуса. Для алерта это нейтрально: `_alert_if_consecutive_failures` считает `failed` и `banned` одинаково. ## Путь до прокси-пула не тронут `fetcher.report_ban(...)` (#2600 п.1) живёт в `providers/domclick/serp.py::fetch_city`, срабатывает внутри живого lease и раньше финализации прогона. Правка только в `pipeline.py`, в serp.py ни строки. Статус прогона и пометка прокси — разные вещи, обе на месте; регрессию стережёт существующий `test_fetch_city_reports_ban_on_qrator_block`. ## Test plan - [x] `test_domclick_city_sweep_blocked_with_lots_marks_banned` — новый, ловит ровно прод-случай (блок после части лотов) - [x] `test_domclick_city_sweep_blocked_banned` — блок + 0 лотов → `banned` (был `blocked_failed`) - [x] `test_domclick_city_sweep_fetch_errors_without_block_stays_failed` — анти-оверрич: ошибки без блока остаются `failed` - [x] `test_domclick_city_sweep_honest_empty_stays_done` — анти-оверрич: честная пустота остаётся `done` - [x] **Фальсификация** (патч-методом, откат только реализации): 2 теста краснеют — `mark_done` вместо `mark_banned` и `mark_failed` вместо `mark_banned`. Два анти-оверрич-теста зелёные и без фикса — это гарды от регрессии в другую сторону, не доказательство фикса; говорю честно. - [x] Полный сьют tradein: 3394 passed, 1 failed — `test_search_api.py::test_search_cache_hit` (401 vs 200), **красный и на чистом `origin/main`**, к этой правке отношения не имеет. - [ ] Post-deploy: ближайший `domclick_city_sweep` должен дать `banned` вместо `done` (блокировки там ежедневные — верификация ожидается в течение суток, в отличие от Циана/Яндекса, где живого эпизода с #2642 так и не было). ## Что НЕ входит и почему - **п.1 issue (восстановить охват свипа)** — упирается в прокси (#2638), решение владельца. Этот PR делает статус честным, но лотов не добавляет: после деплоя Домклик станет выглядеть в мониторинге хуже, потому что он и есть хуже. - **п.3 issue (судьба `scripts/ingest_domclick_jsonl.py`)** — отдельное решение (прошить через `run_id` либо убрать). - **Ротация IP по `banned`** — статус теперь достижим как триггер, вызов не подключён (тот же гард, что в #2642). - **20 прогонов с `blocked=0, errors=1`** — блок, не доехавший до `DomClickBlockedError` (исключение из fetcher'а вместо HTML с маркерами). Они и сейчас честно `failed`, не `done`, поэтому в скоуп «блок не должен быть done» не попадают. Отдельный вопрос — стоит ли их классифицировать как блок; прод-логи за тот период уже ротировались (контейнер перезапущен), диагностировать сейчас не на чем. Refs #2657
bot-backend added 1 commit 2026-08-05 18:39:31 +00:00
fix(tradein/scraper): блок QRATOR у Домклика больше не помечает прогон успешным (#2657)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI / changes (pull_request) Successful in 8s
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 2m43s
9b9f299922
Honest-status в run_domclick_city_sweep требовал ОДНОВРЕМЕННО блок И ноль лотов,
поэтому распознанный QRATOR-блок после первых собранных лотов уходил в `done`.
На проде это 13 из 13 прогонов с blocked=1 (39-464 лота вместо ~6300) — ни один
распознанный блок ни разу не дал не-`done` статус.

Домклик структурно отличается от cian/yandex (#2625/#2642): там независимые
anchor'ы и провал одного среди успешных — не бан (анти-флап). Здесь anchor'ов нет,
sweep линейный по ROOM_BUCKETS, и первый же блок делает break — оставшиеся бакеты
не пробуются вовсе. Значит блок = прогон оборван, сколько бы лотов он ни успел
взять до этого.

Теперь: blocked → mark_banned (external constraint, не наш баг; тот же статус,
что #2642 дал cian/yandex — доступен как триггер ротации IP #2611, сама ротация
не вызывается). Ноль лотов с fetch-ошибками, но БЕЗ блока → по-прежнему failed.
Честная пустота → по-прежнему done.

Пометка прокси-пула (fetcher.report_ban, #2600 п.1) не тронута — живёт в
providers/domclick/serp.py и срабатывает раньше и независимо от статуса прогона.

Refs #2657
bot-backend merged commit 301fbed0d7 into main 2026-08-05 18:49:21 +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#2667
No description provided.