Скрапперы A1: detail-пути ДомКлика, Циана и Яндекса не проставляют ban_kind — 23 бана Авито числятся unknown, у Яндекса ветка бана мертва #3196

Closed
opened 2026-08-28 19:17:02 +00:00 by lekss361 · 1 comment
Owner

Первый шаг унификации скрапперов: сделать диагноз отказа сопоставимым между поставщиками. Пока причина бана не записывается, любой разбор простоя начинается с ручных замеров — как это было при разборе #3190, где две недели banned оказались тремя разными явлениями под одним ярлыком.

Факты с прода (проверено 28.08, окно 14 дней)

Источник Итог
avito_detail_backfill 54 бана: 28 platform, 23 unknown, 3 infra
domclick_detail_backfill 14 банов, все unknown
yandex_detail_backfill 12 done, 1 failed, 1 zombie, 0 банов

Яндекс здесь не «здоров» — он не умеет зарегистрировать бан вовсе: counters['blocked'] не ведётся, поэтому ветка перевода прогона в banned (scrape_runs.py:911) недостижима по построению. Любой блок площадки у него выглядит как failed или просто как прогон поменьше.

У ДомКлика ban_kind не передаётся в финализатор, поэтому unknown стоит всегда — включая случаи, когда причина точно известна (DomClickBlockedError с маркерами QRATOR).

Что сделать

  • Проставлять ban_kind в detail-путях ДомКлика, Циана и Яндекса — образец готов у Авито (avito_detail_backfill.py:749, набор ban_kinds).
  • Завести counters['blocked'] у Яндекса, чтобы ветка бана перестала быть мёртвой.
  • Расширить ban_kind_of_exception (pipeline.py:198-222) так, чтобы unknown оставался только для действительно неопознанных отказов.

Отдельно стоит развести три случая, которые сейчас схлопываются в один: блок площадки, разлогин сессии и обычный сбой сети. Сегодня DomClickBlockedError поднимается и на anti-bot маркер, и на любой сбой браузерного фетча — по счётчикам они неразличимы.

Приёмка

За неделю после мержа доля unknown среди банов падает; у Яндекса появляются прогоны со статусом banned, если блоки есть, и не появляются, если их нет.

SELECT source, ban_kind, count(*)
FROM scrape_runs
WHERE started_at > now() - interval '7 days' AND status = 'banned'
GROUP BY 1, 2 ORDER BY 1, 2;

Границы

Только диагноз отказа. Не чинить сам сбор, не трогать пороги остановки, пул прокси и чекпоинты — это соседние задачи.

Часть плана унификации скрапперов (шаг A1). Refs #3190, #3172

Первый шаг унификации скрапперов: сделать диагноз отказа сопоставимым между поставщиками. Пока причина бана не записывается, любой разбор простоя начинается с ручных замеров — как это было при разборе #3190, где две недели `banned` оказались тремя разными явлениями под одним ярлыком. ## Факты с прода (проверено 28.08, окно 14 дней) | Источник | Итог | |---|---| | `avito_detail_backfill` | 54 бана: 28 `platform`, 23 **`unknown`**, 3 `infra` | | `domclick_detail_backfill` | 14 банов, **все `unknown`** | | `yandex_detail_backfill` | 12 `done`, 1 `failed`, 1 `zombie`, **0 банов** | Яндекс здесь не «здоров» — он не умеет зарегистрировать бан вовсе: `counters['blocked']` не ведётся, поэтому ветка перевода прогона в `banned` (`scrape_runs.py:911`) недостижима по построению. Любой блок площадки у него выглядит как `failed` или просто как прогон поменьше. У ДомКлика `ban_kind` не передаётся в финализатор, поэтому `unknown` стоит всегда — включая случаи, когда причина точно известна (`DomClickBlockedError` с маркерами QRATOR). ## Что сделать - Проставлять `ban_kind` в detail-путях ДомКлика, Циана и Яндекса — образец готов у Авито (`avito_detail_backfill.py:749`, набор `ban_kinds`). - Завести `counters['blocked']` у Яндекса, чтобы ветка бана перестала быть мёртвой. - Расширить `ban_kind_of_exception` (`pipeline.py:198-222`) так, чтобы `unknown` оставался только для действительно неопознанных отказов. Отдельно стоит развести три случая, которые сейчас схлопываются в один: блок площадки, разлогин сессии и обычный сбой сети. Сегодня `DomClickBlockedError` поднимается и на anti-bot маркер, и на любой сбой браузерного фетча — по счётчикам они неразличимы. ## Приёмка За неделю после мержа доля `unknown` среди банов падает; у Яндекса появляются прогоны со статусом `banned`, если блоки есть, и не появляются, если их нет. ```sql SELECT source, ban_kind, count(*) FROM scrape_runs WHERE started_at > now() - interval '7 days' AND status = 'banned' GROUP BY 1, 2 ORDER BY 1, 2; ``` ## Границы Только диагноз отказа. Не чинить сам сбор, не трогать пороги остановки, пул прокси и чекпоинты — это соседние задачи. Часть плана унификации скрапперов (шаг A1). Refs #3190, #3172
lekss361 added the
bug
observability
priority/p1
scope/backend
scrapers
tradein
labels 2026-08-28 19:17:30 +00:00
Author
Owner

Добавляется в scope: сайдкар не читает HTTP-статус навигации

Живой замер по ДомКлику (подробности и сырьё — #3190) вскрыл причину, по которой ban_kind=unknown в принципе неизбежен на браузерном пути, сколько бы типов исключений мы ни завели наверху.

_fetch_once в tradein-mvp/browser/server.py делает page.goto(...), игнорирует возвращённый Response и определяет характер ответа только по текстовым маркерам в HTML:

_CHALLENGE_MARKERS = ("startpow", "доступ ограничен: проверка безопасности")
_BAN_MARKERS = ("доступ ограничен: проблема с ip",)

Оба списка сняты с авитовских страниц. Блокирующий ответ ДомКлика — статическая страница 403 | Домклик на 26 624 байта — не совпадает ни с одним маркером, поэтому уходит наверх как валидный контент. Дальше парсер не находит __SSR_STATE__, поднимает DomClickBlockedError, и причина теряется: HTTP-статус, в котором она была написана прямым текстом, никто не прочитал.

Это же объясняет, почему добавления ban_kind наверху недостаточно — снизу нечего классифицировать.

Что добавить к задаче

  • Читать статус ответа page.goto в сайдкаре и прокидывать его наверх вместе с HTML. 403/429 не нуждаются в интерпретации по тексту.
  • Развести по статусу: 403/429 → лимит или отказ площадки; 5xx → инфраструктура; 200 + отсутствие ожидаемого маркера контента → разбирать текстом, как сейчас.
  • Маркерные списки перестают быть единственным способом диагноза и остаются как дополнение для случаев, когда площадка отдаёт 200 с заглушкой (как Авито с PoW).

Правка небольшая, но именно она делает ban_kind осмысленным на всех четырёх поставщиках сразу, а не только у того, чьи маркеры зашиты.

## Добавляется в scope: сайдкар не читает HTTP-статус навигации Живой замер по ДомКлику (подробности и сырьё — #3190) вскрыл причину, по которой `ban_kind=unknown` в принципе неизбежен на браузерном пути, сколько бы типов исключений мы ни завели наверху. `_fetch_once` в `tradein-mvp/browser/server.py` делает `page.goto(...)`, **игнорирует возвращённый Response** и определяет характер ответа только по текстовым маркерам в HTML: ```python _CHALLENGE_MARKERS = ("startpow", "доступ ограничен: проверка безопасности") _BAN_MARKERS = ("доступ ограничен: проблема с ip",) ``` Оба списка сняты с авитовских страниц. Блокирующий ответ ДомКлика — статическая страница `403 | Домклик` на 26 624 байта — не совпадает ни с одним маркером, поэтому уходит наверх **как валидный контент**. Дальше парсер не находит `__SSR_STATE__`, поднимает `DomClickBlockedError`, и причина теряется: HTTP-статус, в котором она была написана прямым текстом, никто не прочитал. Это же объясняет, почему добавления `ban_kind` наверху недостаточно — снизу нечего классифицировать. ## Что добавить к задаче - Читать статус ответа `page.goto` в сайдкаре и прокидывать его наверх вместе с HTML. `403`/`429` не нуждаются в интерпретации по тексту. - Развести по статусу: `403`/`429` → лимит или отказ площадки; `5xx` → инфраструктура; `200` + отсутствие ожидаемого маркера контента → разбирать текстом, как сейчас. - Маркерные списки перестают быть единственным способом диагноза и остаются как дополнение для случаев, когда площадка отдаёт `200` с заглушкой (как Авито с PoW). Правка небольшая, но именно она делает `ban_kind` осмысленным на всех четырёх поставщиках сразу, а не только у того, чьи маркеры зашиты.
Sign in to join this conversation.
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#3196
No description provided.