feat(#2989): локальный ручной сборщик SERP Авито по Москве и МО #3420

Merged
lekss361 merged 2 commits from feat/2989-msk-avito-local-collector into main 2026-09-08 20:04:26 +00:00
Owner

Summary

Трек 1 плана наполнения МЕРЫ по Москве: сбор SERP-карточек Авито (вторичка, Москва+МО) начинается сейчас, не дожидаясь региональной модели данных — рыночную историю задним числом не добрать.

Два новых файла, оба вне прод-путей: tradein-mvp/scripts/local-avito-msk/collect.py + README.md. Прод-скрейпер, расписания, пул прокси и флаг avito_serp_ekb_only не тронуты.

  • Браузерconnect_over_cdp к уже открытому Chrome владельца с залогиненным техническим аккаунтом (localhost:9222). Свой профиль не поднимается, открывается только своя вкладка, чужие не закрываются.
  • Парсер — импорт из scraper-kit (_parse_html, _extract_total_count, _is_firewall_page), а не копия. avito_serp_ekb_only=False — иначе парсер выбросит всё без /ekaterinburg/ в URL, включая все подмосковные слаги.
  • Дробление — адаптивная бисекция по цене до ≤1500 на коридор (потолок выдачи Авито 30×60=1800). Середина геометрическая: на диапазоне 1 млн–100 млн арифметическая даёт вырожденные половины. Коридор, который не удалось разбить, помечается truncated и считается явно — молчаливого усечения нет. Гео-параметры radius/geoCoords не используются (#3043 — сервером не применяются).
  • Стоп — жёсткий, без ретраев, на первом признаке блока: 403/439 → platform, 429 → ratelimit, маркеры фаервола → firewall, PoW → challenge, 0 карточек при ненулевом счётчике → empty_page.
  • Куда — прод-схема msk_raw (создана заранее, вне линейки миграций приложения) через ssh + docker exec -i psql -f -, батчами по 1000.
  • Дефолт — --measure 100. Полный проход только явным --full.

Test plan

  • Импорт scraper-kit без установки backend: from scraper_kit.providers.avito.serp import AvitoScraper → ok
  • --help — все ключи на месте, дефолт --measure 100
  • --dry-run на фикстуре backend/tests/fixtures/avito_serp_sample.html → 2 карточки, CSV корректной формы
  • Стоп-детекторы на синтетике: 403/429/firewall/startPow/empty_page — все 6 совпали с ожиданием
  • build_url сохраняет токен f= дословно, добавляет pmin/pmax/p
  • Живой замерный прогон 100 страниц против Авито — отдельно, вручную, после мержа
  • Счётчик page-title/count на живой московской выдаче: фикстура екатеринбургская и без счётчика, ветка count=None → empty_page на живых данных ещё не проверена

Тесты намеренно не пишутся до живой проверки функционала.

Часть эпика #2989.

## Summary Трек 1 плана наполнения МЕРЫ по Москве: сбор SERP-карточек Авито (вторичка, Москва+МО) начинается сейчас, не дожидаясь региональной модели данных — рыночную историю задним числом не добрать. Два новых файла, оба вне прод-путей: `tradein-mvp/scripts/local-avito-msk/collect.py` + `README.md`. Прод-скрейпер, расписания, пул прокси и флаг `avito_serp_ekb_only` не тронуты. - **Браузер** — `connect_over_cdp` к уже открытому Chrome владельца с залогиненным техническим аккаунтом (`localhost:9222`). Свой профиль не поднимается, открывается только своя вкладка, чужие не закрываются. - **Парсер** — импорт из scraper-kit (`_parse_html`, `_extract_total_count`, `_is_firewall_page`), а не копия. `avito_serp_ekb_only=False` — иначе парсер выбросит всё без `/ekaterinburg/` в URL, включая все подмосковные слаги. - **Дробление** — адаптивная бисекция по цене до ≤1500 на коридор (потолок выдачи Авито 30×60=1800). Середина геометрическая: на диапазоне 1 млн–100 млн арифметическая даёт вырожденные половины. Коридор, который не удалось разбить, помечается `truncated` и считается явно — молчаливого усечения нет. Гео-параметры `radius`/`geoCoords` не используются (#3043 — сервером не применяются). - **Стоп** — жёсткий, без ретраев, на первом признаке блока: 403/439 → `platform`, 429 → `ratelimit`, маркеры фаервола → `firewall`, PoW → `challenge`, 0 карточек при ненулевом счётчике → `empty_page`. - **Куда** — прод-схема `msk_raw` (создана заранее, вне линейки миграций приложения) через `ssh + docker exec -i psql -f -`, батчами по 1000. - **Дефолт — `--measure 100`.** Полный проход только явным `--full`. ## Test plan - [x] Импорт scraper-kit без установки backend: `from scraper_kit.providers.avito.serp import AvitoScraper` → ok - [x] `--help` — все ключи на месте, дефолт `--measure 100` - [x] `--dry-run` на фикстуре `backend/tests/fixtures/avito_serp_sample.html` → 2 карточки, CSV корректной формы - [x] Стоп-детекторы на синтетике: 403/429/firewall/startPow/empty_page — все 6 совпали с ожиданием - [x] `build_url` сохраняет токен `f=` дословно, добавляет `pmin/pmax/p` - [ ] Живой замерный прогон 100 страниц против Авито — отдельно, вручную, после мержа - [ ] Счётчик `page-title/count` на живой московской выдаче: фикстура екатеринбургская и без счётчика, ветка `count=None → empty_page` на живых данных ещё не проверена Тесты намеренно не пишутся до живой проверки функционала. Часть эпика #2989.
lekss361 added 2 commits 2026-09-08 19:55:24 +00:00
Трек 1 эпика: нужен разовый корпус вторички Москвы+МО в msk_raw, а прод-скрейпер
для этого не подходит — его расписания, прокси-пул и сайдкар держат ЕКБ и трогать
их ради ручного замера нельзя.

Поэтому скрипт: браузер — уже открытый Chrome владельца с залогиненным техаккаунтом
(connect_over_cdp, своя вкладка, чужие вкладки/контекст/браузер не трогаем и не
закрываем; своего профиля не поднимаем); парсер — импорт _parse_html /
_extract_total_count / _is_firewall_page из scraper-kit, а не копия (копия разъедется
с прод-парсером на первом же DOM-drift); заливка — поток в psql через ssh, потому что
прямого доступа к прод-Postgres с локалки нет, а одиночный psql -c ломается на
квотинге.

avito_serp_ekb_only=False обязателен: с True парсер выбрасывает всё, где в URL нет
/ekaterinburg/ — из московской выдачи не осталось бы ни одной карточки.

Потолок Авито 30x60=1800 на запрос, поэтому план ценовых коридоров с бисекцией по
ГЕОМЕТРИЧЕСКОЙ середине: цены логнормальны, арифметическая середина 1млн..100млн
даёт вырожденно-пустую верхнюю половину. Коридор, который не влезает в 1800 даже
на минимальной ширине, помечается truncated и недобор пишется в batches.notes —
молчаливое усечение читалось бы как полный охват.

Стоп на первом признаке блока (403/439, 429, firewall, PoW, 0 карточек при ненулевом
счётчике) без ретраев: ретрай по забаненному техаккаунту только углубляет бан.
Дефолт — --measure 100, полный проход только по явному --full.
fix(msk-collector): --resume берёт base_url из сохранённого плана
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 10s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
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
bde38cdce7
Коридоры считаются под конкретный URL выдачи. При --resume код брал свежий
args.base_url, поэтому запуск без повтора --base-url молча качал другую
выдачу под тем же batch_id. Теперь URL из плана, расхождение с аргументом —
явная ошибка вместо тихого выбора одного из двух.
lekss361 merged commit 76d018b5a0 into main 2026-09-08 20:04:26 +00:00
lekss361 deleted branch feat/2989-msk-avito-local-collector 2026-09-08 20:04:26 +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#3420
No description provided.