[tradein][scrapers] tradein-browser: per-provider concurrency — параллель между поставщиками, ≤1 коннект на поставщика #1793

Closed
opened 2026-06-19 15:46:58 +00:00 by lekss361 · 0 comments
Owner

Цель (требование владельца 2026-06-19)

Скраппим через браузер (camoufox) — медленно, но надёжно (#883). Нужно подтюнить конкурентность: параллелизм МЕЖДУ поставщиками, но ≤1 одновременное соединение НА поставщика (avito/cian/yandex гоняются параллельно, но внутри одного источника — строго по одному, чтобы не множить коннекты на один прокси-IP и не триггерить антибот). IP-ротацию делает сам прокси-провайдер (авто), наш код её НЕ делает.

Текущее состояние (tradein-mvp/browser/server.py)

  • Один глобальный _browser (AsyncCamoufox) + один _fetch_lock (server.py:93,292) → /fetch сериализован глобально: один запрос за раз НА ВСЕ источники. Параллелизма между поставщиками нет.
  • Один прокси на всех: SCRAPER_PROXY_URL = AVITO_PROXY_URL or SCRAPER_PROXY_URL (server.py:57) → cian/yandex фетчатся через avito-IP.
  • Env уже содержит per-provider прокси: BROWSER_PROXY_AVITO, BROWSER_PROXY_CIAN, BROWSER_PROXY_YANDEX (mobileproxy, разные порты) — но server.py их не использует.

Дизайн

  1. Определение поставщика по host URL: avito.ru→avito, cian.ru→cian, yandex.ru→yandex (default → generic).
  2. Per-provider camoufox-инстансы: словарь {provider: browser} , каждый поднят со своим BROWSER_PROXY_<PROVIDER> + geoip=True (фингерпринт привязан к гео IP — поэтому отдельные инстансы, не общие контексты). Lazy-launch + та же resilience (retry-loop, crash-recovery, recycle по счётчику страниц — но per-provider счётчик).
  3. Per-provider asyncio.Lock: {provider: Lock} → ≤1 fetch на поставщика; разные поставщики не блокируют друг друга (параллель).
  4. /fetch принимает (или инферит из host) provider → берёт его browser+lock+proxy.
  5. Память: 3 camoufox ≈ 3×~1ГБ. Текущий лимит контейнера 5ГБ (сейчас 1ГБ/5ГБ used). Должно влезть, но мониторить; при нужде поднять лимит в compose. Можно lazy: инстанс поднимается при первом запросе к поставщику.

Что сделать

  • browser/server.py: рефактор глобального состояния в per-provider (dict browsers/locks/counters), routing по host, per-provider proxy из BROWSER_PROXY_<PROVIDER>.
  • Сохранить контракты /fetch /login /health (health отдаёт per-provider readiness).
  • Тесты: проверить, что 2 запроса к разным поставщикам идут параллельно, а 2 к одному — сериализованы.
  • docker-compose: при нужде поднять mem limit tradein-browser.

Усилие

L (рефактор браузер-сервиса + тесты + проверка памяти на проде).

Файлы: tradein-mvp/browser/server.py, docker-compose*.yml, tradein-mvp/backend/app/services/scrapers/browser_fetcher.py (если передавать provider явно).
Cross-ref: EPIC #883 (миграция fetch на camoufox), #885/#886 (per-source camoufox), #1184 (browser proxy resilience). Анализ: vault research/Avito_Scraper_Endpoint_Map_0619.md.

## Цель (требование владельца 2026-06-19) Скраппим через браузер (camoufox) — медленно, но надёжно (#883). Нужно подтюнить конкурентность: **параллелизм МЕЖДУ поставщиками, но ≤1 одновременное соединение НА поставщика** (avito/cian/yandex гоняются параллельно, но внутри одного источника — строго по одному, чтобы не множить коннекты на один прокси-IP и не триггерить антибот). IP-ротацию делает сам прокси-провайдер (авто), наш код её НЕ делает. ## Текущее состояние (`tradein-mvp/browser/server.py`) - Один глобальный `_browser` (AsyncCamoufox) + один `_fetch_lock` (`server.py:93,292`) → `/fetch` сериализован **глобально**: один запрос за раз НА ВСЕ источники. Параллелизма между поставщиками нет. - Один прокси на всех: `SCRAPER_PROXY_URL = AVITO_PROXY_URL or SCRAPER_PROXY_URL` (`server.py:57`) → cian/yandex фетчатся через avito-IP. - Env уже содержит per-provider прокси: `BROWSER_PROXY_AVITO`, `BROWSER_PROXY_CIAN`, `BROWSER_PROXY_YANDEX` (mobileproxy, разные порты) — но server.py их не использует. ## Дизайн 1. **Определение поставщика по host URL**: `avito.ru→avito`, `cian.ru→cian`, `yandex.ru→yandex` (default → generic). 2. **Per-provider camoufox-инстансы**: словарь `{provider: browser}` , каждый поднят со своим `BROWSER_PROXY_<PROVIDER>` + `geoip=True` (фингерпринт привязан к гео IP — поэтому отдельные инстансы, не общие контексты). Lazy-launch + та же resilience (retry-loop, crash-recovery, recycle по счётчику страниц — но per-provider счётчик). 3. **Per-provider `asyncio.Lock`**: `{provider: Lock}` → ≤1 fetch на поставщика; разные поставщики не блокируют друг друга (параллель). 4. `/fetch` принимает (или инферит из host) provider → берёт его browser+lock+proxy. 5. **Память**: 3 camoufox ≈ 3×~1ГБ. Текущий лимит контейнера 5ГБ (сейчас 1ГБ/5ГБ used). Должно влезть, но мониторить; при нужде поднять лимит в compose. Можно lazy: инстанс поднимается при первом запросе к поставщику. ## Что сделать - `browser/server.py`: рефактор глобального состояния в per-provider (dict browsers/locks/counters), routing по host, per-provider proxy из `BROWSER_PROXY_<PROVIDER>`. - Сохранить контракты `/fetch` `/login` `/health` (health отдаёт per-provider readiness). - Тесты: проверить, что 2 запроса к разным поставщикам идут параллельно, а 2 к одному — сериализованы. - `docker-compose`: при нужде поднять mem limit tradein-browser. ## Усилие L (рефактор браузер-сервиса + тесты + проверка памяти на проде). **Файлы:** `tradein-mvp/browser/server.py`, `docker-compose*.yml`, `tradein-mvp/backend/app/services/scrapers/browser_fetcher.py` (если передавать provider явно). **Cross-ref:** EPIC #883 (миграция fetch на camoufox), #885/#886 (per-source camoufox), #1184 (browser proxy resilience). Анализ: vault `research/Avito_Scraper_Endpoint_Map_0619.md`.
lekss361 added
status/review
and removed
status/wip
labels 2026-06-19 16:01:06 +00:00
Sign in to join this conversation.
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#1793
No description provided.