fix(cian): капча видна в отчёте прогона (ban_kinds.platform по типу) и банит узел на curl-пути (#3402 follow-up) #3406

Merged
bot-backend merged 1 commit from fix/3402-followups-ban-kinds-curl into main 2026-09-06 10:40:12 +00:00
Collaborator

Follow-up к #3402 / PR #3403 (два замечания ревью). Не закрывает issue (приёмка 13:24 UTC).

1. Капча-волна не была видна в отчёте прогона. _note_refusal в cian_history_backfill.py ключевался по HTTP-статусу (ban_kind_from_status): на сайдкар-пути None, на своём детекте 200 → ban_kinds пустой, рос только listings_failed_fetch. Теперь _note_refusal(result, status, exc=None) (:101, :123, вызов :271) — диагноз сперва по ТИПУ через ban_kind_of_exception, статус — фолбэк. ban_kind_of_exception (orchestration/pipeline.py:221): isinstance(exc, AvitoBlockedError | AvitoRateLimitedError)isinstance(exc, ProxyBanError | AvitoRateLimitedError)CianBlockedError(ProxyBanError) раньше уезжал в unknown; для avito ноль изменений (AvitoBlockedError(AvitoError, ProxyBanError)), DomClickBlockedError тоже перестаёт быть unknown; ветка BAN_KIND_INFRA выше и не задета. Инвариант #3196: unknown по типу и None по статусу → счётчики не растут. SidecarBanPageError до бэкфилла не долетает (конвертируется в CianBlockedError в fetch_detail:192).

2. curl-путь детектил капчу, но не банил. Детект перенесён ВНУТРЬ with curl_proxy_url(...) (providers/cian/detail.py:257-262, сразу после html = resp.text) — finally хелпера сам делает mark_banned(source='cian'); общий предикат _is_captcha_title (:141) для обоих путей, чтобы маркеры не разъехались.

Порог banned (ответ на вопрос ревью): scheduler.py:199banned при ban_kinds непустом и нуле успехов; порога по числу отказов нет ни для 403 (#3196/#2700), ни теперь для капчи — правка кладёт капчу в ту же корзину, что 403, не понижает порог. Что грубо: banned на одном наблюдении читается как «Циан отбил нас», а показано «отбил 1-2 узла» (ротация на 3-м провале; «всем узлам» подкреплено с ~3×N подряд). Гейт «не раньше ротации» — отдельное решение, одинаковое для 403 и капчи. Побочно: после mark_banned текущая аренда живёт до 3-го провала — 2 фетча уходят через уже забаненный узел.

Тесты (tests/test_3402_cian_captcha_http200.py:207/235/262): счётчики батча {"platform": 1}; curl-путь → mark_banned(1,'cian'), mark_health(ok=True) не вызван; «Ошибка - Циан» на curl-пути по-прежнему не банит (контроль от «баним всё подряд»).

Фальсификация (git apply -R исходников): assert {} == {'platform': 1}, assert [] == [(1, 'cian')] — 2 failed. Восстановлено.

Прогоны: полный backend 5602 passed, 35 skipped (rc=0); ruff OK.

Приёмка: прогон с капчей — ban_kinds.platform > 0, blocked > 0, ban_kind='platform'; cian_city_sweep detail-фаза с капчей — свежая строка в scrape_proxy_source_bans (source='cian') на капча-узле.

Follow-up к #3402 / PR #3403 (два замечания ревью). Не закрывает issue (приёмка 13:24 UTC). **1. Капча-волна не была видна в отчёте прогона.** `_note_refusal` в `cian_history_backfill.py` ключевался по HTTP-статусу (`ban_kind_from_status`): на сайдкар-пути `None`, на своём детекте 200 → `ban_kinds` пустой, рос только `listings_failed_fetch`. Теперь `_note_refusal(result, status, exc=None)` (`:101`, `:123`, вызов `:271`) — диагноз сперва по ТИПУ через `ban_kind_of_exception`, статус — фолбэк. `ban_kind_of_exception` (`orchestration/pipeline.py:221`): `isinstance(exc, AvitoBlockedError | AvitoRateLimitedError)` → `isinstance(exc, ProxyBanError | AvitoRateLimitedError)` — `CianBlockedError(ProxyBanError)` раньше уезжал в `unknown`; для avito ноль изменений (`AvitoBlockedError(AvitoError, ProxyBanError)`), `DomClickBlockedError` тоже перестаёт быть `unknown`; ветка `BAN_KIND_INFRA` выше и не задета. Инвариант #3196: `unknown` по типу и `None` по статусу → счётчики не растут. `SidecarBanPageError` до бэкфилла не долетает (конвертируется в `CianBlockedError` в `fetch_detail:192`). **2. curl-путь детектил капчу, но не банил.** Детект перенесён ВНУТРЬ `with curl_proxy_url(...)` (`providers/cian/detail.py:257-262`, сразу после `html = resp.text`) — `finally` хелпера сам делает `mark_banned(source='cian')`; общий предикат `_is_captcha_title` (`:141`) для обоих путей, чтобы маркеры не разъехались. **Порог `banned` (ответ на вопрос ревью):** `scheduler.py:199` — `banned` при `ban_kinds` непустом и нуле успехов; порога по числу отказов нет ни для 403 (#3196/#2700), ни теперь для капчи — правка кладёт капчу в ту же корзину, что 403, не понижает порог. Что грубо: `banned` на одном наблюдении читается как «Циан отбил нас», а показано «отбил 1-2 узла» (ротация на 3-м провале; «всем узлам» подкреплено с ~3×N подряд). Гейт «не раньше ротации» — отдельное решение, одинаковое для 403 и капчи. Побочно: после `mark_banned` текущая аренда живёт до 3-го провала — 2 фетча уходят через уже забаненный узел. **Тесты** (`tests/test_3402_cian_captcha_http200.py:207/235/262`): счётчики батча `{"platform": 1}`; curl-путь → `mark_banned(1,'cian')`, `mark_health(ok=True)` не вызван; «Ошибка - Циан» на curl-пути по-прежнему не банит (контроль от «баним всё подряд»). **Фальсификация** (`git apply -R` исходников): `assert {} == {'platform': 1}`, `assert [] == [(1, 'cian')]` — 2 failed. Восстановлено. **Прогоны:** полный backend `5602 passed, 35 skipped` (rc=0); ruff OK. **Приёмка:** прогон с капчей — `ban_kinds.platform > 0`, `blocked > 0`, `ban_kind='platform'`; `cian_city_sweep` detail-фаза с капчей — свежая строка в `scrape_proxy_source_bans` (`source='cian'`) на капча-узле.
bot-backend added 1 commit 2026-09-06 10:32:31 +00:00
fix(cian): капча видна в отчёте прогона и банит узел на curl-пути (#3402)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / changes (pull_request) Successful in 11s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m11s
224f91e8fa
Два follow-up из ревью #3403.

1. Капча-волна была невидима в счётчиках. `_note_refusal` ключевался только по
   HTTP-статусу, а капча приходит с 200 (свой детект по <title>) или без статуса
   (сайдкар) → рос один `listings_failed_fetch`, `ban_kinds` оставался пустым, и
   волна отказа площадки читалась как дрейф нашей разметки. Теперь диагноз берётся
   сперва по ТИПУ исключения (`ban_kind_of_exception`), статус — фолбэк. Инвариант
   #3196 сохранён: 'unknown' по типу И None по статусу по-прежнему ничего не пишут.

   `ban_kind_of_exception` расширен с `AvitoBlockedError` до `ProxyBanError` — это
   ровно тот mixin, по которому generic-прокси-слой уже снимает узел с выдачи
   источнику. Для Авито поведение не меняется (AvitoBlockedError его наследует),
   Cian/DomClick перестают приезжать как 'unknown'.

2. curl-путь детектил капчу, но не банил: общий parse-путь лежит ЗА границей
   `with curl_proxy_url(...)`, и `CianBlockedError` поднимался уже после
   `mark_health(ok=True)` — узел, которому Циан показывает капчу, оставался в
   выдаче Циану (дефект #2700, только на HTTP 200). Проверка перенесена ВНУТРЬ
   блока, `finally` хелпера сам делает `mark_banned(source='cian')`.

Тесты по значению (оба красные на main): батч с капчей → ban_kinds == {platform: 1};
curl-путь + HTML капчи → mark_banned == [(1, 'cian')], mark_health(ok=True) нет.
Author
Collaborator

Code-review (06.09): . Радиус расширения ban_kind_of_exception до ProxyBanError — no-op для всех девяти существующих вызывающих (за Avito-тьюплами и except NoProxyAvailableError; ветка infra первая), домклик-бэкфилл зовёт свой _ban_kind_of_block по статусу, BlockRatioBreaker.record_block — единственный вызывающий avito; поведение меняется ровно у нового _note_refusal. curl-путь: mark_banned + mark_health(False) + release в finally хелпера; оба теста краснеют по месту детекта (снять in-block детект — pytest.raises всё ещё зелёный, а mark_banned_calls == []). Известное следствие: капча-волна на curl-пути банит до ~3 узлов за прогон (_cian_detail_abort=3) — та же сделка, что #2700 для 403.

Code-review (06.09): ✅. Радиус расширения `ban_kind_of_exception` до `ProxyBanError` — no-op для всех девяти существующих вызывающих (за Avito-тьюплами и `except NoProxyAvailableError`; ветка `infra` первая), домклик-бэкфилл зовёт свой `_ban_kind_of_block` по статусу, `BlockRatioBreaker.record_block` — единственный вызывающий avito; поведение меняется ровно у нового `_note_refusal`. curl-путь: `mark_banned` + `mark_health(False)` + release в `finally` хелпера; оба теста краснеют по месту детекта (снять in-block детект — `pytest.raises` всё ещё зелёный, а `mark_banned_calls == []`). Известное следствие: капча-волна на curl-пути банит до ~3 узлов за прогон (`_cian_detail_abort=3`) — та же сделка, что #2700 для 403.
bot-backend merged commit 75b0931fad into main 2026-09-06 10:40:12 +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#3406
No description provided.