fix(tradein/avito): страница SERP отдаёт 60 карточек, а не 50 — лишние 17% запросов #3041

Merged
lekss361 merged 1 commit from fix/avito-offers-per-page into main 2026-08-21 14:46:33 +00:00
Owner

Хвост от #3033, не вошедший в #3039. Одна константа плюс регрессионный тест.

Замер

Живьём 2026-08-21 через tradein-browser (camoufox, JS исполняется — не curl_cffi, который на этом пути отдаёт app-shell без карточек): 59-60 уникальных data-item-id на странице выдачи.

«Лишние» сверх 50 — это не рекламный блок и не секция «похожие». Это обычные объявления с платным продвижением (vas-icon_type-promoted), они лежат в том же списке под page-title/count и собираются наравне с остальными.

Направление эффекта — обратное тому, как это легко прочитать

Константа участвует только в ceil(total / PAGE). Значит занижение размера страницы завышало расчётное число страниц:

Что При 50 При 60
pages_needed для 1000 объявлений 20 17

Последствия занижения:

  • запрашивали примерно на 17% страниц больше, чем нужно. При доле отказов 37-83% по avito-заданиям лишние запросы — основная цена ошибки: каждый лишний запрос это лишний шанс словить SERP firewall;
  • tail_loss считался как total − cap × 50 и завышал потерю в предупреждениях;
  • флаг complete в пагинации листа (pages_needed <= max_pages) чаще ложно показывал «неполно», из-за чего бакет реже помечался завершённым.

Тихой потери данных не было. Я специально искал условие вида «страница вернула меньше PAGE карточек, значит последняя» — такого в коде нет, пагинация ограничена только max_pages. Правка ценна тем, что убирает лишние запросы и чинит враньё в метриках, а не тем, что спасает данные.

Это стоит подчеркнуть, потому что в исходном разборе направление было сформулировано наоборот («занижала pages_needed → тихий tail-loss»). Тест test_pages_needed_arithmetic_uses_the_constant закрепляет верную трактовку, чтобы она не потерялась.

Тесты

tradein-mvp/backend/tests/test_avito_offers_per_page.py — 4 проверки:

  • значение равно 60;
  • арифметика ceil при 60 даёт меньше страниц, чем при 50 (направление эффекта);
  • константа всё ещё проведена в расчёты — не осталась мёртвой после правки;
  • комментарий рядом не утверждает «~50 карточек».

Прогнано: новый файл 4 passed; pytest -k avito249 passed, 1 skipped; ruff check чисто.

Отдельно: ложная тревога, которую проверил и не подтвердил

При этой же работе прозвучало, что города kamensk_uralskiy и verkhnyaya_pyshma якобы 404-ят из-за расхождения слагов (у Авито kamensk-uralskiy через дефис и verhnyaya_pyshma без «к»). Не подтвердилось:

  • оба свипа собирают данные — 1048 и 1346 объявлений за 30 дней;
  • маппинг существует: orchestration/pipeline.py:413, CityLocation("kamensk-uralskiy", …), и рядом дважды стоит комментарий ровно про эту особенность.

Проверка шла в обход маппинга, поэтому и дала 404. Issue не заводил.

Refs #3033

Хвост от #3033, не вошедший в #3039. Одна константа плюс регрессионный тест. ## Замер Живьём 2026-08-21 через `tradein-browser` (camoufox, JS исполняется — не curl_cffi, который на этом пути отдаёт app-shell без карточек): **59-60 уникальных `data-item-id`** на странице выдачи. «Лишние» сверх 50 — это не рекламный блок и не секция «похожие». Это обычные объявления с платным продвижением (`vas-icon_type-promoted`), они лежат в том же списке под `page-title/count` и собираются наравне с остальными. ## Направление эффекта — обратное тому, как это легко прочитать Константа участвует **только** в `ceil(total / PAGE)`. Значит занижение размера страницы **завышало** расчётное число страниц: | Что | При 50 | При 60 | |---|---|---| | `pages_needed` для 1000 объявлений | 20 | **17** | Последствия занижения: - **запрашивали примерно на 17% страниц больше, чем нужно.** При доле отказов 37-83% по avito-заданиям лишние запросы — основная цена ошибки: каждый лишний запрос это лишний шанс словить SERP firewall; - `tail_loss` считался как `total − cap × 50` и **завышал** потерю в предупреждениях; - флаг `complete` в пагинации листа (`pages_needed <= max_pages`) чаще ложно показывал «неполно», из-за чего бакет реже помечался завершённым. **Тихой потери данных не было.** Я специально искал условие вида «страница вернула меньше PAGE карточек, значит последняя» — такого в коде нет, пагинация ограничена только `max_pages`. Правка ценна тем, что убирает лишние запросы и чинит враньё в метриках, а не тем, что спасает данные. Это стоит подчеркнуть, потому что в исходном разборе направление было сформулировано наоборот («занижала pages_needed → тихий tail-loss»). Тест `test_pages_needed_arithmetic_uses_the_constant` закрепляет верную трактовку, чтобы она не потерялась. ## Тесты `tradein-mvp/backend/tests/test_avito_offers_per_page.py` — 4 проверки: - значение равно 60; - арифметика `ceil` при 60 даёт меньше страниц, чем при 50 (направление эффекта); - константа всё ещё проведена в расчёты — не осталась мёртвой после правки; - комментарий рядом не утверждает «~50 карточек». Прогнано: новый файл 4 passed; `pytest -k avito` — **249 passed, 1 skipped**; `ruff check` чисто. ## Отдельно: ложная тревога, которую проверил и не подтвердил При этой же работе прозвучало, что города `kamensk_uralskiy` и `verkhnyaya_pyshma` якобы 404-ят из-за расхождения слагов (у Авито `kamensk-uralskiy` через дефис и `verhnyaya_pyshma` без «к»). **Не подтвердилось:** - оба свипа собирают данные — 1048 и 1346 объявлений за 30 дней; - маппинг существует: `orchestration/pipeline.py:413`, `CityLocation("kamensk-uralskiy", …)`, и рядом дважды стоит комментарий ровно про эту особенность. Проверка шла в обход маппинга, поэтому и дала 404. Issue не заводил. Refs #3033
lekss361 added 1 commit 2026-08-21 14:39:26 +00:00
fix(tradein/avito): страница SERP отдаёт 60 карточек, а не 50
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 11s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 4m27s
6ffcd78d35
Замер живьём 2026-08-21 через tradein-browser (camoufox, JS исполняется):
59-60 уникальных `data-item-id` на странице выдачи. «Лишние» сверх 50 —
обычные объявления с платным продвижением (`vas-icon_type-promoted`), они
лежат в том же списке под `page-title/count`, а не отдельным рекламным
блоком, и собираются наравне с остальными.

Направление эффекта важно понимать правильно. Константа участвует ТОЛЬКО
в `ceil(total / PAGE)`, поэтому занижение размера страницы ЗАВЫШАЛО
расчётное число страниц, а не занижало:

  - запрашивали примерно на 17 % страниц больше, чем нужно; при доле банов
    37-83 % по avito-заданиям лишние запросы — основная цена ошибки;
  - `tail_loss` считался как `total - cap * 50` и завышал потерю;
  - флаг `complete` в пагинации листа чаще ложно показывал «неполно».

Тихой потери данных НЕ было: условия «страница вернула меньше PAGE
карточек, значит последняя» в коде нет, пагинация ограничена только
`max_pages`. Тест закрепляет и значение, и направление арифметики, чтобы
неверная трактовка не вернулась при следующем рефакторинге.

Найдено при разборе Авито сверкой живого браузера со скраппером; полный
разбор — в волте `research/Avito_Live_Browser_Recon_0821.md`.

Refs #3033
lekss361 merged commit 28f5e8079e into main 2026-08-21 14:46:33 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#3041
No description provided.