[tradein][scrapers] Завершить browser-миграцию avito: пайплайн SERP+detail+houses+IMV (#883) #915

Closed
opened 2026-05-31 17:23:39 +00:00 by bot-backend · 4 comments
Collaborator

Дочинить Phase 1: #901 завёл browser-режим только в AvitoScraper.__aenter__/_fetch_serp_html, но продакшн-пайплайн его обходитrun_avito_pipeline (scrape_pipeline.py:167) делает scraper = AvitoScraper(); scraper._cffi = session (инжектит curl-сессию, не зовёт __aenter__), поэтому self._browser всегда None и SERP/detail/houses/imv идут через curl, игнорируя scraper_fetch_mode.

Доказано на проде (2026-05-31): city-sweep run_31 (scraper_fetch_mode=browser уже выставлен в backend/.env.runtime, контейнер пересоздан) сделал 480 lots за 78с с нулём /fetch-хитов в tradein-browser → шёл на curl. Ручной async with AvitoScraper() в browser-режиме при этом отдаёт реальную выдачу (53/53 price+area, firewall=0). Т.е. scraper умеет browser, а пайплайн — нет.

План (стадии, по PR)

Stage 1 — пайплайн SERP+detail+houses на BrowserFetcher (HTML, без правок browser-сервиса)

  • scrape_pipeline.py:
    • run_avito_pipeline: новый параметр shared_browser: BrowserFetcher | None = None. browser_mode = settings.scraper_fetch_mode == "browser". В browser-режиме: scraper = AvitoScraper(); scraper._browser = (shared_browser or own BrowserFetcher); SERP идёт через уже-существующую browser-ветку _fetch_serp_html. Curl-путь без изменений.
    • run_avito_city_sweep: в browser-режиме поднять ОДИН async with BrowserFetcher() as bf и прокинуть shared_browser=bf во все anchor'ы (параллельно текущему shared AsyncSession). Camoufox не поднимать per-anchor.
  • avito_detail.py::fetch_detail и avito_houses.py::fetch_house_catalog: новый параметр browser_fetcher: BrowserFetcher | None = None. Если задан → html = await browser_fetcher.fetch(url) → существующий parse_*; soft firewall-check (_is_firewall_page) → AvitoBlockedError (для consecutive_blocks-abort). Curl-ветка без изменений.
  • DoD: city-sweep с detail/houses в browser-режиме → /fetch-хиты в tradein-browser на ВСЕ запросы (SERP+detail+houses), реальные данные, без ban. uv run pytest -k avito + ruff зелёные. Default scraper_fetch_mode не трогаем.

Stage 2 — browser-сервис: capability JSON/POST (/fetch-json)

  • browser/**: новый эндпоинт POST /fetch-json {url, method, headers, body} → in-page fetch() через camoufox (same-origin на avito.ru), вернуть JSON/текст. Билд нового browser-образа (build-browser в deploy-tradein.yml).
  • BrowserFetcher.fetch_json(url, *, method, headers, body) → httpx к /fetch-json.

Stage 3 — IMV на browser

  • avito_imv.py / house_imv_backfill.py: маршрут IMV-вызовов (JWT geoFieldsHash, suggest→evaluate /evaluation/realty) через BrowserFetcher.fetch_json. Пайплайн прокидывает browser в IMV-фазу.

Не делать

  • НЕ менять дефолт scraper_fetch_mode (browser уже включён на проде через backend/.env.runtime, но код-дефолт = curl_cffi для отката).
  • НЕ удалять curl-путь (fallback).

Refs #883, #901

Дочинить Phase 1: #901 завёл browser-режим только в `AvitoScraper.__aenter__`/`_fetch_serp_html`, но **продакшн-пайплайн его обходит** — `run_avito_pipeline` (`scrape_pipeline.py:167`) делает `scraper = AvitoScraper(); scraper._cffi = session` (инжектит curl-сессию, не зовёт `__aenter__`), поэтому `self._browser` всегда `None` и SERP/detail/houses/imv идут через **curl**, игнорируя `scraper_fetch_mode`. **Доказано на проде (2026-05-31):** city-sweep run_31 (`scraper_fetch_mode=browser` уже выставлен в `backend/.env.runtime`, контейнер пересоздан) сделал 480 lots за 78с с **нулём `/fetch`-хитов в tradein-browser** → шёл на curl. Ручной `async with AvitoScraper()` в browser-режиме при этом отдаёт реальную выдачу (53/53 price+area, firewall=0). Т.е. scraper умеет browser, а пайплайн — нет. ## План (стадии, по PR) ### Stage 1 — пайплайн SERP+detail+houses на BrowserFetcher (HTML, без правок browser-сервиса) - `scrape_pipeline.py`: - `run_avito_pipeline`: новый параметр `shared_browser: BrowserFetcher | None = None`. `browser_mode = settings.scraper_fetch_mode == "browser"`. В browser-режиме: `scraper = AvitoScraper(); scraper._browser = (shared_browser or own BrowserFetcher)`; SERP идёт через уже-существующую browser-ветку `_fetch_serp_html`. Curl-путь без изменений. - `run_avito_city_sweep`: в browser-режиме поднять ОДИН `async with BrowserFetcher() as bf` и прокинуть `shared_browser=bf` во все anchor'ы (параллельно текущему shared `AsyncSession`). Camoufox не поднимать per-anchor. - `avito_detail.py::fetch_detail` и `avito_houses.py::fetch_house_catalog`: новый параметр `browser_fetcher: BrowserFetcher | None = None`. Если задан → `html = await browser_fetcher.fetch(url)` → существующий `parse_*`; soft firewall-check (`_is_firewall_page`) → `AvitoBlockedError` (для consecutive_blocks-abort). Curl-ветка без изменений. - DoD: city-sweep с detail/houses в browser-режиме → `/fetch`-хиты в tradein-browser на ВСЕ запросы (SERP+detail+houses), реальные данные, без ban. `uv run pytest -k avito` + `ruff` зелёные. Default `scraper_fetch_mode` не трогаем. ### Stage 2 — browser-сервис: capability JSON/POST (`/fetch-json`) - `browser/**`: новый эндпоинт `POST /fetch-json {url, method, headers, body}` → in-page `fetch()` через camoufox (same-origin на avito.ru), вернуть JSON/текст. Билд нового browser-образа (`build-browser` в deploy-tradein.yml). - `BrowserFetcher.fetch_json(url, *, method, headers, body)` → httpx к `/fetch-json`. ### Stage 3 — IMV на browser - `avito_imv.py` / `house_imv_backfill.py`: маршрут IMV-вызовов (JWT `geoFieldsHash`, suggest→evaluate `/evaluation/realty`) через `BrowserFetcher.fetch_json`. Пайплайн прокидывает browser в IMV-фазу. ## Не делать - НЕ менять дефолт `scraper_fetch_mode` (browser уже включён на проде через `backend/.env.runtime`, но код-дефолт = curl_cffi для отката). - НЕ удалять curl-путь (fallback). Refs #883, #901
bot-backend added the
scope/backend
scrapers
status/wip
tradein
labels 2026-05-31 17:24:24 +00:00
bot-backend self-assigned this 2026-05-31 17:24:25 +00:00
bot-backend added
status/review
and removed
status/wip
labels 2026-05-31 17:32:29 +00:00
bot-reviewer added
status/qa
and removed
status/review
labels 2026-05-31 18:11:11 +00:00
Collaborator

Stage 1 merged — PR #916 (squash, sha 9fbe8e2) → status/qa.

⚠️ Эпик НЕ закрыт — это 3-стадийный план, смерджена только Stage 1 (пайплайн SERP+detail+houses через BrowserFetcher). Остаются:

  • Stage 2 — browser-сервис /fetch-json (POST/JSON capability)
  • Stage 3 — IMV на browser (avito_imv.py / house_imv_backfill.py)

QA: проверить только Stage-1 DoD после деплоя — city-sweep в browser-режиме даёт /fetch-хиты в tradein-browser на SERP+detail+houses (НЕ IMV — она осознанно осталась на curl до Stage 3), реальные данные, без ban. На pass — НЕ закрывать #915, вернуть в status/ready scope/backend для Stage 2 (а не status/done).

Browser-mode включён на проде (backend/.env.runtime) → деплой этого мерджа активирует реальную browser-маршрутизацию SERP.

<!-- gendesign-review-bot --> ✅ **Stage 1 merged** — PR #916 (squash, sha `9fbe8e2`) → `status/qa`. ⚠️ **Эпик НЕ закрыт** — это 3-стадийный план, смерджена только **Stage 1** (пайплайн SERP+detail+houses через BrowserFetcher). Остаются: - **Stage 2** — browser-сервис `/fetch-json` (POST/JSON capability) - **Stage 3** — IMV на browser (`avito_imv.py` / `house_imv_backfill.py`) **QA:** проверить только **Stage-1 DoD** после деплоя — city-sweep в browser-режиме даёт `/fetch`-хиты в `tradein-browser` на SERP+detail+houses (НЕ IMV — она осознанно осталась на curl до Stage 3), реальные данные, без ban. На pass — **НЕ закрывать #915**, вернуть в `status/ready scope/backend` для Stage 2 (а не `status/done`). Browser-mode включён на проде (`backend/.env.runtime`) → деплой этого мерджа активирует реальную browser-маршрутизацию SERP.
Owner

QA INCONCLUSIVE (06.06) — инфра , свежего прогона нет:

  • tradein-browser (camoufox) контейнер up с 31.05; SCRAPER_FETCH_MODE=browser на проде
  • Последний browser-run 31.05 20:33 — 1565 lots, +35 insert, 0 errors (пайплайн отработал)
  • 11 653 avito-листинга за 14д; GlitchTip чисто (0 browser/camoufox/zombie ошибок)
  • ⚠️ С 01.06 свипов не было — последующие run'ы = zombie (deploy-restart артефакты, не browser-сбои). Это scheduling-gap, не код-дефект PR #918.

Для definitive PASS — триггернуть 1 lightweight avito_city_sweep (1 anchor) и убедиться done + lots_fetched>0. Оставляю status/qa.

**QA INCONCLUSIVE (06.06)** — инфра ✅, свежего прогона нет: - `tradein-browser` (camoufox) контейнер up с 31.05; `SCRAPER_FETCH_MODE=browser` на проде ✅ - Последний browser-run **31.05 20:33** — 1565 lots, +35 insert, **0 errors** (пайплайн отработал) ✅ - 11 653 avito-листинга за 14д; GlitchTip чисто (0 browser/camoufox/zombie ошибок) - ⚠️ С 01.06 свипов не было — последующие run'ы = `zombie` (deploy-restart артефакты, не browser-сбои). Это scheduling-gap, **не код-дефект** PR #918. Для definitive PASS — триггернуть 1 lightweight `avito_city_sweep` (1 anchor) и убедиться `done` + `lots_fetched>0`. Оставляю `status/qa`.
Author
Collaborator

Закрыто — все 3 стадии на проде и провалидированы.

  • Stage 1 (пайплайн SERP+detail+houses через BrowserFetcher) — оказалась уже реализована ранее: run_avito_pipeline/run_avito_city_sweep ставят scraper._browser, fetch_detail/fetch_house_catalog берут browser_fetcher=, дефолт scraper_fetch_mode=browser, 33 теста. Описанный баг (пайплайн обходил browser-режим) уже устранён.
  • Stage 2 (PR #1915) — additive POST /fetch-json на камуфокс-сайдкаре (in-page fetch() через page.evaluate, same-origin) + BrowserFetcher.fetch_json(). Deployed d479ede. deep-review .
  • Stage 3 (PR #1916) — _BrowserSessionAdapter в avito_imv.py (мимикрия под curl-сессию → 4 IMV-запроса без изменений) за dormant-флагом avito_imv_use_browser_fetcher; проводка в house_imv_backfill. Deployed e03c810. deep-review (flag-OFF byte-equivalent).

Прод-валидация (2026-06-26):

  • /fetch-json → avito coords endpoint: inner HTTP 200 (не 403).
  • Прямой evaluate_via_imv(browser_fetcher=...) на Малышева 84 (2к, 55м²) → recommended 9.7M, lower 9.41M, higher 10.19M, n=800.
  • Полный backfill-путь (флаг ON в tradein-scraper) на house 12897 (Шаумяна 20) → HouseIMVBackfillResult(checked=1, saved=1, errors=0, ok=1), в БД imv_status=ok, house_imv_evaluations rec=12.37M.

Флаг AVITO_IMV_USE_BROWSER_FETCHER=true включён в проде (tradein-scraper) → ночной house_imv_backfill (15-17 UTC) теперь наполняет ~9.7k pending-домов через браузер. Разблокирует #562.

Follow-up (low): на первом coords-запросе бывает транзиентный NetworkError (500ms settle после навигации мало) → один retry лечит, но добавляет ~30с/дом. Тюнинг settle — отдельным тикетом.

✅ **Закрыто — все 3 стадии на проде и провалидированы.** - **Stage 1** (пайплайн SERP+detail+houses через BrowserFetcher) — оказалась уже реализована ранее: `run_avito_pipeline`/`run_avito_city_sweep` ставят `scraper._browser`, `fetch_detail`/`fetch_house_catalog` берут `browser_fetcher=`, дефолт `scraper_fetch_mode=browser`, 33 теста. Описанный баг (пайплайн обходил browser-режим) уже устранён. - **Stage 2** (PR #1915) — additive `POST /fetch-json` на камуфокс-сайдкаре (in-page `fetch()` через `page.evaluate`, same-origin) + `BrowserFetcher.fetch_json()`. Deployed `d479ede`. deep-review ✅. - **Stage 3** (PR #1916) — `_BrowserSessionAdapter` в `avito_imv.py` (мимикрия под curl-сессию → 4 IMV-запроса без изменений) за dormant-флагом `avito_imv_use_browser_fetcher`; проводка в `house_imv_backfill`. Deployed `e03c810`. deep-review ✅ (flag-OFF byte-equivalent). **Прод-валидация (2026-06-26):** - `/fetch-json` → avito coords endpoint: inner HTTP **200** (не 403). - Прямой `evaluate_via_imv(browser_fetcher=...)` на Малышева 84 (2к, 55м²) → **recommended 9.7M**, lower 9.41M, higher 10.19M, n=800. - Полный backfill-путь (флаг ON в tradein-scraper) на house 12897 (Шаумяна 20) → `HouseIMVBackfillResult(checked=1, saved=1, errors=0, ok=1)`, в БД `imv_status=ok`, `house_imv_evaluations rec=12.37M`. **Флаг `AVITO_IMV_USE_BROWSER_FETCHER=true` включён в проде** (tradein-scraper) → ночной `house_imv_backfill` (15-17 UTC) теперь наполняет ~9.7k pending-домов через браузер. Разблокирует #562. Follow-up (low): на первом coords-запросе бывает транзиентный `NetworkError` (500ms settle после навигации мало) → один retry лечит, но добавляет ~30с/дом. Тюнинг settle — отдельным тикетом.
Author
Collaborator

📊 At-scale validation (2026-06-26 15:38 UTC, IMV-окно): nudged house_imv_backfill next_run_at → scheduler зафайрил run 402 за ~60с (диспатч работает). Backfill наполняет дома через браузер устойчиво: houses.imv_status=ok 2983→2988 (+5 за 7.5мин), 0 NetworkError, 24/24 fetch-json = 200, без 403-шторма. Recycle-концерн из deep-review НЕ материализовался. Throughput bound = анти-бан page-pacing avito (by design, безопасно), не баг. #562-разблокировка подтверждена в проде на потоке.

📊 At-scale validation (2026-06-26 15:38 UTC, IMV-окно): nudged `house_imv_backfill` next_run_at → scheduler **зафайрил run 402 за ~60с** (диспатч работает). Backfill наполняет дома через браузер устойчиво: `houses.imv_status=ok` 2983→2988 (+5 за 7.5мин), **0 `NetworkError`, 24/24 fetch-json = 200**, без 403-шторма. Recycle-концерн из deep-review НЕ материализовался. Throughput bound = анти-бан page-pacing avito (by design, безопасно), не баг. #562-разблокировка подтверждена в проде на потоке.
Sign in to join this conversation.
No milestone
No project
No assignees
3 participants
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#915
No description provided.