Домклик: блок бьёт внутри первой корзины, ротация узла его не видит — шесть корзин не доводятся никогда #2854

Open
opened 2026-08-13 05:29:58 +00:00 by bot-backend · 6 comments
Collaborator

Решающее число: шесть комнатных корзин не доводятся НИКОГДА

сутки       корзин готово   собрано   блок   статус
2026-08-13        0            79      1     banned
2026-08-12        0            59      1     banned
2026-08-11        0            58      1     banned
2026-08-10        0           372      1     banned

buckets_completed = 0 во всех прогонах, включая тот, где собрано 372 лота. Значит блок бьёт не «после нескольких корзин», а внутри первой, до её завершения. Разброс 79 против 372 — это лишь глубина, на которую удалось уйти по страницам, прежде чем прилетел блок.

Это меняет постановку. Задача не «продолжить с оставшихся корзин», а пережить блок внутри корзины.

Механизм: ротация узла и блок живут в разных плоскостях

Ротация существует и работает: browser_fetcher._LEASE_ROTATE_AFTER_FAILS = 3 — после трёх подряд неудач lease меняется на другой узел.

Но она считает транспортные отказы (ok=False: таймаут, 5xx, соединение). А блок QRATOR приходит как успешный HTTP 200 с маркерами в теле: _extract_json распознаёт их и поднимает DomClickBlockedError (providers/domclick/serp.py:113).

Дальше исключение летит насквозь: _extract_json_paginate (строка 509) → цикл корзин (строка 353) → report_ban + break.

Итог: счётчик неудач блок не видит (fetch-то удался), поэтому ротация не наступает никогда, а прогон обрывается на первом же блоке. Два механизма защиты есть, и они не встречаются.

Устаревшее допущение

Обрыв всего прогона был верен, когда у Домклика был один выделенный резидентный прокси — так и записано в app/tasks/domclick_detail_backfill.py: «No IP-rotation/cooldown recovery step exists here (DomClick uses one dedicated residential proxy, not a rotating pool)».

Сегодня это не так: узлов четыре, у всех provider_affinity='any', закрепления domclick больше нет. После бана одного остаются три, и прогон обрывается при живом резерве.

Проводка бана при этом исправна — проверено на живом прогоне 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

Что предлагается — и чего это может стоить

Направление: на распознанном блоке не обрывать прогон, а сменить lease и продолжить страничный обход с текущего места. Пометка узла уже делается и уже корректна — не хватает только шага «взять следующий узел вместо выхода».

Риск, который надо измерить ДО реализации. Если площадка опознаёт нас не по адресу, а иначе, ротация сожжёт все четыре узла в шестичасовые баны по паре, и Домклик останется вообще без узлов. Это может оказаться хуже нынешнего.

Довод в пользу ротации: узлы 9, 10 и 11 банились в разное время, а выработка скачет (372 против 79) — то есть поведение зависит от узла, а не одинаково для всех. Довод слабый, потому что косвенный.

Как проверить дёшево: различающая проба — один и тот же запрос к Домклику через каждый из четырёх узлов, с контролем на живом источнике (Авито и Циан сегодня собирают 4792 и 1891 объявление). Если хотя бы один узел отдаёт содержимое там, где другой отбит, ротация оправдана числом. Я эту пробу трижды запускал разбором и все три раза он обрывался по среде — вопрос остаётся открытым, и я не выдаю отсутствие проверки за проверку.

Смежное, снято с подозрения

ban_kind='unknown' у domclick_detail_backfillне дефект, вопреки тому, что я написал ранее в #2657. Докстринг задачи (строки 46-51) объясняет: один и тот же DomClickBlockedError возбуждается и на маркере площадки, и на сбое транспорта, поэтому любой диагноз оттуда был бы назначенным, а не установленным. Развести исключение на подтипы — отдельная выполнимая задача; различение уже есть в месте возбуждения (detail.py: report_ban только на генуинном маркере, строка 553).

Связано: #2657, #2800, #2832 (смержен, подтверждён на прогоне 3838: ban_kind='platform' против unknown накануне).

## Решающее число: шесть комнатных корзин не доводятся НИКОГДА ``` сутки корзин готово собрано блок статус 2026-08-13 0 79 1 banned 2026-08-12 0 59 1 banned 2026-08-11 0 58 1 banned 2026-08-10 0 372 1 banned ``` `buckets_completed = 0` во **всех** прогонах, включая тот, где собрано 372 лота. Значит блок бьёт не «после нескольких корзин», а **внутри первой**, до её завершения. Разброс 79 против 372 — это лишь глубина, на которую удалось уйти по страницам, прежде чем прилетел блок. Это меняет постановку. Задача не «продолжить с оставшихся корзин», а **пережить блок внутри корзины**. ## Механизм: ротация узла и блок живут в разных плоскостях Ротация существует и работает: `browser_fetcher._LEASE_ROTATE_AFTER_FAILS = 3` — после трёх подряд неудач lease меняется на другой узел. Но она считает **транспортные** отказы (`ok=False`: таймаут, 5xx, соединение). А блок QRATOR приходит как **успешный HTTP 200** с маркерами в теле: `_extract_json` распознаёт их и поднимает `DomClickBlockedError` (`providers/domclick/serp.py:113`). Дальше исключение летит насквозь: `_extract_json` → `_paginate` (строка 509) → цикл корзин (строка 353) → `report_ban` + **`break`**. Итог: счётчик неудач блок **не видит** (fetch-то удался), поэтому ротация не наступает никогда, а прогон обрывается на первом же блоке. Два механизма защиты есть, и они не встречаются. ## Устаревшее допущение Обрыв всего прогона был верен, когда у Домклика был **один выделенный резидентный прокси** — так и записано в `app/tasks/domclick_detail_backfill.py`: «No IP-rotation/cooldown recovery step exists here (DomClick uses one dedicated residential proxy, not a rotating pool)». Сегодня это не так: узлов **четыре**, у всех `provider_affinity='any'`, закрепления `domclick` больше нет. После бана одного остаются три, и прогон обрывается при живом резерве. Проводка бана при этом исправна — проверено на живом прогоне 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 ``` ## Что предлагается — и чего это может стоить **Направление:** на распознанном блоке не обрывать прогон, а сменить lease и продолжить страничный обход с текущего места. Пометка узла уже делается и уже корректна — не хватает только шага «взять следующий узел вместо выхода». **Риск, который надо измерить ДО реализации.** Если площадка опознаёт нас не по адресу, а иначе, ротация сожжёт все четыре узла в шестичасовые баны по паре, и Домклик останется вообще без узлов. Это может оказаться хуже нынешнего. Довод в пользу ротации: узлы 9, 10 и 11 банились в разное время, а выработка скачет (372 против 79) — то есть поведение зависит от узла, а не одинаково для всех. Довод слабый, потому что косвенный. **Как проверить дёшево:** различающая проба — один и тот же запрос к Домклику через каждый из четырёх узлов, с контролем на живом источнике (Авито и Циан сегодня собирают 4792 и 1891 объявление). Если хотя бы один узел отдаёт содержимое там, где другой отбит, ротация оправдана числом. Я эту пробу трижды запускал разбором и все три раза он обрывался по среде — вопрос остаётся открытым, и я не выдаю отсутствие проверки за проверку. ## Смежное, снято с подозрения `ban_kind='unknown'` у `domclick_detail_backfill` — **не дефект**, вопреки тому, что я написал ранее в #2657. Докстринг задачи (строки 46-51) объясняет: один и тот же `DomClickBlockedError` возбуждается и на маркере площадки, и на сбое транспорта, поэтому любой диагноз оттуда был бы назначенным, а не установленным. Развести исключение на подтипы — отдельная выполнимая задача; различение уже есть в месте возбуждения (`detail.py`: `report_ban` только на генуинном маркере, строка 553). Связано: #2657, #2800, #2832 (смержен, подтверждён на прогоне 3838: `ban_kind='platform'` против `unknown` накануне).
Author
Collaborator

Контрдовод к собственному предложению — и почему он не устоял

Прежде чем предлагать ротацию, стоило проверить обратное: три последних блока пришлись на три РАЗНЫХ узла (10 — 10.08, 9 — 11.08, 11 — 13.08). Если площадка опознаёт нас независимо от узла, ротация лишь сожжёт пул в шестичасовые баны и сделает хуже.

Различил две гипотезы длительностью прогона:

сутки лотов секунд до обрыва узел
13.08 79 134 11
12.08 59 111
11.08 58 140 9
10.08 372 332 10
06-09.08 0 31-49 — (иной отказ, failed)

Блок наступает не мгновенно. Свип работает от двух до пяти с половиной минут и всё это время собирает. Мгновенное опознание узла выглядело бы как прогоны 06-09.08: обрыв за 31-49 секунд с нулём лотов — и это, судя по статусу failed и нулю блоков, вообще другой отказ.

Разброс 79 → 372 при работе 134 → 332 секунды даёт примерно постоянную выработку (0.4-1.1 лота в секунду), то есть отбивают по накопленному объёму или времени, а не по узлу при первом обращении.

Следствие: свежий узел, вероятно, получает свой бюджет. При трёх оставшихся узлах ожидаемая выработка вырастает примерно вчетверо — с нынешних ~79 до ~300 лотов за прогон.

Чего этот вывод НЕ доказывает

Это вывод по длительности, а не измерение спускового крючка. Возможны другие объяснения: разная населённость первой корзины в разные сутки, разная скорость узла, разное поведение площадки по времени суток (все прогоны идут около 05:00 UTC, кроме 12.08 в 04:28).

Решающей остаётся живая различающая проба: один запрос к Домклику через каждый из четырёх узлов, с контролем на Авито и Циане. Её я довести не смог — три захода оборвались по среде.

Порядок работ, который следует из этого: сначала проба, потом ротация. Реализовывать ротацию на выводе из длительности — значит строить на догадке, пусть и правдоподобной. Триста лотов вместо восьмидесяти — это по-прежнему 5% от того, что собиралось 18.07 (5937 за сутки), так что цена ошибки в постановке выше, чем выигрыш от спешки.

## Контрдовод к собственному предложению — и почему он не устоял Прежде чем предлагать ротацию, стоило проверить обратное: **три последних блока пришлись на три РАЗНЫХ узла** (10 — 10.08, 9 — 11.08, 11 — 13.08). Если площадка опознаёт нас независимо от узла, ротация лишь сожжёт пул в шестичасовые баны и сделает хуже. Различил две гипотезы длительностью прогона: | сутки | лотов | секунд до обрыва | узел | |---|---:|---:|---| | 13.08 | 79 | **134** | 11 | | 12.08 | 59 | **111** | — | | 11.08 | 58 | **140** | 9 | | **10.08** | **372** | **332** | 10 | | 06-09.08 | 0 | 31-49 | — (иной отказ, `failed`) | **Блок наступает не мгновенно.** Свип работает от двух до пяти с половиной минут и всё это время собирает. Мгновенное опознание узла выглядело бы как прогоны 06-09.08: обрыв за 31-49 секунд с нулём лотов — и это, судя по статусу `failed` и нулю блоков, вообще другой отказ. Разброс 79 → 372 при работе 134 → 332 секунды даёт примерно постоянную выработку (0.4-1.1 лота в секунду), то есть **отбивают по накопленному объёму или времени, а не по узлу при первом обращении**. **Следствие:** свежий узел, вероятно, получает свой бюджет. При трёх оставшихся узлах ожидаемая выработка вырастает примерно вчетверо — с нынешних ~79 до ~300 лотов за прогон. ## Чего этот вывод НЕ доказывает Это **вывод по длительности, а не измерение спускового крючка**. Возможны другие объяснения: разная населённость первой корзины в разные сутки, разная скорость узла, разное поведение площадки по времени суток (все прогоны идут около 05:00 UTC, кроме 12.08 в 04:28). Решающей остаётся живая различающая проба: один запрос к Домклику через каждый из четырёх узлов, с контролем на Авито и Циане. Её я довести не смог — три захода оборвались по среде. **Порядок работ, который следует из этого:** сначала проба, потом ротация. Реализовывать ротацию на выводе из длительности — значит строить на догадке, пусть и правдоподобной. Триста лотов вместо восьмидесяти — это по-прежнему 5% от того, что собиралось 18.07 (5937 за сутки), так что цена ошибки в постановке выше, чем выигрыш от спешки.
Author
Collaborator

Живая проба наконец сделана — и она НЕ подтверждает премиссу правки

Вызвал починенную probe_proxy_via_browser напрямую из контейнера tradein-scraper (функция не берёт аренду и бана не пишет, поэтому боевому сбору не вредит). Каждый узел, две площадки — Домклик и Авито как контроль.

узел | источник   | ok    | подробность
-----+------------+-------+-------------
   1 | domclick   | True  | html_len=174
   1 | avito      | True  | html_len=16324
   9 | domclick   | True  | html_len=174
   9 | avito      | True  | html_len=16324
  10 | domclick   | True  | html_len=174
  10 | avito      | True  | html_len=16324
  11 | domclick   | True  | html_len=174
  11 | avito      | True  | html_len=16324

Все четыре узла сейчас получают живой ответ count-эндпоинта — включая узел 11, который в 05:02 получил QRATOR-блок на свипе и до 11:02 числится забаненным.

Что из этого следует, и чего НЕ следует

Не следует, что правка неверна. Блок в 05:02 к 06:5x уже снят: за два часа площадка перестала отбивать этот узел. Проба честно отвечает «сейчас пускают», и это правда.

Следует, что подтвердить правку сейчас нельзя. Её ценность видна только в окне, когда блок реально действует, а окно оказалось коротким. Одна проба каждые полчаса может в него не попасть вовсе.

И следует кое-что важнее самой правки: блок Домклика временный, а не постоянный. Через два часа после блока все четыре узла ходят на боевой API. Это меняет картину в #2854: там я рассуждал про ротацию узла внутри прогона, опираясь на то, что блок наступает по объёму. Теперь видно и вторую половину — он и снимается сам.

Что это даёт задаче

Если блок временный и снимается за ~2 часа, то к нынешней схеме (один прогон в сутки, обрыв на первой корзине, 79 лотов) есть более дешёвый подход, чем ротация узлов: дробить прогон по времени. Шесть комнатных корзин — шесть заходов с промежутком, а не один заход, обрывающийся на первой.

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

Честный статус правки #2856

Она не ухудшает ничего: проба спрашивает более близкий к работе путь, ответ 174 байта против 16 КБ у robots.txt Авито, то есть нагрузка на площадку даже меньше прежней. Но заявленную пользу — «увидит блок, который robots.txt скрывает» — я подтвердить не смог, и записываю это как есть.

Критерий приёмки остаётся прежним и ждёт своего окна: бан пары узел×domclick с причиной probe:browser в сутки, когда свип был заблокирован. Если за неделю такого не случится ни разу при ежедневных блоках свипа — значит проба в окно не попадает, и правка бесполезна независимо от того, верна ли её премисса.

## Живая проба наконец сделана — и она НЕ подтверждает премиссу правки Вызвал починенную `probe_proxy_via_browser` напрямую из контейнера `tradein-scraper` (функция не берёт аренду и бана не пишет, поэтому боевому сбору не вредит). Каждый узел, две площадки — Домклик и Авито как контроль. ``` узел | источник | ok | подробность -----+------------+-------+------------- 1 | domclick | True | html_len=174 1 | avito | True | html_len=16324 9 | domclick | True | html_len=174 9 | avito | True | html_len=16324 10 | domclick | True | html_len=174 10 | avito | True | html_len=16324 11 | domclick | True | html_len=174 11 | avito | True | html_len=16324 ``` **Все четыре узла сейчас получают живой ответ count-эндпоинта** — включая узел 11, который в 05:02 получил QRATOR-блок на свипе и до 11:02 числится забаненным. ## Что из этого следует, и чего НЕ следует **Не следует, что правка неверна.** Блок в 05:02 к 06:5x уже снят: за два часа площадка перестала отбивать этот узел. Проба честно отвечает «сейчас пускают», и это правда. **Следует, что подтвердить правку сейчас нельзя.** Её ценность видна только в окне, когда блок реально действует, а окно оказалось коротким. Одна проба каждые полчаса может в него не попасть вовсе. **И следует кое-что важнее самой правки:** блок Домклика **временный, а не постоянный**. Через два часа после блока все четыре узла ходят на боевой API. Это меняет картину в #2854: там я рассуждал про ротацию узла внутри прогона, опираясь на то, что блок наступает по объёму. Теперь видно и вторую половину — он и снимается сам. ## Что это даёт задаче Если блок временный и снимается за ~2 часа, то к нынешней схеме (один прогон в сутки, обрыв на первой корзине, 79 лотов) есть более дешёвый подход, чем ротация узлов: **дробить прогон по времени**. Шесть комнатных корзин — шесть заходов с промежутком, а не один заход, обрывающийся на первой. Это гипотеза, а не вывод: я померил снятие блока **один раз** и не знаю ни его типичной длительности, ни от чего она зависит. Прежде чем что-то менять, нужен ряд замеров — например, пробовать count-эндпоинт каждые 15 минут после ближайшего блока и записать, когда он снимется. ## Честный статус правки #2856 Она **не ухудшает** ничего: проба спрашивает более близкий к работе путь, ответ 174 байта против 16 КБ у robots.txt Авито, то есть нагрузка на площадку даже меньше прежней. Но заявленную пользу — «увидит блок, который robots.txt скрывает» — я подтвердить **не смог**, и записываю это как есть. Критерий приёмки остаётся прежним и ждёт своего окна: бан пары `узел×domclick` с причиной `probe:browser` в сутки, когда свип был заблокирован. Если за неделю такого не случится ни разу при ежедневных блоках свипа — значит проба в окно не попадает, и правка бесполезна независимо от того, верна ли её премисса.
lekss361 added the
bug
priority/p1
scope/backend
scrapers
tradein
labels 2026-08-16 10:25:25 +00:00
Author
Collaborator

Цена этой задачи видна в самих данных: Домклик слеп к 2/3 рынка

buckets_completed = 0 — это не абстракция. Активные объявления по комнатности (19.08):

комнат домклик циан яндекс
студии 575 3 2 451
1 408 6 461 4 769
2 1 7 352 4 895
3 0 4 581 3 175
4 0 873 780

Одна двушка и ноль трёшек. У соседних площадок 2+ комнат — это ~66% активных объявлений. То есть Домклик как источник структурно не покрывает две трети рынка, и никакое увеличение глубины первой корзины этого не изменит.

Это ровно предсказание из тела задачи («шесть корзин не доводятся никогда»), подтверждённое с другой стороны — не по счётчикам прогона, а по составу накопленных данных.

Ограничение, которое надо учесть при реализации: ротировать почти некуда

узлов в пуле          4
из них включено       3
забанено Домкликом    2   (до 21.08 04:22 и 21.08 05:15)

Свободен один узел. Ротация lease после блока — верное направление, но сегодня она упирается в пул: сожжёт оставшийся узел и упрётся в NoProxyAvailableError. Расширение пула — не кодовый вопрос (см. #2704).

Отсюда предложение по порядку работ: сначала смещение стартовой корзины, потом ротация lease.

Если каждый прогон начинает обход не с rooms='1', а со сдвигом по номеру суток, то даже при жёстком «одна корзина за прогон» все шесть корзин собираются за шесть суток. Это работает при ОДНОМ свободном узле, то есть в сегодняшних условиях, и не зависит от расширения пула. Ротация lease поверх этого добавляет глубины, когда узлы есть.

Что проверено и не является риском

Опасение, что более редкий сбор приведёт к ложным деактивациям, данные снимают: сторож здоровья источника работает и это видно в прогонах deactivate_stale_domklik.

15.08  confirmations=94   skipped_unhealthy=1   <- деактивация пропущена
16-19.08  confirmations≈455-469  deactivated=0  ttl_days_effective=28

То есть в сутки, когда сбор провалился, деактивация сама себя останавливает.

Приёмка

Критерий, проверяемый по данным, а не по логам: через 7 суток после правки в listings должны появиться домкликовские объявления с rooms >= 2 — сейчас их 1 на весь источник. Счётчик buckets_completed > 0 — необходимое, но не достаточное условие.

### Цена этой задачи видна в самих данных: Домклик слеп к 2/3 рынка `buckets_completed = 0` — это не абстракция. Активные объявления по комнатности (19.08): | комнат | домклик | циан | яндекс | |---:|---:|---:|---:| | студии | 575 | 3 | 2 451 | | 1 | 408 | 6 461 | 4 769 | | **2** | **1** | 7 352 | 4 895 | | **3** | **0** | 4 581 | 3 175 | | **4** | **0** | 873 | 780 | Одна двушка и ноль трёшек. У соседних площадок 2+ комнат — это ~66% активных объявлений. То есть Домклик как источник структурно не покрывает две трети рынка, и никакое увеличение глубины первой корзины этого не изменит. Это ровно предсказание из тела задачи («шесть корзин не доводятся никогда»), подтверждённое с другой стороны — не по счётчикам прогона, а по составу накопленных данных. ### Ограничение, которое надо учесть при реализации: ротировать почти некуда ``` узлов в пуле 4 из них включено 3 забанено Домкликом 2 (до 21.08 04:22 и 21.08 05:15) ``` Свободен **один** узел. Ротация lease после блока — верное направление, но сегодня она упирается в пул: сожжёт оставшийся узел и упрётся в `NoProxyAvailableError`. Расширение пула — не кодовый вопрос (см. #2704). Отсюда предложение по порядку работ: **сначала смещение стартовой корзины**, потом ротация lease. Если каждый прогон начинает обход не с `rooms='1'`, а со сдвигом по номеру суток, то даже при жёстком «одна корзина за прогон» все шесть корзин собираются за шесть суток. Это работает при ОДНОМ свободном узле, то есть в сегодняшних условиях, и не зависит от расширения пула. Ротация lease поверх этого добавляет глубины, когда узлы есть. ### Что проверено и не является риском Опасение, что более редкий сбор приведёт к ложным деактивациям, данные снимают: сторож здоровья источника работает и это видно в прогонах `deactivate_stale_domklik`. ``` 15.08 confirmations=94 skipped_unhealthy=1 <- деактивация пропущена 16-19.08 confirmations≈455-469 deactivated=0 ttl_days_effective=28 ``` То есть в сутки, когда сбор провалился, деактивация сама себя останавливает. ### Приёмка Критерий, проверяемый по данным, а не по логам: через 7 суток после правки в `listings` должны появиться домкликовские объявления с `rooms >= 2` — сейчас их 1 на весь источник. Счётчик `buckets_completed > 0` — необходимое, но не достаточное условие.
Author
Collaborator

На проде (PR #2932 смержен и задеплоен)

Проверял не «смержено», а что в работающем контейнере другой код:

tradein-scraper  providers/domclick/serp.py:306   start_bucket_index: int = 0
                                          :354   offset = start_bucket_index % len(ROOM_BUCKETS)
                 orchestration/pipeline.py:4138   start_bucket_index=run_id % len(ROOM_BUCKETS)

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

Чего проверка НЕ доказывает

Ни одного прогона со сдвигом ещё не было: последний свип Домклика — 19.08 04:17, следующий по расписанию около 04:00 20.08. То есть сегодня измерять нечего, и «задеплоено» ≠ «сработало».

Критерий приёмки, со сроком

К 26.08 (семь суток, семь прогонов при шести корзинах):

-- сейчас: 1 на весь источник
SELECT count(*) FROM listings WHERE source='domklik' AND is_active AND rooms >= 2;

-- сейчас: одинаковый у всех прогонов
SELECT bucket_start_index, count(*)
  FROM scrape_runs, jsonb_to_record(counters) AS x(bucket_start_index int)
 WHERE source='domclick_city_sweep' AND started_at > now() - interval '7 days'
 GROUP BY 1 ORDER BY 1;

Ожидание: во втором запросе появятся разные значения сдвига, в первом — счётчик станет больше 1.

Если сдвиг разный, а двушек по-прежнему нет — значит блок бьёт раньше, чем успевает собраться хоть один лот сдвинутой корзины, и правка не помогла. Это тоже результат, и он будет виден по тем же двум запросам, а не по ощущениям.

### На проде (PR #2932 смержен и задеплоен) Проверял не «смержено», а что в работающем контейнере другой код: ``` tradein-scraper providers/domclick/serp.py:306 start_bucket_index: int = 0 :354 offset = start_bucket_index % len(ROOM_BUCKETS) orchestration/pipeline.py:4138 start_bucket_index=run_id % len(ROOM_BUCKETS) ``` Проводка на месте — это важнее самой возможности: аргумент можно добавить в функцию и забыть передать, и все тесты скрейпера останутся зелёными. Поэтому на неё написан отдельный тест, фальсифицированный снятием передачи. ### Чего проверка НЕ доказывает Ни одного прогона со сдвигом ещё не было: последний свип Домклика — 19.08 04:17, следующий по расписанию около 04:00 20.08. То есть сегодня измерять нечего, и «задеплоено» ≠ «сработало». ### Критерий приёмки, со сроком **К 26.08** (семь суток, семь прогонов при шести корзинах): ```sql -- сейчас: 1 на весь источник SELECT count(*) FROM listings WHERE source='domklik' AND is_active AND rooms >= 2; -- сейчас: одинаковый у всех прогонов SELECT bucket_start_index, count(*) FROM scrape_runs, jsonb_to_record(counters) AS x(bucket_start_index int) WHERE source='domclick_city_sweep' AND started_at > now() - interval '7 days' GROUP BY 1 ORDER BY 1; ``` Ожидание: во втором запросе появятся разные значения сдвига, в первом — счётчик станет больше 1. Если сдвиг разный, а двушек по-прежнему нет — значит блок бьёт раньше, чем успевает собраться хоть один лот сдвинутой корзины, и правка не помогла. Это тоже результат, и он будет виден по тем же двум запросам, а не по ощущениям.
Author
Collaborator

Первый свип со сдвигом прошёл — критерий выполнен за сутки вместо семи

20.08 03:24  banned  собрано=423  сдвиг=5  корзин пройдено=1
19.08 04:17  banned  собрано=389  сдвиг=—  корзин пройдено=0
18.08 05:13  banned  собрано= 59  сдвиг=—  корзин пройдено=0

buckets_completed = 1 — впервые за всю историю прогонов корзина доведена до конца. Во всех предыдущих было ровно 0, и именно это число было решающим доводом задачи.

Сдвиг 5 → обход начался с корзины 5+. Она маленькая, поэтому успела завершиться до блока.

Что это дало в данных

Вчерашний замер (19.08) по активным объявлениям Домклика:

комнат:  4 → 0     5 → 0     6 → 0

Сегодня, через шесть часов после прогона:

комнат:  5 → 26 объявлений   (все 26 видели за последние 12 ч)
         6 →  3 объявления   (все 3 за 12 ч)

Итог по критерию приёмки из моего же комментария выше:

объявлений Домклика с rooms >= 2:   было 1  →  стало 34
из них собрано за последние 12 ч:   33

Тридцать три записи, которых не существовало до правки. Это не перераспределение уже собранного: категории 5 и 6 комнат были пусты полностью.

Чего это НЕ доказывает

Двушек и трёшек по-прежнему 1 и 0 — до них очередь дойдёт при сдвигах 2 и 3. Прогон всё так же уходит в banned: блок никуда не делся, правка его и не лечила. Она раздаёт то, что успевает собраться, по всем корзинам — и первый же прогон это подтвердил.

Второй критерий (разные значения bucket_start_index в прогонах за неделю) проверю к 26.08, как и записано: одна точка ещё не цикл.

## Первый свип со сдвигом прошёл — критерий выполнен за сутки вместо семи ``` 20.08 03:24 banned собрано=423 сдвиг=5 корзин пройдено=1 19.08 04:17 banned собрано=389 сдвиг=— корзин пройдено=0 18.08 05:13 banned собрано= 59 сдвиг=— корзин пройдено=0 ``` **`buckets_completed = 1`** — впервые за всю историю прогонов корзина доведена до конца. Во всех предыдущих было ровно 0, и именно это число было решающим доводом задачи. Сдвиг 5 → обход начался с корзины `5+`. Она маленькая, поэтому успела завершиться до блока. ## Что это дало в данных Вчерашний замер (19.08) по активным объявлениям Домклика: ``` комнат: 4 → 0 5 → 0 6 → 0 ``` Сегодня, через шесть часов после прогона: ``` комнат: 5 → 26 объявлений (все 26 видели за последние 12 ч) 6 → 3 объявления (все 3 за 12 ч) ``` Итог по критерию приёмки из моего же комментария выше: ``` объявлений Домклика с rooms >= 2: было 1 → стало 34 из них собрано за последние 12 ч: 33 ``` Тридцать три записи, которых не существовало до правки. Это не перераспределение уже собранного: категории 5 и 6 комнат были пусты полностью. ## Чего это НЕ доказывает Двушек и трёшек по-прежнему 1 и 0 — до них очередь дойдёт при сдвигах 2 и 3. Прогон всё так же уходит в `banned`: блок никуда не делся, правка его и не лечила. Она раздаёт то, что успевает собраться, по всем корзинам — и первый же прогон это подтвердил. Второй критерий (разные значения `bucket_start_index` в прогонах за неделю) проверю к 26.08, как и записано: одна точка ещё не цикл.
Author
Collaborator

Промежуточный замер 20.08 — сдвиг работает, бан остаётся

Первый прогон со сдвигом

запуск            статус  сдвиг  корзин_завершено/всего  увидено  новых
2026-08-20 03:24  banned    5           1 / 6              423      20
2026-08-19 04:17  banned    —           0 / 6              389      21
2026-08-18 05:13  banned    —           0 / 6               59       1
… и так все 11 прогонов с 09.08: сдвиг пуст, завершённых корзин ноль

buckets_completed=1 — первое ненулевое значение за всю историю источника.

Состав собранного — то, ради чего затевалось

Раньше блок бил внутри первой корзины, и выборка вырождалась. Проверил по listings:

сутки   всего  1-комн  2-комн  3+  студии/NULL
08-10    294     292      1     0        1      ← слепая зона
08-11      4       4      0     0        0
08-18     59      59      0     0        0
08-20    423       3      0    33      387      ← со сдвигом

10.08: 292 однушки, одна двушка, ноль трёшек. 20.08: 33 трёхкомнатных, чего в этом источнике не появлялось ни разу за две недели.

То есть сдвиг делает ровно то, что должен: обход перестал упираться в одну и ту же точку обрыва.

Чего сдвиг НЕ починил, и это надо сказать прямо

Прогон по-прежнему заканчивается статусом banned, и последний done был 05.08 — пятнадцать суток назад:

domclick_city_sweep:   banned 11 (посл. 20.08)  ·  done 5 (посл. 05.08)  ·  failed 5
domclick_detail_backfill: banned 14 (посл. 19.08) · done 3 (посл. 05.08)

Сторож просроченных источников (#2670) это видит и сообщает верно: «domclick_city_sweep 15.1d/1d». Проверил его формулу — он считает последний прогон со status='done', а не факт записи строк, и потому не обманывается ручейком частичного сбора.

Отдельно стоит отметить: по таблице listings Домклик выглядит свежим (последнее объявление 8 часов назад). Свежесть таблицы маскирует то, что полного обхода нет уже две недели — ровно тот случай, когда «данные приходят» и «сбор работает» это разные утверждения.

Второй критерий приёмки — ещё не наступил

Критерий «сдвиг принимает РАЗНЫЕ значения в разных прогонах» одной точкой не проверяется: сегодня он равен 5, и это первое значение вообще. Проверять 26.08, как и записано.

## Промежуточный замер 20.08 — сдвиг работает, бан остаётся ### Первый прогон со сдвигом ``` запуск статус сдвиг корзин_завершено/всего увидено новых 2026-08-20 03:24 banned 5 1 / 6 423 20 2026-08-19 04:17 banned — 0 / 6 389 21 2026-08-18 05:13 banned — 0 / 6 59 1 … и так все 11 прогонов с 09.08: сдвиг пуст, завершённых корзин ноль ``` `buckets_completed=1` — первое ненулевое значение за всю историю источника. ### Состав собранного — то, ради чего затевалось Раньше блок бил внутри первой корзины, и выборка вырождалась. Проверил по `listings`: ``` сутки всего 1-комн 2-комн 3+ студии/NULL 08-10 294 292 1 0 1 ← слепая зона 08-11 4 4 0 0 0 08-18 59 59 0 0 0 08-20 423 3 0 33 387 ← со сдвигом ``` 10.08: **292 однушки, одна двушка, ноль трёшек**. 20.08: **33 трёхкомнатных**, чего в этом источнике не появлялось ни разу за две недели. То есть сдвиг делает ровно то, что должен: обход перестал упираться в одну и ту же точку обрыва. ### Чего сдвиг НЕ починил, и это надо сказать прямо Прогон по-прежнему заканчивается статусом `banned`, и последний `done` был **05.08** — пятнадцать суток назад: ``` domclick_city_sweep: banned 11 (посл. 20.08) · done 5 (посл. 05.08) · failed 5 domclick_detail_backfill: banned 14 (посл. 19.08) · done 3 (посл. 05.08) ``` Сторож просроченных источников (#2670) это видит и сообщает верно: «domclick_city_sweep 15.1d/1d». Проверил его формулу — он считает последний прогон со `status='done'`, а не факт записи строк, и потому не обманывается ручейком частичного сбора. Отдельно стоит отметить: по таблице `listings` Домклик выглядит свежим (последнее объявление 8 часов назад). Свежесть таблицы маскирует то, что полного обхода нет уже две недели — ровно тот случай, когда «данные приходят» и «сбор работает» это разные утверждения. ### Второй критерий приёмки — ещё не наступил Критерий «сдвиг принимает РАЗНЫЕ значения в разных прогонах» одной точкой не проверяется: сегодня он равен 5, и это первое значение вообще. Проверять 26.08, как и записано.
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#2854
No description provided.