Яндекс: недорендеренная карточка (1,8 МБ вместо 3,9) принимается за успешную — проверка размера отвечает не на тот вопрос #3191

Open
opened 2026-08-28 18:35:15 +00:00 by bot-backend · 4 comments
Collaborator

Найдено при проверке гипотезы «дадут ли авторизованные куки Яндекса больше данных» (28.08). Сама гипотеза не подтвердилась (разбор ниже), а вот это — реальный дефект.

Что наблюдалось

Три карточки realty.yandex.ru, каждая забрана через прод-сайдкар анонимно:

offer 52275656   3 920 119 б   90 телефонов   encryptedPhones 65
offer 62983459   1 805 556 б    0 телефонов   encryptedPhones  0     <-- недогруз
offer 12108408   4 732 884 б   89 телефонов   encryptedPhones 65

Средняя карточка вернулась вдвое меньше нормы и без блока контактов вообще. Не заглушка антибота, не ошибка — просто недорендеренная страница. Статус 200, HTML валиден.

Та же карточка из другого захода отдавалась целиком, так что это не свойство объявления.

Почему это проходит незамеченным

Единственная проверка размера в провайдере — providers/yandex/newbuilding.py:194:

if not html or len(html) < 500:

Порог 500 байт отвечает на вопрос «пришло ли хоть что-то». Страница на 1,8 МБ проходит его в 3600 раз. То есть карточка без единого контакта считается успешно собранной.

providers/yandex/detail.py размер не проверяет вовсе: там брейс-балансер вытаскивает JSON-состояние, и если блок оборван — вернётся None (парс-фейл, это заметно). Но если состояние отрендерилось, а секция контактов нет — распарсится частичная карточка, и она уедет в БД как полноценная.

Чем выстрелит

Тихо. Объявление сохраняется без телефонов, detail_enriched_at проставляется, повторно карточка не берётся. Потери не видно ни в статусе прогона, ни в счётчиках — только в том, что у части объявлений нет контактов, и списать это можно на что угодно.

Что сделать

  1. Порог полноты, а не наличия. Для detail-страницы Яндекса нормальный размер 3-5 МБ; всё заметно ниже (скажем, меньше 1 МБ) — это недогруз, а не карточка. Порог в настройку.
  2. Лучше размера — структурный признак. Проверять, что в разметке есть ожидаемые ключи (encryptedPhones / блок контактов). Размер зависит от объявления, наличие секции — нет.
  3. Недогруз должен быть отказом, а не успехом: не проставлять detail_enriched_at, вернуть карточку в очередь. Сейчас она выбывает навсегда.
  4. Посчитать по БД, у какой доли уже собранных объявлений Яндекса нет контактов — это оценка накопленного ущерба.

Приёмка

  • Недогруженная страница классифицируется как отказ, карточка остаётся в очереди
  • Признак полноты структурный, не только размерный
  • Известна доля уже сохранённых объявлений без контактов
  • Тест: страница 1,8 МБ без блока контактов не считается успехом

Оценка S/M.


Побочно: авторизация Яндекса ничего не даёт

Проверено на тех же трёх карточках, залогиненная сессия против анонимной:

залогинен аноним
размер 3,9-4,9 МБ 3,9-4,7 МБ (одна недогрузилась)
уникальных телефонов 91 / 92 / 100 90 / — / 89
encryptedPhones 65 / 58 / 65 65 / — / 65
mainPhone есть нет

Телефоны доступны и без авторизации. Единственное устойчивое различие — ключ mainPhone, он один.

Плюс у Яндекса нет проблемы с блоками: за 7 суток ни одного прогона banned, свипы done (352, 213, 554 объявления), yandex_detail_backfill — 5 done из 7.

Вывод: хранить живую сессию Яндекса (с её протуханием, ротацией и риском для учётки) не за что. Показательно, что настройка YANDEX_COOKIES_FILE объявлена в config.py:1065, но не читается никаким кодом и указывает на несуществующий файл — идею уже пробовали и не довели, и ничего от этого не сломалось.

Найдено при проверке гипотезы «дадут ли авторизованные куки Яндекса больше данных» (28.08). Сама гипотеза не подтвердилась (разбор ниже), а вот это — реальный дефект. ## Что наблюдалось Три карточки `realty.yandex.ru`, каждая забрана через прод-сайдкар анонимно: ``` offer 52275656 3 920 119 б 90 телефонов encryptedPhones 65 offer 62983459 1 805 556 б 0 телефонов encryptedPhones 0 <-- недогруз offer 12108408 4 732 884 б 89 телефонов encryptedPhones 65 ``` Средняя карточка вернулась **вдвое меньше нормы и без блока контактов вообще**. Не заглушка антибота, не ошибка — просто недорендеренная страница. Статус 200, HTML валиден. Та же карточка из другого захода отдавалась целиком, так что это не свойство объявления. ## Почему это проходит незамеченным Единственная проверка размера в провайдере — `providers/yandex/newbuilding.py:194`: ```python if not html or len(html) < 500: ``` Порог 500 байт отвечает на вопрос «пришло ли хоть что-то». Страница на 1,8 МБ проходит его в 3600 раз. То есть карточка без единого контакта считается успешно собранной. `providers/yandex/detail.py` размер не проверяет вовсе: там брейс-балансер вытаскивает JSON-состояние, и если блок оборван — вернётся `None` (парс-фейл, это заметно). Но если состояние отрендерилось, а секция контактов нет — распарсится частичная карточка, и она уедет в БД как полноценная. ## Чем выстрелит Тихо. Объявление сохраняется без телефонов, `detail_enriched_at` проставляется, повторно карточка не берётся. Потери не видно ни в статусе прогона, ни в счётчиках — только в том, что у части объявлений нет контактов, и списать это можно на что угодно. ## Что сделать 1. **Порог полноты, а не наличия.** Для detail-страницы Яндекса нормальный размер 3-5 МБ; всё заметно ниже (скажем, меньше 1 МБ) — это недогруз, а не карточка. Порог в настройку. 2. **Лучше размера — структурный признак.** Проверять, что в разметке есть ожидаемые ключи (`encryptedPhones` / блок контактов). Размер зависит от объявления, наличие секции — нет. 3. **Недогруз должен быть отказом, а не успехом**: не проставлять `detail_enriched_at`, вернуть карточку в очередь. Сейчас она выбывает навсегда. 4. Посчитать по БД, у какой доли уже собранных объявлений Яндекса нет контактов — это оценка накопленного ущерба. ## Приёмка - [ ] Недогруженная страница классифицируется как отказ, карточка остаётся в очереди - [ ] Признак полноты структурный, не только размерный - [ ] Известна доля уже сохранённых объявлений без контактов - [ ] Тест: страница 1,8 МБ без блока контактов не считается успехом Оценка S/M. --- ## Побочно: авторизация Яндекса ничего не даёт Проверено на тех же трёх карточках, залогиненная сессия против анонимной: | | залогинен | аноним | |---|---|---| | размер | 3,9-4,9 МБ | 3,9-4,7 МБ (одна недогрузилась) | | уникальных телефонов | 91 / 92 / 100 | 90 / — / 89 | | `encryptedPhones` | 65 / 58 / 65 | 65 / — / 65 | | `mainPhone` | есть | **нет** | Телефоны доступны и без авторизации. Единственное устойчивое различие — ключ `mainPhone`, он один. Плюс у Яндекса **нет проблемы с блоками**: за 7 суток ни одного прогона `banned`, свипы `done` (352, 213, 554 объявления), `yandex_detail_backfill` — 5 `done` из 7. Вывод: хранить живую сессию Яндекса (с её протуханием, ротацией и риском для учётки) не за что. Показательно, что настройка `YANDEX_COOKIES_FILE` объявлена в `config.py:1065`, но **не читается никаким кодом** и указывает на несуществующий файл — идею уже пробовали и не довели, и ничего от этого не сломалось.
bot-backend added the
bug
priority/p1
scope/backend
scrapers
tradein
labels 2026-08-28 18:35:15 +00:00
Author
Collaborator

Прод-замер накопленного ущерба (05.09, п.4 приёмки; телефоны вычищены PII-пургой #2217, поэтому прокси-признак блока контактов — агентский блок OfferCardAuthorInfo: agency_name IS NULL AND is_pro_seller IS NULL):

шт доля
обогащённых карточек Яндекса 23 208
без блока контактов (верхняя оценка — у частника блока законно нет, #3063 п.2) 7 248 31,2 %
из них ещё и без описания (почти чистый недогруз) 457 2,0 %
без контактов и без фото 0

Честная граница: 31,2 % — потолок, реальный недогруз где-то между 457 (2 %) и 7 248; точнее — только повторным заходом на выборку. После PR #3364 недогруз перестаёт засчитываться успехом (карточка остаётся в очереди), но уже сохранённые карточки без контактов в очередь сами не вернутся: detail_enriched_at у них проставлен. Если нужен backfill — сброс detail_enriched_at по узкому признаку (например, 457 «без контактов и без описания») отдельной миграцией; для 7 248 по верхней оценке — не стоит, снесёт законных частников.

Per-listing счётчик попыток в схеме отсутствует — вечно недогружаемая карточка ограничена только брейкером прогона (consecutive_none).

Прод-замер накопленного ущерба (05.09, п.4 приёмки; телефоны вычищены PII-пургой #2217, поэтому прокси-признак блока контактов — агентский блок `OfferCardAuthorInfo`: `agency_name IS NULL AND is_pro_seller IS NULL`): | | шт | доля | |---|---:|---:| | обогащённых карточек Яндекса | 23 208 | | | без блока контактов (верхняя оценка — у частника блока законно нет, #3063 п.2) | **7 248** | **31,2 %** | | из них ещё и без описания (почти чистый недогруз) | 457 | 2,0 % | | без контактов и без фото | 0 | | Честная граница: 31,2 % — потолок, реальный недогруз где-то между 457 (2 %) и 7 248; точнее — только повторным заходом на выборку. После PR #3364 недогруз перестаёт засчитываться успехом (карточка остаётся в очереди), но **уже сохранённые** карточки без контактов в очередь сами не вернутся: `detail_enriched_at` у них проставлен. Если нужен backfill — сброс `detail_enriched_at` по узкому признаку (например, 457 «без контактов и без описания») отдельной миграцией; для 7 248 по верхней оценке — не стоит, снесёт законных частников. Per-listing счётчик попыток в схеме отсутствует — вечно недогружаемая карточка ограничена только брейкером прогона (`consecutive_none`).
Author
Collaborator

PR #3364 смержен 05.09 (маркеры encryptedPhones ИЛИ redirectPhones + порог yandex_detail_min_html_bytes=1 МБ; недогруз → incomplete/failed, карточка остаётся в очереди; newbuilding.py не тронут — маркера для ЖК-лендинга нет).

Критерий приёмки, записанный ДО факта (годно до 12.09): первый прогон yandex_detail_backfill после деплоя — counters.incomplete / counters.attempted:

  • < 2 % — ожидаемо (недогруз редок, маркер не отбраковывает законные карточки);
  • ≥ 5 % — маркер режет законный класс (частник/«только чат»/другая раскладка INITIAL_STATE): взять listing_id из warning-лога, посмотреть категорию продавца и размер HTML, расширить набор маркеров, а не порог.

Проверка: SELECT id, counters->>'attempted', counters->>'incomplete', counters->>'enriched' FROM scrape_runs WHERE source='yandex_detail_backfill' ORDER BY id DESC LIMIT 3.

Открытым в issue остаётся п.4 backfill уже сохранённых недогруженных карточек (457 «без контактов и без описания» — узкий кандидат на сброс detail_enriched_at; 7 248 по верхней оценке трогать нельзя) и newbuilding.py — нужна одна полная ЖК-страница как фикстура.

PR #3364 смержен 05.09 (маркеры `encryptedPhones` ИЛИ `redirectPhones` + порог `yandex_detail_min_html_bytes=1 МБ`; недогруз → `incomplete`/`failed`, карточка остаётся в очереди; `newbuilding.py` не тронут — маркера для ЖК-лендинга нет). **Критерий приёмки, записанный ДО факта (годно до 12.09):** первый прогон `yandex_detail_backfill` после деплоя — `counters.incomplete / counters.attempted`: - **< 2 %** — ожидаемо (недогруз редок, маркер не отбраковывает законные карточки); - **≥ 5 %** — маркер режет законный класс (частник/«только чат»/другая раскладка INITIAL_STATE): взять `listing_id` из warning-лога, посмотреть категорию продавца и размер HTML, расширить набор маркеров, а не порог. Проверка: `SELECT id, counters->>'attempted', counters->>'incomplete', counters->>'enriched' FROM scrape_runs WHERE source='yandex_detail_backfill' ORDER BY id DESC LIMIT 3`. Открытым в issue остаётся п.4 backfill уже сохранённых недогруженных карточек (457 «без контактов и без описания» — узкий кандидат на сброс `detail_enriched_at`; 7 248 по верхней оценке трогать нельзя) и `newbuilding.py` — нужна одна полная ЖК-страница как фикстура.
Author
Collaborator

Первая точка приёмки #3364 (критерий записан 05.09 до факта): прогон yandex_detail_backfill id 6150 (05.09 20:57–21:02 UTC, первый после деплоя 357c434) — attempted=39, enriched=39, incomplete=00 % < 2 % . Выборка маленькая (39 карточек); держу критерий открытым до 12.09 — нужен хотя бы один прогон на сотни карточек (как 6139: 175), чтобы исключить «маркер режет законный класс».

Первая точка приёмки #3364 (критерий записан 05.09 до факта): прогон `yandex_detail_backfill` **id 6150** (05.09 20:57–21:02 UTC, первый после деплоя `357c434`) — `attempted=39, enriched=39, incomplete=0` → **0 % < 2 %** ✅. Выборка маленькая (39 карточек); держу критерий открытым до 12.09 — нужен хотя бы один прогон на сотни карточек (как 6139: 175), чтобы исключить «маркер режет законный класс».
Author
Collaborator

Вторая точка #3364: run 6161 yandex_detail_backfill (05.09 23:58 UTC) — attempted=39, enriched=39, incomplete=0 → 0 % (порог < 2 %). Обе точки по 39 карточек — похоже, это размер очереди за окно; критерий держу открытым до 12.09 на случай большого прогона.

Вторая точка #3364: run **6161** `yandex_detail_backfill` (05.09 23:58 UTC) — `attempted=39, enriched=39, incomplete=0` → 0 % (порог < 2 %). Обе точки по 39 карточек — похоже, это размер очереди за окно; критерий держу открытым до 12.09 на случай большого прогона.
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#3191
No description provided.