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
Замер живьём 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
61 lines
3 KiB
Python
61 lines
3 KiB
Python
"""Регрессия на размер страницы Avito SERP.
|
||
|
||
Замер живьём 2026-08-21 (tradein-browser / camoufox, JS исполняется): страница
|
||
выдачи отдаёт 59-60 уникальных `data-item-id`. Константа стояла 50 — то есть
|
||
`ceil(total / PAGE)` завышал число страниц примерно на 17 %, и каждый лишний
|
||
запрос — лишний шанс словить SERP firewall.
|
||
|
||
Тест намеренно проверяет НЕ только значение, но и то, что константа
|
||
по-прежнему участвует в расчёте страниц: иначе правку значения легко потерять
|
||
при рефакторинге, оставив мёртвую константу.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import ast
|
||
import math
|
||
from pathlib import Path
|
||
|
||
from scraper_kit.providers.avito import serp
|
||
|
||
_SERP_SOURCE = Path(serp.__file__)
|
||
|
||
|
||
def test_offers_per_page_matches_live_measurement() -> None:
|
||
"""60, а не 50 — замер 2026-08-21 через реальный браузер."""
|
||
assert serp._AVITO_OFFERS_PER_PAGE == 60
|
||
|
||
|
||
def test_pages_needed_arithmetic_uses_the_constant() -> None:
|
||
"""`ceil(total / PAGE)` при 60 даёт меньше страниц, чем при 50.
|
||
|
||
Закрепляет направление эффекта: занижение размера страницы ЗАВЫШАЛО число
|
||
запрашиваемых страниц. В PR-разборе это было сформулировано наоборот, и
|
||
тест существует, чтобы неверная трактовка не вернулась.
|
||
"""
|
||
total = 1000
|
||
assert math.ceil(total / 60) == 17
|
||
assert math.ceil(total / 50) == 20
|
||
assert math.ceil(total / serp._AVITO_OFFERS_PER_PAGE) == 17
|
||
|
||
|
||
def test_constant_is_still_wired_into_pagination() -> None:
|
||
"""Константа реально используется, а не осталась мёртвой после правки."""
|
||
tree = ast.parse(_SERP_SOURCE.read_text(encoding="utf-8"))
|
||
uses = sum(
|
||
1
|
||
for node in ast.walk(tree)
|
||
if isinstance(node, ast.Name) and node.id == "_AVITO_OFFERS_PER_PAGE"
|
||
)
|
||
# 1 присваивание + минимум два места расчёта (pages_needed в бисекции и в
|
||
# пагинации листа); если станет меньше — кто-то отвязал константу.
|
||
assert uses >= 3, f"_AVITO_OFFERS_PER_PAGE упоминается {uses} раз, ожидалось >= 3"
|
||
|
||
|
||
def test_no_stale_fifty_in_page_size_comment() -> None:
|
||
"""Комментарий рядом с константой не должен утверждать «~50 карточек»."""
|
||
src = _SERP_SOURCE.read_text(encoding="utf-8")
|
||
marker = "_AVITO_OFFERS_PER_PAGE = "
|
||
idx = src.index(marker)
|
||
preceding = src[max(0, idx - 1200) : idx]
|
||
assert "~50 карточек" not in preceding
|