[HIGH] tradein/scraper: охват Домклика упал в 8-10 раз — прод-свип даёт ~1% предложения, а видимый пул держался внешним ручным ингестом #2657

Closed
opened 2026-08-05 15:17:03 +00:00 by bot-backend · 11 comments
Collaborator

Обвал охвата Домклика: прод-свип даёт ~1% предложения, а видимый пул держался внешним ручным ингестом.

Статус: третий пункт закрыт (PR #2667, смержен и проверен на проде). Первый и третий из «что решать» остаются — они на владельце.


Найдено при разборе #2574. Домклик выглядел здоровым: сбор ежедневный, свежесть идеальная. Но активных объявлений 376 против ~6 296 месяц назад, и объяснение оказалось хуже, чем «сузили охват».

Пул держался не тем, чем казалось

Те ~6 296 активных никогда не приходили из прод-развёртки. Их залил внешний ручной ингест scripts/ingest_domclick_jsonl.pysave_listings без run_id: в listings_snapshots видно ровно 6 296 строк в сутки с run_id IS NULL с 28.06 по 18.07, дальше ноль. Все 6 128 «мёртвых» строк заморожены на одном scraped_at = 2026-07-18 11:54:46.215350 — одна транзакция.

То есть месяц наш «домкликовский инвентарь» был снимком, залитым руками и ни разу не подтверждённым.

Прод-свип сломался ещё 13 июля

domclick_city_sweep: 18 прогонов подряд failed («QRATOR block or fetch errors — 0 listings»). После частичного восстановления 31.07 даёт 39-59 лотов в сутки против 347-464 в начале июля. Это примерно 1% реального предложения Екатеринбурга.

Обвал счётчика произошёл в одном прогоне

deactivate_stale_domklik, run_id=2960, 2026-08-02 07:26, TTL=14 дней по scraped_at (включён миграцией 183) — деактивировал 6 131 строку разом. До этого дня порог NOW()-14d просто ещё не доставал до 18 июля.

Миграция 206 к обвалу отношения не имеет: она выключила только domclick_detail_backfill и задеплоена в тот же день, но на 7 часов позже деактивации; темп domclick_city_sweep она не трогала. Я сам подозревал её первой — не подтвердилось.

Потерь между «найдено» и «сохранено» нет: lots_fetched == inserted + updated в каждом прогоне. Теряется всё выше по потоку, на блоке QRATOR.

Блок QRATOR больше не помечает прогон успешным — сделано

PR #2667. Оказалось, что моя формулировка была неверна: блок никогда не давал ноль лотов (срабатывает после сбора части бакетов, 39-464 лота), а гейт честности требовал одновременно блока и нуля — условие недостижимое, поэтому все 13 блоков из 13 уходили в done. Подробности и две поправки к моим утверждениям — в комментариях ниже.

Что остаётся решать (на владельце)

  1. Восстановить охват domclick_city_sweep — упирается в QRATOR, то есть в прокси (#2638), либо признать, что Домклик даёт 1% рынка, и перестать считать его полноценным источником в покрытии.
  2. Судьба scripts/ingest_domclick_jsonl.py: ручной ингест без run_id создаёт инвентарь, неотличимый от собранного, но не подтверждаемый и невидимый для журнала прогонов. Либо прошить его через обычный путь с run_id, либо убрать.

Связано: #2574, #2625, #2638, #2670, миграции 183 и 206.

Обвал охвата Домклика: прод-свип даёт ~1% предложения, а видимый пул держался внешним ручным ингестом. **Статус: третий пункт закрыт (PR #2667, смержен и проверен на проде). Первый и третий из «что решать» остаются — они на владельце.** --- Найдено при разборе #2574. Домклик выглядел здоровым: сбор ежедневный, свежесть идеальная. Но активных объявлений 376 против ~6 296 месяц назад, и объяснение оказалось хуже, чем «сузили охват». ## Пул держался не тем, чем казалось Те ~6 296 активных **никогда не приходили из прод-развёртки**. Их залил внешний ручной ингест `scripts/ingest_domclick_jsonl.py` → `save_listings` без `run_id`: в `listings_snapshots` видно ровно 6 296 строк в сутки с `run_id IS NULL` с 28.06 по 18.07, дальше ноль. Все 6 128 «мёртвых» строк заморожены на одном `scraped_at = 2026-07-18 11:54:46.215350` — одна транзакция. То есть месяц наш «домкликовский инвентарь» был снимком, залитым руками и ни разу не подтверждённым. ## Прод-свип сломался ещё 13 июля `domclick_city_sweep`: 18 прогонов подряд `failed` («QRATOR block or fetch errors — 0 listings»). После частичного восстановления 31.07 даёт **39-59 лотов в сутки** против 347-464 в начале июля. Это примерно **1% реального предложения Екатеринбурга**. ## Обвал счётчика произошёл в одном прогоне `deactivate_stale_domklik`, `run_id=2960`, 2026-08-02 07:26, TTL=14 дней по `scraped_at` (включён миграцией 183) — деактивировал **6 131 строку** разом. До этого дня порог `NOW()-14d` просто ещё не доставал до 18 июля. Миграция 206 к обвалу отношения не имеет: она выключила только `domclick_detail_backfill` и задеплоена в тот же день, но на 7 часов позже деактивации; темп `domclick_city_sweep` она не трогала. Я сам подозревал её первой — не подтвердилось. Потерь между «найдено» и «сохранено» нет: `lots_fetched == inserted + updated` в каждом прогоне. Теряется всё выше по потоку, на блоке QRATOR. ## ✅ Блок QRATOR больше не помечает прогон успешным — сделано PR #2667. Оказалось, что моя формулировка была неверна: блок **никогда** не давал ноль лотов (срабатывает после сбора части бакетов, 39-464 лота), а гейт честности требовал одновременно блока **и** нуля — условие недостижимое, поэтому все 13 блоков из 13 уходили в `done`. Подробности и две поправки к моим утверждениям — в комментариях ниже. ## Что остаётся решать (на владельце) 1. **Восстановить охват** `domclick_city_sweep` — упирается в QRATOR, то есть в прокси (#2638), либо признать, что Домклик даёт 1% рынка, и перестать считать его полноценным источником в покрытии. 2. **Судьба `scripts/ingest_domclick_jsonl.py`**: ручной ингест без `run_id` создаёт инвентарь, неотличимый от собранного, но не подтверждаемый и невидимый для журнала прогонов. Либо прошить его через обычный путь с `run_id`, либо убрать. Связано: #2574, #2625, #2638, #2670, миграции 183 и 206.
Author
Collaborator

Working on this in PR #2667 — только пункт 2 issue (прогон с блоком не должен быть done).

Прод-факт: 13 из 13 прогонов domclick_city_sweep с blocked=1 уходили в done, потому что honest-status (#1968) требовал ОДНОВРЕМЕННО блок и ноль лотов, а QRATOR обрывает sweep уже после первых собранных бакетов. 20 failed-прогонов — другой путь (blocked=0, errors_count=1), там блок до DomClickBlockedError не доехал.

Пункты 1 (охват свипа) и 3 (ingest_domclick_jsonl.py) не трогал.

Working on this in PR #2667 — только пункт 2 issue (прогон с блоком не должен быть `done`). Прод-факт: 13 из 13 прогонов `domclick_city_sweep` с `blocked=1` уходили в `done`, потому что honest-status (#1968) требовал ОДНОВРЕМЕННО блок и ноль лотов, а QRATOR обрывает sweep уже после первых собранных бакетов. 20 `failed`-прогонов — другой путь (`blocked=0`, `errors_count=1`), там блок до `DomClickBlockedError` не доехал. Пункты 1 (охват свипа) и 3 (`ingest_domclick_jsonl.py`) не трогал.
Author
Collaborator

Поправка к третьему пункту, который я сам же и сформулировал неверно. PR #2667 (открыт, на ревью).

Я написал «блок обрывает все бакеты, забирает ноль лотов — и прогон всё равно done». Прод-данные говорят другое:

статус признак блока прогонов
done есть 13
failed нет 20

Распознанный блок никогда не давал ноль лотов. Он срабатывает уже после того, как часть комнатных бакетов собрана — 39-464 лота при ёмкости города около 6300. А проверка честного статуса (#1968) требовала одновременно блок и ноль лотов. Это условие на живых данных не выполнялось ни разу, поэтому все 13 распознанных блоков из 13 ушли в done.

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

Это третий случай того же класса за сегодня, и они складываются в закономерность:

  • статус «пропущено» существовал с миграции 015, был локализован во фронте — и имел ноль строк за всю историю (#2658);
  • алерт про протухшие куки Циана стоял в ветке, до которой исполнение не доходит никогда, потому что загрузчик сессии отсеивает просроченное раньше (#2658);
  • здесь — гейт честности с условием «И», где вторая половина никогда не истинна.

Общее у всех трёх: код написан, ревью пройдено, и никто не проверил, что условие вообще достижимо. Тестами такое не ловится — тест пишут на тот случай, который автор представлял.

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

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

Статус выбран banned, а не failed — симметрично #2642: это внешнее ограничение, и он же триггерит ротацию адреса (#2611, когда появится токен).

Сигнал в прокси-пул не тронут: он живёт в парсере, срабатывает внутри живой аренды и раньше финализации прогона. Статус прогона и пометка прокси — разные вещи, обе нужны.

Что изменится в картине мониторинга

Домклик начнёт уходить в banned ежедневно, пока не починены прокси (#2638). Считаю это правдой, а не шумом: охват реально около 1% городского предложения, и молчаливое done эту правду скрывало. Но картина в админке изменится, и лучше знать заранее.

Что осталось непонятым

Двадцать прогонов с ошибкой, но без признака блока — вероятно тоже QRATOR, только прилетевший исключением, а не страницей с маркерами. Они честно failed, поэтому под «блок не должен быть done» не подпадают. Диагностировать нечем: контейнер скрапера перезапущен деплоем, логов за тот период не осталось. Это довод в пользу того, чтобы логи скрапера не терялись при редеплое — отдельный разговор.

Поправка к третьему пункту, который я сам же и сформулировал неверно. PR #2667 (открыт, на ревью). Я написал «блок обрывает все бакеты, забирает ноль лотов — и прогон всё равно `done`». Прод-данные говорят другое: | статус | признак блока | прогонов | |---|---|---| | `done` | есть | **13** | | `failed` | нет | 20 | **Распознанный блок никогда не давал ноль лотов.** Он срабатывает уже после того, как часть комнатных бакетов собрана — 39-464 лота при ёмкости города около 6300. А проверка честного статуса (#1968) требовала **одновременно** блок **и** ноль лотов. Это условие на живых данных не выполнялось ни разу, поэтому все 13 распознанных блоков из 13 ушли в `done`. То есть механизм честности был построен, но его условие было составлено так, что сработать не могло. **Это третий случай того же класса за сегодня**, и они складываются в закономерность: - статус «пропущено» существовал с миграции 015, был локализован во фронте — и имел ноль строк за всю историю (#2658); - алерт про протухшие куки Циана стоял в ветке, до которой исполнение не доходит никогда, потому что загрузчик сессии отсеивает просроченное раньше (#2658); - здесь — гейт честности с условием «И», где вторая половина никогда не истинна. Общее у всех трёх: **код написан, ревью пройдено, и никто не проверил, что условие вообще достижимо**. Тестами такое не ловится — тест пишут на тот случай, который автор представлял. ## Чем отличается фикс от #2642 У Циана и Яндекса порог — «все попытки провалились», с защитой от ложного срабатывания на независимых точках входа. У Домклика точек входа нет: свип линейный по комнатным бакетам, первое же исключение блока прерывает цикл, и оставшиеся бакеты не пробуются вовсе. Поэтому порог здесь — сам факт блока, а не доля неудач. Статус выбран `banned`, а не `failed` — симметрично #2642: это внешнее ограничение, и он же триггерит ротацию адреса (#2611, когда появится токен). Сигнал в прокси-пул не тронут: он живёт в парсере, срабатывает внутри живой аренды и раньше финализации прогона. Статус прогона и пометка прокси — разные вещи, обе нужны. ## Что изменится в картине мониторинга **Домклик начнёт уходить в `banned` ежедневно**, пока не починены прокси (#2638). Считаю это правдой, а не шумом: охват реально около 1% городского предложения, и молчаливое `done` эту правду скрывало. Но картина в админке изменится, и лучше знать заранее. ## Что осталось непонятым Двадцать прогонов с ошибкой, но **без** признака блока — вероятно тоже QRATOR, только прилетевший исключением, а не страницей с маркерами. Они честно `failed`, поэтому под «блок не должен быть `done`» не подпадают. Диагностировать нечем: контейнер скрапера перезапущен деплоем, логов за тот период не осталось. Это довод в пользу того, чтобы логи скрапера не терялись при редеплое — отдельный разговор.
Author
Collaborator

Третий пункт закрыт — PR #2667 смержен. Ревью поправило два моих утверждения, оба в сторону «я перестраховался или ошибся».

Опасение про шум алертов было перевёрнутым

Я писал, что после правки Домклик будет ежедневно уходить в banned и завалит алерты. Проверка кода показала обратное: алерт срабатывает на точной серии из трёх, а не на каждом прогоне — условие требует, чтобы три последних прогона были неуспешны и четвёртый был успешен. Как только серия становится длиннее трёх, алерт замолкает навсегда.

И вот что интереснее: сегодня те самые 13 успешных прогонов с блоком серию РАЗРЫВАЮТ, позволяя алерту взводиться заново и срабатывать каждый раз, когда неудачи возобновляются. После правки серия становится непрерывной. Разложение по дням: первый день без алерта, второй без, на третий — один алерт, дальше тишина.

То есть правка уменьшает количество алертов, а не увеличивает. Дедупликация не нужна, и строить её не надо.

Настоящий риск — зеркальный: постоянно забаненный источник замолкает навсегда, повторного напоминания нет. Это не привнесено этой правкой (так устроено с #1968), но именно она делает длинную серию бана ожидаемым состоянием до починки прокси. Завожу отдельно.

Статус banned сегодня не триггерит ротацию

Я написал, что banned заодно запустит ротацию адреса. Это неверно на сегодня: у Домклика фетчер собирается без подключения к прокси-пулу (незакрытая проводка #2160 P4, в коде рядом стоит комментарий об этом), поэтому сигнал бана в пул — пустышка. Значит и ротация не сработает.

То есть новый статус — чистая наблюдаемость, пока Домклик не подключен к пулу. Это не отменяет правку (видеть правду важно само по себе), но убирает из неё половину, которую я ей приписал.

Что подтвердилось

Переезд случая «блок и ноль лотов» из failed в banned инертен: ни одной строки на проде под него не подпадает. Потребители статусов проверены поимённо — планировщик смотрит только на running, админский фильтр и фронт banned уже умеют, отката темпа по статусу нет, алерт считает оба одинаково.

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

Ещё одна мина в шаге отсюда

Та же форма дефекта уцелела рядом: если прогон прервётся по таймауту или исключению после того, как часть лотов собрана, он попадёт в ветку «успешно». Такой прогон ровно так же неполон, как заблокированный, но отчитается зелёным. На проде пока не случалось (строк с ошибками и ненулевым сбором нет), но это тот же класс, что и весь сегодняшний список. Завожу отдельно.

Третий пункт закрыт — PR #2667 смержен. Ревью поправило **два моих утверждения**, оба в сторону «я перестраховался или ошибся». ## Опасение про шум алертов было перевёрнутым Я писал, что после правки Домклик будет ежедневно уходить в `banned` и завалит алерты. Проверка кода показала обратное: **алерт срабатывает на точной серии из трёх**, а не на каждом прогоне — условие требует, чтобы три последних прогона были неуспешны **и** четвёртый был успешен. Как только серия становится длиннее трёх, алерт замолкает навсегда. И вот что интереснее: **сегодня те самые 13 успешных прогонов с блоком серию РАЗРЫВАЮТ**, позволяя алерту взводиться заново и срабатывать каждый раз, когда неудачи возобновляются. После правки серия становится непрерывной. Разложение по дням: первый день без алерта, второй без, на третий — **один** алерт, дальше тишина. То есть правка **уменьшает** количество алертов, а не увеличивает. Дедупликация не нужна, и строить её не надо. Настоящий риск — зеркальный: постоянно забаненный источник замолкает навсегда, повторного напоминания нет. Это не привнесено этой правкой (так устроено с #1968), но именно она делает длинную серию бана ожидаемым состоянием до починки прокси. Завожу отдельно. ## Статус `banned` сегодня не триггерит ротацию Я написал, что `banned` заодно запустит ротацию адреса. **Это неверно на сегодня**: у Домклика фетчер собирается без подключения к прокси-пулу (незакрытая проводка #2160 P4, в коде рядом стоит комментарий об этом), поэтому сигнал бана в пул — **пустышка**. Значит и ротация не сработает. То есть новый статус — чистая наблюдаемость, пока Домклик не подключен к пулу. Это не отменяет правку (видеть правду важно само по себе), но убирает из неё половину, которую я ей приписал. ## Что подтвердилось Переезд случая «блок и ноль лотов» из `failed` в `banned` **инертен**: ни одной строки на проде под него не подпадает. Потребители статусов проверены поимённо — планировщик смотрит только на `running`, админский фильтр и фронт `banned` уже умеют, отката темпа по статусу нет, алерт считает оба одинаково. Прерывание цикла на первом же блоке — структурный факт, а не эвристика: единственный путь с продолжением цикла это ошибка разбора, а не блок. Частичного блока, при котором объявлять весь прогон забаненным было бы неверно, не бывает. ## Ещё одна мина в шаге отсюда Та же форма дефекта уцелела рядом: если прогон прервётся по таймауту или исключению **после** того, как часть лотов собрана, он попадёт в ветку «успешно». Такой прогон ровно так же неполон, как заблокированный, но отчитается зелёным. На проде пока не случалось (строк с ошибками и ненулевым сбором нет), но это тот же класс, что и весь сегодняшний список. Завожу отдельно.
Author
Collaborator

Постановка проверена на проде сегодня (09.08) — устарела в худшую сторону, но не опровергнута: не «~1% предложения», а ноль.

дата domclick cian yandex avito
09.08 0 (failed, 0/6 бакетов) 1680 1952 120
08.08 0 1680 2744 159
07.08 0 1680 2669 134
06.08 0 1680 1239 131
05.08 59 1540 2643 300

listings source='domklik': 376 активных, max(scraped_at)=2026-08-05 05:43 — четверо суток без подтверждения. Контрольная группа отвечает: упал один Домклик, прокси/инфраструктура в целом живы.

Где теряется — «не собираем», причём не там, где искали. Логи прогона 3502:

BrowserFetcher: клиент создан, proxy_lease_id=None
tradein-browser: {"error": "Page.goto: NS_ERROR_PROXY_BAD_GATEWAY"}
buckets=0/6 errors=1 blocked=0

Домклик — единственный sweep-источник без proxy_provider (#2160 P4), поэтому идёт через статический SCRAPER_PROXY_URL = scrape_proxies.id=1. Живая проба тем же трактом, один URL BFF: через id=1 → 500 NS_ERROR_PROXY_BAD_GATEWAY, через мобильный узел пула id=10 → 200 и snippetsCount=678 в бакете 'st'.

Второй дефект рядом: fetch_city ловил только ValueError/TypeError, транспортная ошибка сайдкара пролетала мимо цикла и убивала прогон на первом бакете — отсюда 0/6 вместо «5 бакетов из 6».

Опровергнуто три предпосылки:

  1. «Теряется на блоке QRATOR» — с 06.08 блока в прогонах нет вообще (blocked=0), отказ транспортный.
  2. «Протухшие куки Домклика» (#2704 п.5) — к свипу не относятся: свип кук не шлёт, BFF через рабочий узел отдаёт данные без сессии.
  3. «QRATOR банит все прокси кроме одного чистого residential» (миграция 173 / #2000) — ровно наоборот: мобильный пускает, residential нет.

Правка в PR #2796. Пункт 2 issue (ingest_domclick_jsonl.py) не трогал.

У владельца / для database-expert: scrape_proxies.id=1 с provider_affinity='domclick' теперь вредит — держит под Домклик узел, для Домклика мёртвый, и прячет его от остальных. Просится provider_affinity='any' отдельной миграцией. И отдельным issue: браузерная проба узла (#2723) ходит на avito.ru/robots.txt для ВСЕХ узлов — поэтому id=1 стоит с browser_fail_streak=0, будучи непригодным для Домклика; проба идёт не тем трактом, что работа.

Постановка проверена на проде сегодня (09.08) — **устарела в худшую сторону**, но не опровергнута: не «~1% предложения», а **ноль**. | дата | domclick | cian | yandex | avito | |---|---|---|---|---| | 09.08 | **0** (failed, 0/6 бакетов) | 1680 | 1952 | 120 | | 08.08 | **0** | 1680 | 2744 | 159 | | 07.08 | **0** | 1680 | 2669 | 134 | | 06.08 | **0** | 1680 | 1239 | 131 | | 05.08 | 59 | 1540 | 2643 | 300 | `listings` source='domklik': 376 активных, `max(scraped_at)=2026-08-05 05:43` — четверо суток без подтверждения. Контрольная группа отвечает: упал один Домклик, прокси/инфраструктура в целом живы. **Где теряется — «не собираем», причём не там, где искали.** Логи прогона 3502: ``` BrowserFetcher: клиент создан, proxy_lease_id=None tradein-browser: {"error": "Page.goto: NS_ERROR_PROXY_BAD_GATEWAY"} buckets=0/6 errors=1 blocked=0 ``` Домклик — единственный sweep-источник без `proxy_provider` (#2160 P4), поэтому идёт через статический `SCRAPER_PROXY_URL` = `scrape_proxies.id=1`. Живая проба тем же трактом, один URL BFF: через id=1 → 500 `NS_ERROR_PROXY_BAD_GATEWAY`, через мобильный узел пула id=10 → **200 и `snippetsCount=678`** в бакете 'st'. Второй дефект рядом: `fetch_city` ловил только `ValueError/TypeError`, транспортная ошибка сайдкара пролетала мимо цикла и убивала прогон на первом бакете — отсюда `0/6` вместо «5 бакетов из 6». **Опровергнуто три предпосылки:** 1. «Теряется на блоке QRATOR» — с 06.08 блока в прогонах нет вообще (`blocked=0`), отказ транспортный. 2. «Протухшие куки Домклика» (#2704 п.5) — к свипу не относятся: свип кук не шлёт, BFF через рабочий узел отдаёт данные без сессии. 3. «QRATOR банит все прокси кроме одного чистого residential» (миграция 173 / #2000) — ровно наоборот: мобильный пускает, residential нет. Правка в PR #2796. Пункт 2 issue (`ingest_domclick_jsonl.py`) не трогал. **У владельца / для database-expert:** `scrape_proxies.id=1` с `provider_affinity='domclick'` теперь вредит — держит под Домклик узел, для Домклика мёртвый, и прячет его от остальных. Просится `provider_affinity='any'` отдельной миграцией. И отдельным issue: браузерная проба узла (#2723) ходит на `avito.ru/robots.txt` для ВСЕХ узлов — поэтому id=1 стоит с `browser_fail_streak=0`, будучи непригодным для Домклика; проба идёт не тем трактом, что работа.
Author
Collaborator

Причина найдена и опровергает шапку: это не блокировка площадки, а мёртвый статический прокси

Постановка подтвердилась, но устарела в худшую сторону: не «~1% предложения», а ноль, четвёртые сутки.

дата domclick cian yandex avito
09.08 0 1680 1952 120
08.08 0 1680 2744 159
07.08 0 1680 2669 134
05.08 59 (число из шапки) 1540 2643 300

max(scraped_at) у источника — 05.08 05:43. Контрольная группа отвечает однозначно: упал один Домклик, инфраструктура и остальные источники живы.

Различающая проба, а не рассуждение

Один и тот же сайдкар, один и тот же URL Домклика, разные узлы:

узел результат
id=1 residential (affinity='domclick') 500 NS_ERROR_PROXY_BAD_GATEWAY
id=10 mobile (affinity='any') 200, {"offersCount":704,"snippetsCount":678}

Домклик оказался единственным свип-источником, не подключённым к пулу прокси — он шёл через статический SCRAPER_PROXY_URL, то есть через узел id=1, закреплённый за ним миграцией 173 и для него мёртвый.

Второй дефект рядом, усиливавший первый

fetch_city ловил только ValueError/TypeError (ошибки разбора), а httpx.HTTPStatusError от сайдкара летел мимо цикла по бакетам и убивал прогон на первом же — отсюда buckets 0/6. Он же не давал сработать ротации узла: до неё просто не доживали.

Опровергнутые предпосылки — их пять

  1. «Теряется на блоке QRATOR» — с 06.08 блока в прогонах нет вообще (blocked=0), отказ транспортный. Диагноз «QRATOR block or fetch errors» проставлялся по умолчанию до #2765 — та самая догадка, читающаяся дальше как факт.
  2. «Протухшие куки Домклика» (#2704 п.5) — к свипу отношения не имеют: свип кук не шлёт, а через рабочий узел данные приходят без сессии.
  3. «QRATOR банит все прокси кроме одного чистого residential» (миграция 173) — ровно наоборот: мобильный пускает, residential отбивается.
  4. «Паушальный TTL выкосил живые строки» (#2659) — на сегодня не воспроизводится: deactivated=0 с 03.08, с 07.08 ещё и skipped_unhealthy=1 (гейт #2710 работает).
  5. «Пул держится ручным ингестом» (п.2 шапки) — ручной ингест мёртв: за 14 суток ноль снапшотов без run_id.

Что сделано

PR #2796 смержен и задеплоен, проверен по коду в живом контейнере, а не по статусу: свип получает ctx.proxy_provider, бакет-уровневый перехват расширен до Exception (не BaseException — отмена проходит насквозь). Три теста, все три красные на старом коде (TypeError: unexpected keyword argument 'proxy_provider').

Критерий приёмки, записанный ДО факта: первый прогон после 10.08 ~03:50 UTC должен дать lots_fetched > 1000 и buckets_completed >= 5. Если снова ноль при proxy_lease_id != None — блок реально выше по потоку, и вопрос уходит владельцу (#2638).

Следствие вынесено отдельно — #2800

Узел id=1 имеет browser_fail_streak=0 и свежую пробу, будучи мёртвым для Домклика. Причина: браузерная проба ходит для всех узлов на Авито. Прокси-узел — не «жив/мёртв» вообще, а пара «узел × площадка», и понятие пары в системе уже есть (scrape_proxy_source_bans, #2654), но проба про него не знает.

Это продолжение #2723, не повтор: там проба шла другим транспортом, здесь — верным транспортом не туда. Оставляю эту задачу открытой до прогона 10.08.

## Причина найдена и опровергает шапку: это не блокировка площадки, а мёртвый статический прокси Постановка подтвердилась, но **устарела в худшую сторону**: не «~1% предложения», а **ноль**, четвёртые сутки. | дата | domclick | cian | yandex | avito | |---|---|---|---|---| | 09.08 | **0** | 1680 | 1952 | 120 | | 08.08 | **0** | 1680 | 2744 | 159 | | 07.08 | **0** | 1680 | 2669 | 134 | | 05.08 | 59 (число из шапки) | 1540 | 2643 | 300 | `max(scraped_at)` у источника — **05.08 05:43**. Контрольная группа отвечает однозначно: упал **один** Домклик, инфраструктура и остальные источники живы. ### Различающая проба, а не рассуждение Один и тот же сайдкар, один и тот же URL Домклика, разные узлы: | узел | результат | |---|---| | id=1 residential (`affinity='domclick'`) | **500** `NS_ERROR_PROXY_BAD_GATEWAY` | | id=10 mobile (`affinity='any'`) | **200**, `{"offersCount":704,"snippetsCount":678}` | Домклик оказался **единственным свип-источником, не подключённым к пулу прокси** — он шёл через статический `SCRAPER_PROXY_URL`, то есть через узел id=1, закреплённый за ним миграцией 173 и для него мёртвый. ### Второй дефект рядом, усиливавший первый `fetch_city` ловил только `ValueError`/`TypeError` (ошибки разбора), а `httpx.HTTPStatusError` от сайдкара летел мимо цикла по бакетам и убивал прогон **на первом же** — отсюда `buckets 0/6`. Он же не давал сработать ротации узла: до неё просто не доживали. ## Опровергнутые предпосылки — их пять 1. **«Теряется на блоке QRATOR»** — с 06.08 блока в прогонах нет вообще (`blocked=0`), отказ транспортный. Диагноз «QRATOR block or fetch errors» проставлялся **по умолчанию** до #2765 — та самая догадка, читающаяся дальше как факт. 2. **«Протухшие куки Домклика» (#2704 п.5)** — к свипу отношения не имеют: свип кук не шлёт, а через рабочий узел данные приходят без сессии. 3. **«QRATOR банит все прокси кроме одного чистого residential» (миграция 173)** — ровно наоборот: мобильный пускает, residential отбивается. 4. **«Паушальный TTL выкосил живые строки» (#2659)** — на сегодня не воспроизводится: `deactivated=0` с 03.08, с 07.08 ещё и `skipped_unhealthy=1` (гейт #2710 работает). 5. **«Пул держится ручным ингестом» (п.2 шапки)** — ручной ингест мёртв: за 14 суток ноль снапшотов без `run_id`. ## Что сделано PR **#2796** смержен и задеплоен, проверен **по коду в живом контейнере**, а не по статусу: свип получает `ctx.proxy_provider`, бакет-уровневый перехват расширен до `Exception` (не `BaseException` — отмена проходит насквозь). Три теста, **все три красные на старом коде** (`TypeError: unexpected keyword argument 'proxy_provider'`). **Критерий приёмки, записанный ДО факта:** первый прогон после **10.08 ~03:50 UTC** должен дать `lots_fetched > 1000` и `buckets_completed >= 5`. Если снова ноль при `proxy_lease_id != None` — блок реально выше по потоку, и вопрос уходит владельцу (#2638). ## Следствие вынесено отдельно — #2800 Узел id=1 имеет `browser_fail_streak=0` и свежую пробу, будучи мёртвым для Домклика. Причина: браузерная проба ходит **для всех узлов на Авито**. Прокси-узел — не «жив/мёртв» вообще, а **пара «узел × площадка»**, и понятие пары в системе уже есть (`scrape_proxy_source_bans`, #2654), но проба про него не знает. Это продолжение #2723, не повтор: там проба шла **другим транспортом**, здесь — верным транспортом **не туда**. Оставляю эту задачу открытой до прогона 10.08.
Author
Collaborator

Прогон 10.08 04:44 — критерий выполнен наполовину, и это честный результат

Критерий, записанный ДО факта: lots_fetched > 1000 и buckets_completed >= 5. Факт — прогон 3594:

{"lots_fetched": 372, "lots_updated": 188, "blocked": 1, "errors_count": 1, "geo_filtered": 8}
status: banned
до правки после
лотов за прогон 0 (четверо суток подряд) 372
обновлено объявлений 0 188
статус done при нуле (врал) banned — честно назвал, чем кончилось

Сбор пошёл. Транспортная стена, из-за которой узел отдавал отказ шлюза на каждом запросе, снята — Домклик больше не ходит через мёртвый статический прокси.

Но до 1000 не дошёл: прогон оборвался на блокировке. Одна blocked, статус banned. То есть теперь мы упираемся не в свою инфраструктуру, а в саму площадку — и это качественно другая стена.

Подтверждение, что механизм пары «узел × источник» заработал

Побочно видно, что правка #2803 не только не мешает, но и делает своё дело. Таблица банов пар сейчас:

proxy 1  | avito    | banned:avito    | до 10.08 12:52
proxy 1  | cian     | probe:browser   | до 11.08 01:44
proxy 9  | avito    | banned:avito    | до 10.08 04:18
proxy 9  | cian     | banned:cian     | до 09.08 23:47
proxy 10 | cian     | banned:cian     | до 09.08 23:48
proxy 10 | domclick | banned:domclick | до 10.08 10:49   ← новое

Строка (10, domclick) появилась сегодня: узел 10, который в моей вчерашней пробе отдавал 200 и 704 предложения, получил бан от самой площадки в ходе боевого сбора. Раньше такой строки быть не могло — Домклик не пользовался пулом вовсе.

Метки при этом раздельные и честные: banned:domclick (площадка отбила) отличается от probe:browser (наша проба не смогла), и после #2805 проба больше не может присвоить себе чужую строку.

Что дальше

Задачу оставляю открытой: «собирает 372 из ожидаемых 1000+ и упирается в бан площадки» — это прогресс, а не решение.

Новый критерий, записанный ДО факта: прогон 11.08 ~04:44 должен дать lots_fetched > 1000 при blocked = 0 — если пул успеет сменить узел до исчерпания бакетов. Если снова banned на 300-500 лотах, значит одного рабочего узла на Домклик не хватает, и вопрос уходит владельцу в #2638 (пул из четырёх узлов на четыре площадки) — тогда это не дефект кода, а нехватка ресурса, и чинить надо не здесь.

Оговорка про мигающую пару из #2800 в силе: узел может отвечать по-разному с интервалом в минуты, поэтому один прогон — не приговор ни в какую сторону.

## Прогон 10.08 04:44 — критерий выполнен наполовину, и это честный результат Критерий, записанный ДО факта: `lots_fetched > 1000` и `buckets_completed >= 5`. Факт — прогон **3594**: ``` {"lots_fetched": 372, "lots_updated": 188, "blocked": 1, "errors_count": 1, "geo_filtered": 8} status: banned ``` | | до правки | после | |---|---|---| | лотов за прогон | **0** (четверо суток подряд) | **372** | | обновлено объявлений | 0 | **188** | | статус | `done` при нуле (врал) | `banned` — честно назвал, чем кончилось | **Сбор пошёл.** Транспортная стена, из-за которой узел отдавал отказ шлюза на каждом запросе, снята — Домклик больше не ходит через мёртвый статический прокси. **Но до 1000 не дошёл: прогон оборвался на блокировке.** Одна `blocked`, статус `banned`. То есть теперь мы упираемся не в свою инфраструктуру, а в **саму площадку** — и это качественно другая стена. ## Подтверждение, что механизм пары «узел × источник» заработал Побочно видно, что правка #2803 не только не мешает, но и делает своё дело. Таблица банов пар сейчас: ``` proxy 1 | avito | banned:avito | до 10.08 12:52 proxy 1 | cian | probe:browser | до 11.08 01:44 proxy 9 | avito | banned:avito | до 10.08 04:18 proxy 9 | cian | banned:cian | до 09.08 23:47 proxy 10 | cian | banned:cian | до 09.08 23:48 proxy 10 | domclick | banned:domclick | до 10.08 10:49 ← новое ``` Строка `(10, domclick)` появилась сегодня: узел 10, который в моей вчерашней пробе отдавал 200 и 704 предложения, получил бан **от самой площадки** в ходе боевого сбора. Раньше такой строки быть не могло — Домклик не пользовался пулом вовсе. Метки при этом раздельные и честные: `banned:domclick` (площадка отбила) отличается от `probe:browser` (наша проба не смогла), и после #2805 проба больше не может присвоить себе чужую строку. ## Что дальше Задачу **оставляю открытой**: «собирает 372 из ожидаемых 1000+ и упирается в бан площадки» — это прогресс, а не решение. **Новый критерий, записанный ДО факта:** прогон 11.08 ~04:44 должен дать `lots_fetched > 1000` при `blocked = 0` — если пул успеет сменить узел до исчерпания бакетов. Если снова `banned` на 300-500 лотах, значит одного рабочего узла на Домклик не хватает, и вопрос уходит владельцу в #2638 (пул из четырёх узлов на четыре площадки) — тогда это не дефект кода, а нехватка ресурса, и чинить надо не здесь. Оговорка про мигающую пару из #2800 в силе: узел может отвечать по-разному с интервалом в минуты, поэтому один прогон — не приговор ни в какую сторону.
Author
Collaborator

Критерий 11.08 НЕ выполнен, и провал глубже прогноза — вопрос уходит владельцу в #2638

Критерий, записанный 10.08 ДО факта: прогон 11.08 ~04:44 должен дать lots_fetched > 1000 при blocked = 0.

прогон дата (UTC) lots_fetched blocked buckets статус
3594 10.08 04:44 372 1 0/6 banned
3675 11.08 05:17 58 1 0/6 banned
3751 12.08 04:28 59 1 0/6 banned

Не просто «не дотянул до 1000» — сбор упал с 372 до 58-59. Оговорка про мигающую пару (#2800) здесь уже не спасает: три прогона подряд, все banned, все на блокировке площадки, а не на транспорте.

Сработала ровно та развилка, которую критерий предусматривал: «одного рабочего узла на Домклик не хватает». Видно в таблице банов пар:

proxy  9 | domclick | banned:domclick | ban_count 2 | до 2026-08-12 16:29
proxy 10 | domclick | banned:domclick | ban_count 1 | до 2026-08-10 10:49

Оба мобильных узла получили бан от самой площадки в ходе боевого сбора (метка banned:domclick, не probe:browser — после #2805 проба чужую строку присвоить не может). Значит это не дефект кода, а нехватка ресурса — #2638 (пул из четырёх узлов на четыре площадки). Чинить надо не здесь.

Что правкой #2796 всё же куплено, и это не ноль: listings source='domklik' активных 752 против 376 на 09.08, max(scraped_at) = 2026-08-12 04:29 — подтверждение идёт ежесуточно, а не стоит четверо суток. Транспортная стена (NS_ERROR_PROXY_BAD_GATEWAY через мёртвый статический узел) снята; осталась стена площадки.

Задача остаётся открытой на пункте 1 из «что остаётся решать»: либо восстановить охват через прокси-пул, либо признать Домклик источником на ~1% рынка и перестать считать его полноценным в покрытии.

Пункт 2 (scripts/ingest_domclick_jsonl.py) — не тронут, но его срочность подтверждённо нулевая: скрипт на месте в origin/main, а снимков без run_id в listing_source_snapshots за 14 суток — 0. Ручной ингест мёртв фактически, но остаётся заряженным: следующий запуск снова создаст инвентарь, неотличимый от собранного.

## Критерий 11.08 НЕ выполнен, и провал глубже прогноза — вопрос уходит владельцу в #2638 Критерий, записанный 10.08 ДО факта: прогон 11.08 ~04:44 должен дать `lots_fetched > 1000` при `blocked = 0`. | прогон | дата (UTC) | lots_fetched | blocked | buckets | статус | |---|---|---:|---:|---|---| | 3594 | 10.08 04:44 | 372 | 1 | 0/6 | banned | | 3675 | 11.08 05:17 | **58** | 1 | 0/6 | banned | | 3751 | 12.08 04:28 | **59** | 1 | 0/6 | banned | Не просто «не дотянул до 1000» — сбор упал с 372 до 58-59. Оговорка про мигающую пару (#2800) здесь уже не спасает: три прогона подряд, все `banned`, все на блокировке площадки, а не на транспорте. **Сработала ровно та развилка, которую критерий предусматривал: «одного рабочего узла на Домклик не хватает».** Видно в таблице банов пар: ``` proxy 9 | domclick | banned:domclick | ban_count 2 | до 2026-08-12 16:29 proxy 10 | domclick | banned:domclick | ban_count 1 | до 2026-08-10 10:49 ``` Оба мобильных узла получили бан **от самой площадки** в ходе боевого сбора (метка `banned:domclick`, не `probe:browser` — после #2805 проба чужую строку присвоить не может). Значит это не дефект кода, а нехватка ресурса — **#2638** (пул из четырёх узлов на четыре площадки). Чинить надо не здесь. **Что правкой #2796 всё же куплено, и это не ноль:** `listings source='domklik'` активных **752** против 376 на 09.08, `max(scraped_at)` = 2026-08-12 04:29 — подтверждение идёт ежесуточно, а не стоит четверо суток. Транспортная стена (`NS_ERROR_PROXY_BAD_GATEWAY` через мёртвый статический узел) снята; осталась стена площадки. Задача остаётся открытой на **пункте 1** из «что остаётся решать»: либо восстановить охват через прокси-пул, либо признать Домклик источником на ~1% рынка и перестать считать его полноценным в покрытии. **Пункт 2 (`scripts/ingest_domclick_jsonl.py`) — не тронут, но его срочность подтверждённо нулевая:** скрипт на месте в `origin/main`, а снимков без `run_id` в `listing_source_snapshots` за 14 суток — **0**. Ручной ингест мёртв фактически, но остаётся заряженным: следующий запуск снова создаст инвентарь, неотличимый от собранного.
Author
Collaborator

Замер 13.08: проводка бана работает у свипа и НЕ работает у обогащения

Сначала — поправка к моему же утверждению. Я написал, что для Домклика нет ни одной записи в scrape_proxy_source_bans, при том что для Циана и Авито они есть. Это неверно: я фильтровал по banned_until > now(), то есть спрашивал «бан ещё действует», а не «бан когда-нибудь записывался». Записи есть.

узел  когда                до                   счётчик  причина
 10   2026-08-10 04:49:42  2026-08-10 10:49:42     1     banned:domclick
  9   2026-08-11 05:19:33  2026-08-12 16:29:56     2     banned:domclick

Ровно тот класс ошибки, который в этом же разборе ищут у кода: померил не то и прочитал ноль как дефект.

Что показывает исправленный замер

Забаненных прогонов Домклика — 10, записей по паре узел×источник — 2. И раскол проходит не случайно:

прогон забанено период записи по паре
domclick_city_sweep 3 10.08 04:44 → 12.08 04:28 есть
domclick_detail_backfill 7 06.08 15:17 → 12.08 17:39 нет ни одной

Совпадение по времени подтверждает авторство: бан свипа 10.08 в 04:44, запись по узлу 10 — в 04:49, через пять минут. А обогащение банится с 06.08, и до 10.08 в таблице пусто вовсе.

Тот же раскол, что и в ban_kind

За 14 суток ban_kind='unknown': 7 у обогащения, 3 у свипа. Свип починен #2832 (смержен 12.08), обогащение — нет.

Итого у обогащения две потери на одном пути: диагноз не доезжает до scrape_runs.ban_kind, и отказ не записывается в scrape_proxy_source_bans. При этом блок распознан генуинно — parse_detail_html ловит маркеры QRATOR при HTTP 200 и зовёт report_ban (providers/domclick/detail.py:553).

Цена второй потери конкретна: пока отказ не записан по паре, acquire('domclick') продолжает выдавать тот же узел. У свипа этот механизм сработал дважды и увёл узлы 10 и 9; у обогащения — ни разу за семь банов.

Чего этот замер НЕ говорит

Не установлено, отбивает ли площадка на всех узлах. Живую различающую пробу (тот же тракт, каждый из четырёх узлов, Домклик против контроля на Авито/Циане) я запускал дважды, и оба раза разбор обрывался по среде, не по существу. Поэтому вопрос «можно ли вообще восстановить сбор» остаётся открытым, и я не выдаю отсутствие проверки за проверку.

Что известно про сбор сегодня:

последний полноценный обход  18.07 — 5937 объявлений за сутки
дальше                       307 (31.07) · 313 (10.08) · 59 (12.08) · 4 (11.08)
активных                     752 из 6779 (11.1%)
узлы                         4, все с provider_affinity='any', закрепления domclick больше нет

Починка проводки сбор не воскресит — она сделает отказ различимым и перестанет отдавать заведомо отбитый узел. Это меньше, чем хотелось бы, но это то, что подтверждено замером.

## Замер 13.08: проводка бана работает у свипа и НЕ работает у обогащения Сначала — **поправка к моему же утверждению**. Я написал, что для Домклика нет ни одной записи в `scrape_proxy_source_bans`, при том что для Циана и Авито они есть. Это неверно: я фильтровал по `banned_until > now()`, то есть спрашивал «бан ещё действует», а не «бан когда-нибудь записывался». Записи есть. ``` узел когда до счётчик причина 10 2026-08-10 04:49:42 2026-08-10 10:49:42 1 banned:domclick 9 2026-08-11 05:19:33 2026-08-12 16:29:56 2 banned:domclick ``` Ровно тот класс ошибки, который в этом же разборе ищут у кода: померил не то и прочитал ноль как дефект. ## Что показывает исправленный замер Забаненных прогонов Домклика — **10**, записей по паре узел×источник — **2**. И раскол проходит не случайно: | прогон | забанено | период | записи по паре | |---|---:|---|---| | `domclick_city_sweep` | 3 | 10.08 04:44 → 12.08 04:28 | **есть** | | `domclick_detail_backfill` | **7** | 06.08 15:17 → 12.08 17:39 | **нет ни одной** | Совпадение по времени подтверждает авторство: бан свипа 10.08 в **04:44**, запись по узлу 10 — в **04:49**, через пять минут. А обогащение банится с **06.08**, и до 10.08 в таблице пусто вовсе. ## Тот же раскол, что и в `ban_kind` За 14 суток `ban_kind='unknown'`: 7 у обогащения, 3 у свипа. Свип починен #2832 (смержен 12.08), обогащение — нет. Итого у обогащения **две потери на одном пути**: диагноз не доезжает до `scrape_runs.ban_kind`, и отказ не записывается в `scrape_proxy_source_bans`. При этом блок распознан генуинно — `parse_detail_html` ловит маркеры QRATOR при HTTP 200 и зовёт `report_ban` (`providers/domclick/detail.py:553`). Цена второй потери конкретна: пока отказ не записан по паре, `acquire('domclick')` продолжает выдавать тот же узел. У свипа этот механизм сработал дважды и увёл узлы 10 и 9; у обогащения — ни разу за семь банов. ## Чего этот замер НЕ говорит **Не установлено, отбивает ли площадка на всех узлах.** Живую различающую пробу (тот же тракт, каждый из четырёх узлов, Домклик против контроля на Авито/Циане) я запускал дважды, и оба раза разбор обрывался по среде, не по существу. Поэтому вопрос «можно ли вообще восстановить сбор» остаётся открытым, и я не выдаю отсутствие проверки за проверку. Что известно про сбор сегодня: ``` последний полноценный обход 18.07 — 5937 объявлений за сутки дальше 307 (31.07) · 313 (10.08) · 59 (12.08) · 4 (11.08) активных 752 из 6779 (11.1%) узлы 4, все с provider_affinity='any', закрепления domclick больше нет ``` Починка проводки сбор не воскресит — она сделает отказ различимым и перестанет отдавать заведомо отбитый узел. Это меньше, чем хотелось бы, но это то, что подтверждено замером.
Author
Collaborator

Поправка к моему же комментарию выше — и живое подтверждение #2832

Прогон 3838 сегодня в 05:00 UTC меняет сразу две вещи, которые я сказал неверно.

1. ban_kind='unknown' у обогащения — НЕ дефект, и это написано в коде

Я назвал это «потерей диагноза». Автор задачи уже ответил, и ответ верный —
app/tasks/domclick_detail_backfill.py, строки 46-51:

«Один и тот же DomClickBlockedError поднимается и на распознанном QRATOR-маркере (площадка), и на любом сбое браузерного fetch (наш тракт)… Пока эти два случая не разведены отдельным подтипом (как AvitoSidecarUnavailableError у avito), любой диагноз отсюда был бы назначенным, а не установленным

То есть unknown здесь — правильный ответ: диагноз не теряется, он не установлен. Проставить platform означало бы ровно тот дефект, который чинил #2764. Мою формулировку «теряется дважды» снимаю.

Настоящая работа названа тем же докстрингом: развести исключение на подтипы. Различение уже существует в месте возбуждения — providers/domclick/detail.py зовёт report_ban только на генуинном маркере (строка 553) и НЕ зовёт на сбое транспорта (строки 535-548). Схлопнуто оно только в типе исключения. Это выполнимая задача, и после неё диагноз станет установленным, а не назначенным.

2. «Сбор мёртв, успешных 0» — верно по статусу, неверно по существу

Прогоны помечаются banned — правильно, они упираются в блок. Но собирают они при этом не ноль:

сутки собрано блоков статус
13.08 79 1 banned
12.08 59 1 banned
11.08 58 1 banned
10.08 372 1 banned
06-09.08 0 0 failed

Свип берёт частичный срез, потом упирается в блок и бросает остальные корзины (buckets=0/6, то есть ни одна из шести комнатных корзин не доведена). Обвал реален — 5937 за сутки 18.07 против 60-80 сейчас, — но «ноль успешных прогонов» и «ноль собранного» это разные утверждения, и я подал первое как второе.

3. Проводка бана по паре работает, и сработала сегодня

Из логов прогона 3838:

05:02:12 domklik: QRATOR block during rooms='1' — aborting all buckets
05:02:12 BrowserFetcher: lease id=11 (domclick) BANNED — reporting to pool
05:02:12 proxy_pool: proxy id=11 BANNED by source=domclick — узел снят с выдачи ТОЛЬКО для domclick

Записей по паре для Домклика сейчас три (узлы 11, 9, 10). Моё «нет ни одной» было артефактом фильтра banned_until > now().

4. #2832 подтверждён на живом прогоне

прогон дата ban_kind
3751 12.08 04:28 unknown
3838 13.08 05:00 platform

Правка смержена 12.08 вечером; первый свип после неё проставил диагноз верно. Критерий приёмки закрыт замером, а не рассуждением.


Что остаётся настоящей задачей: блок наступает на первой же комнатной корзине (rooms='1'), и все шесть остаются несобранными. Именно это отделяет 79 лотов от нескольких тысяч, а не отсутствие проводки.

## Поправка к моему же комментарию выше — и живое подтверждение #2832 Прогон **3838** сегодня в 05:00 UTC меняет сразу две вещи, которые я сказал неверно. ### 1. `ban_kind='unknown'` у обогащения — НЕ дефект, и это написано в коде Я назвал это «потерей диагноза». Автор задачи уже ответил, и ответ верный — `app/tasks/domclick_detail_backfill.py`, строки 46-51: > «Один и тот же `DomClickBlockedError` поднимается и на распознанном QRATOR-маркере (площадка), и на любом сбое браузерного fetch (наш тракт)… Пока эти два случая не разведены отдельным подтипом (как `AvitoSidecarUnavailableError` у avito), любой диагноз отсюда был бы **назначенным, а не установленным**.» То есть `unknown` здесь — правильный ответ: диагноз не теряется, он **не установлен**. Проставить `platform` означало бы ровно тот дефект, который чинил #2764. Мою формулировку «теряется дважды» снимаю. **Настоящая работа названа тем же докстрингом:** развести исключение на подтипы. Различение уже существует в месте возбуждения — `providers/domclick/detail.py` зовёт `report_ban` только на генуинном маркере (строка 553) и НЕ зовёт на сбое транспорта (строки 535-548). Схлопнуто оно только в типе исключения. Это выполнимая задача, и после неё диагноз станет установленным, а не назначенным. ### 2. «Сбор мёртв, успешных 0» — верно по статусу, неверно по существу Прогоны помечаются `banned` — правильно, они упираются в блок. Но собирают они при этом не ноль: | сутки | собрано | блоков | статус | |---|---:|---:|---| | 13.08 | **79** | 1 | banned | | 12.08 | 59 | 1 | banned | | 11.08 | 58 | 1 | banned | | 10.08 | **372** | 1 | banned | | 06-09.08 | 0 | 0 | failed | Свип берёт частичный срез, потом упирается в блок и бросает остальные корзины (`buckets=0/6`, то есть ни одна из шести комнатных корзин не доведена). Обвал реален — 5937 за сутки 18.07 против 60-80 сейчас, — но «ноль успешных прогонов» и «ноль собранного» это разные утверждения, и я подал первое как второе. ### 3. Проводка бана по паре работает, и сработала сегодня Из логов прогона 3838: ``` 05:02:12 domklik: QRATOR block during rooms='1' — aborting all buckets 05:02:12 BrowserFetcher: lease id=11 (domclick) BANNED — reporting to pool 05:02:12 proxy_pool: proxy id=11 BANNED by source=domclick — узел снят с выдачи ТОЛЬКО для domclick ``` Записей по паре для Домклика сейчас **три** (узлы 11, 9, 10). Моё «нет ни одной» было артефактом фильтра `banned_until > now()`. ### 4. #2832 подтверждён на живом прогоне | прогон | дата | `ban_kind` | |---|---|---| | 3751 | 12.08 04:28 | `unknown` | | **3838** | **13.08 05:00** | **`platform`** | Правка смержена 12.08 вечером; первый свип после неё проставил диагноз верно. Критерий приёмки закрыт замером, а не рассуждением. --- **Что остаётся настоящей задачей:** блок наступает на первой же комнатной корзине (`rooms='1'`), и все шесть остаются несобранными. Именно это отделяет 79 лотов от нескольких тысяч, а не отсутствие проводки.
Owner

Диагностика транспорта 15.08.2026: блокер — Qrator, а не бан IP и не сломанный транспорт

Проверял по просьбе владельца, начав со страницы ekaterinburg.domclick.ru/search?.... Четыре замера, два из которых опровергают напрашивающиеся объяснения.

1. Это не бан нашего IP

Запрос к той HTML-странице отдаёт одинаково с прод-сервера и с домашнего IP:

code=401  size=279
<script src="/__qrator/qauth_utm_v2d_v9118.js">

279 байт — это JS-челлендж Qrator, а не страница. Одинаковый ответ с двух разных сетей означает: блокируется любой клиент, который не исполняет JS, а не конкретный адрес. Обычный браузер челлендж проходит незаметно.

2. Сертификат: ложный след, который стоит запомнить

ДомКлик отдаёт цепочку на корневом CA Минцифры:

0 s:CN=domclick.ru, O=ООО Домклик
1 s:C=RU, O=The Ministry of Digital Development and Communications, CN=Russian Trusted Sub CA
2 s:C=RU, O=The Ministry of Digital Development and Communications, CN=Russian Trusted Root CA
Verify return code: 19 (self-signed certificate in certificate chain)

Этого корня нет ни в системном trust store наших контейнеров, ни в Chromium. Отсюда:

  • curl без -kcode=000, «self-signed certificate in certificate chain»;
  • браузер на рабочей машине → ERR_CERT_AUTHORITY_INVALID;
  • ручной вызов BrowserFetcher на этот хост → SEC_ERROR_UNKNOWN_ISSUER (в образе tradein-browser каталог /usr/local/share/ca-certificates/ пуст, russian trusted в ca-certificates.crt не встречается).

Выглядит как причина обвала — но ею не является, см. пункт 4. Практический вывод другой: любая ручная диагностика ДомКлика через curl/openssl бессмысленна без -k и будет врать про «источник недоступен».

3. Проверяли не тот хост

Боевой SERP-скрапер (scraper_kit/providers/domclick/serp.py) ходит не на человеческую страницу поиска, а на JSON-BFF https://bff-search-web.domclick.ru/api/offers/v1. У ДомКлика, в отличие от Avito, curl-пути нет вовсе — только BrowserFetcher. То есть страница из исходного вопроса к прод-сбору отношения не имеет.

4. Транспорт жив — это решающий замер

select count(*) total,
       count(*) filter (where last_seen_at > now() - interval '24 hours') seen_24h,
       max(scraped_at)
  from listings where source = 'domklik';
total 6798 | seen_24h 59 | last_scraped 2026-08-15 05:02:43

59 строк реально подтверждены сегодня утром. Значит на боевом пути TLS проходит (доверие решается на уровне camoufox и прокси, а не системного store), и «сертификат» отпадает как объяснение. Совпадает с counters прогона: lots_fetched: 59, pages_fetched: 600, затем banned с «QRATOR block aborted sweep».

Итог

Отказ ровно один и он на уровне приложения: Qrator обрывает свип после 59–79 лотов из ~600 страниц. Ни бана IP, ни поломки транспорта, ни проблемы с сертификатом на боевом пути нет.

Это не меняет развилку из тела задачи, а уточняет её: восстановление охвата упирается в обход Qrator (прокси/темп, #2638), потому что всё остальное в цепочке исправно. Альтернатива прежняя — признать, что ДомКлик даёт около 1% рынка (6798 строк против ~19 500 у Циана, все по ЕКБ), и перестать считать его полноценным источником покрытия.

Замеры делались с бюджетом в два запроса к площадке — долбить её ради диагностики не стал.

## Диагностика транспорта 15.08.2026: блокер — Qrator, а не бан IP и не сломанный транспорт Проверял по просьбе владельца, начав со страницы `ekaterinburg.domclick.ru/search?...`. Четыре замера, два из которых опровергают напрашивающиеся объяснения. ### 1. Это не бан нашего IP Запрос к той HTML-странице отдаёт одинаково с прод-сервера и с домашнего IP: ``` code=401 size=279 <script src="/__qrator/qauth_utm_v2d_v9118.js"> ``` 279 байт — это JS-челлендж Qrator, а не страница. Одинаковый ответ с двух разных сетей означает: блокируется любой клиент, который не исполняет JS, а не конкретный адрес. Обычный браузер челлендж проходит незаметно. ### 2. Сертификат: ложный след, который стоит запомнить ДомКлик отдаёт цепочку на корневом CA Минцифры: ``` 0 s:CN=domclick.ru, O=ООО Домклик 1 s:C=RU, O=The Ministry of Digital Development and Communications, CN=Russian Trusted Sub CA 2 s:C=RU, O=The Ministry of Digital Development and Communications, CN=Russian Trusted Root CA Verify return code: 19 (self-signed certificate in certificate chain) ``` Этого корня нет ни в системном trust store наших контейнеров, ни в Chromium. Отсюда: - `curl` без `-k` → `code=000`, «self-signed certificate in certificate chain»; - браузер на рабочей машине → `ERR_CERT_AUTHORITY_INVALID`; - ручной вызов `BrowserFetcher` на этот хост → `SEC_ERROR_UNKNOWN_ISSUER` (в образе `tradein-browser` каталог `/usr/local/share/ca-certificates/` пуст, `russian trusted` в `ca-certificates.crt` не встречается). Выглядит как причина обвала — но ею не является, см. пункт 4. Практический вывод другой: **любая ручная диагностика ДомКлика через curl/openssl бессмысленна без `-k`** и будет врать про «источник недоступен». ### 3. Проверяли не тот хост Боевой SERP-скрапер (`scraper_kit/providers/domclick/serp.py`) ходит не на человеческую страницу поиска, а на JSON-BFF `https://bff-search-web.domclick.ru/api/offers/v1`. У ДомКлика, в отличие от Avito, curl-пути нет вовсе — только `BrowserFetcher`. То есть страница из исходного вопроса к прод-сбору отношения не имеет. ### 4. Транспорт жив — это решающий замер ```sql select count(*) total, count(*) filter (where last_seen_at > now() - interval '24 hours') seen_24h, max(scraped_at) from listings where source = 'domklik'; ``` ``` total 6798 | seen_24h 59 | last_scraped 2026-08-15 05:02:43 ``` 59 строк реально подтверждены сегодня утром. Значит на боевом пути TLS проходит (доверие решается на уровне camoufox и прокси, а не системного store), и «сертификат» отпадает как объяснение. Совпадает с `counters` прогона: `lots_fetched: 59`, `pages_fetched: 600`, затем `banned` с «QRATOR block aborted sweep». ### Итог Отказ ровно один и он на уровне приложения: **Qrator обрывает свип после 59–79 лотов из ~600 страниц**. Ни бана IP, ни поломки транспорта, ни проблемы с сертификатом на боевом пути нет. Это не меняет развилку из тела задачи, а уточняет её: восстановление охвата упирается в обход Qrator (прокси/темп, #2638), потому что всё остальное в цепочке исправно. Альтернатива прежняя — признать, что ДомКлик даёт около 1% рынка (6798 строк против ~19 500 у Циана, все по ЕКБ), и перестать считать его полноценным источником покрытия. Замеры делались с бюджетом в два запроса к площадке — долбить её ради диагностики не стал.
lekss361 added the
business
needs-human
priority/p2
scrapers
tradein
labels 2026-08-16 10:25:12 +00:00
Owner

Ревизия открытых задач 2026-08-30. Проверено в коде на forgejo/main — сделано, закрываю.

Корень найден и устранён: свип ходил мимо прокси-пула. tests/test_2657_domclick_proxy_pool.py + правки pipeline.py и providers/domclick/serp.py.

Ревизия открытых задач 2026-08-30. Проверено в коде на forgejo/main — сделано, закрываю. Корень найден и устранён: свип ходил мимо прокси-пула. tests/test_2657_domclick_proxy_pool.py + правки pipeline.py и providers/domclick/serp.py.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
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#2657
No description provided.