perf(tradein): avito seed price-brackets with open top bucket (≤2k blocks, no price ceiling) #1759

Merged
lekss361 merged 1 commit from perf/avito-seed-price-brackets into main 2026-06-18 14:33:08 +00:00
Owner

Проблема

avito exhaustive (fetch_all_secondary) на каждую комнатность стартовал с корня [0, 200М] и делил пополам → ~5 холостых probe-спусков через пустой верх (квартиры ЕКБ почти все <16М): probe [0,100М]=6439 → [0,50М]=6439 → [0,25М]=6435… прежде чем начать делить. Каждый probe через camoufox ~12с → ~7 мин/прогон впустую. Прод: run 201 за ~40 мин не вышел из студий.

Решение — data-driven seed-сетка (анализ прод-распределения вторички ЕКБ)

Структура: комнатность (slug) × ценовой брекет. Вместо корня [0,_MAX] — проход по seed-брекетам, каждый дальше дробится _walk_price_range если total > cap. Брекеты настроены под реальный пик 4-8М (студии 5783 — самая плотная, единственный >2к блок был студии 4-6М → пик дроблю по 1М):

[0,4][4,5][5,6][6,7][7,8][8,10][10,13][13,17][17,25][25,250]  +  [250, ∞)

(млн руб). Закрытые брекеты — hi-1 (эксклюзивный верх, без overlap на границах). Последний брекет открытый (hi=None → pmax не ставится → avito отдаёт всё ≥250М без потолка); он крошечный (avito >50М≈73) → пагинируется напрямую без бисекции (guard в _walk_price_range: при hi=None не делить).

Зачем ≤2к (cap=2000): avito жёстко режет пагинацию на ~5000/запрос → бакет >5000 теряет хвост. ≤2к с запасом → полнота + чаще checkpoint (resume). Seed-сетка убирает холостой спуск (ускорение ~10-15%); основной объём (все листинги ÷50) неизбежен.

⚠️ Брекеты настроены под ЕКБ (хардкод города в URL). Для multi-city (Тюмень пик 5-12М, Москва 15-40М — проверено) брекеты нужно делать per-city — отдельный фоллоу-ап.

Изменения

  • scrapers/avito.py_AVITO_PRICE_SEED_BRACKETS (11, открытый верх); fetch_all_secondary loop по брекетам (hi-1 для закрытых); guard hi=None в _walk_price_range (probe pmax=None, пагинация без split); рефактор leaf-пагинации в _paginate_leaf_bucket (closed-path byte-for-byte сохранён).
  • tests/scrapers/test_avito_exhaustive.py — call-count/args по брекетам, открытый брекет не делится даже при total>cap, dedup между брекетами, регресс.

Тесты

Full suite: 1991 passed, 1 skipped, 0 failed. avito-exhaustive: 23 passed. Ruff clean.

Прод после деплоя

price_cap_per_bucket=2000 уже в scrape_schedules. Запустить avito_full_load → в логах больше нет спуска [0,100M]→[0,50M]…; прогон проходит студии быстрее и доходит до всех 7 комнатностей. Глянуть что WARNING-путь открытого брекета (probe-fail → paginate 100 стр) не частит.

## Проблема avito exhaustive (`fetch_all_secondary`) на каждую комнатность стартовал с корня `[0, 200М]` и делил пополам → ~5 холостых probe-спусков через пустой верх (квартиры ЕКБ почти все <16М): `probe [0,100М]=6439 → [0,50М]=6439 → [0,25М]=6435…` прежде чем начать делить. Каждый probe через camoufox ~12с → ~7 мин/прогон впустую. Прод: run 201 за ~40 мин не вышел из студий. ## Решение — data-driven seed-сетка (анализ прод-распределения вторички ЕКБ) Структура: **комнатность (slug) × ценовой брекет**. Вместо корня `[0,_MAX]` — проход по seed-брекетам, каждый дальше дробится `_walk_price_range` если total > cap. Брекеты настроены под реальный пик 4-8М (студии 5783 — самая плотная, единственный >2к блок был студии 4-6М → пик дроблю по 1М): ``` [0,4][4,5][5,6][6,7][7,8][8,10][10,13][13,17][17,25][25,250] + [250, ∞) ``` (млн руб). Закрытые брекеты — `hi-1` (эксклюзивный верх, без overlap на границах). **Последний брекет открытый** (`hi=None` → pmax не ставится → avito отдаёт всё ≥250М без потолка); он крошечный (avito >50М≈73) → пагинируется напрямую без бисекции (guard в `_walk_price_range`: при `hi=None` не делить). **Зачем ≤2к (cap=2000):** avito жёстко режет пагинацию на ~5000/запрос → бакет >5000 теряет хвост. ≤2к с запасом → полнота + чаще checkpoint (resume). Seed-сетка убирает холостой спуск (ускорение ~10-15%); основной объём (все листинги ÷50) неизбежен. > ⚠️ Брекеты настроены под **ЕКБ** (хардкод города в URL). Для multi-city (Тюмень пик 5-12М, Москва 15-40М — проверено) брекеты нужно делать per-city — отдельный фоллоу-ап. ## Изменения - `scrapers/avito.py` — `_AVITO_PRICE_SEED_BRACKETS` (11, открытый верх); `fetch_all_secondary` loop по брекетам (hi-1 для закрытых); guard `hi=None` в `_walk_price_range` (probe pmax=None, пагинация без split); рефактор leaf-пагинации в `_paginate_leaf_bucket` (closed-path byte-for-byte сохранён). - `tests/scrapers/test_avito_exhaustive.py` — call-count/args по брекетам, открытый брекет не делится даже при total>cap, dedup между брекетами, регресс. ## Тесты Full suite: **1991 passed, 1 skipped, 0 failed**. avito-exhaustive: 23 passed. Ruff clean. ## Прод после деплоя `price_cap_per_bucket=2000` уже в scrape_schedules. Запустить avito_full_load → в логах больше нет спуска `[0,100M]→[0,50M]…`; прогон проходит студии быстрее и доходит до всех 7 комнатностей. Глянуть что WARNING-путь открытого брекета (probe-fail → paginate 100 стр) не частит.
lekss361 added 1 commit 2026-06-18 14:32:33 +00:00
perf(tradein): avito seed price-brackets with open top bucket (≤2k blocks, no price ceiling)
All checks were successful
CI / changes (pull_request) Successful in 7s
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
3f85fa59ff
lekss361 added the
enhancement
priority/p1
scope/backend
scrapers
status/review
labels 2026-06-18 14:32:44 +00:00
lekss361 merged commit 0538834c6b into main 2026-06-18 14:33:08 +00:00
lekss361 deleted branch perf/avito-seed-price-brackets 2026-06-18 14:33:08 +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#1759
No description provided.