[EPIC][tradein][scrapers] Миграция fetch-слоя на camoufox (real browser anti-bot) #883

Closed
opened 2026-05-31 10:36:15 +00:00 by lekss361 · 8 comments
Owner

🔬 Обоснование — всё провалидировано локально 2026-05-31 (camoufox vs curl_cffi через тот же мобильный прокси).

Проблема

Avito (и остальные) отдают soft-block заглушку (HTTP 200, ~375КБ, без данных, __preloadedState__ stub, captcha-маркеры) на флагнутых прокси-IP, потом hard-429. Это JS-анти-бот: curl_cffi JS не исполняет → получает заглушку/бан. Доказано A/B: тот же URL/метод — residential IP даёт реальные 1.42МБ, флагнутый proxy-IP даёт 375КБ stub. curl_cffi банится на ~23 запросах/IP.

Решение

camoufox (Firefox + C++-инжект fingerprint, Playwright-API) исполняет JS-challenge → отдаёт реальные данные через тот же прокси. Заменяем curl_cffi fetch-слой на browser-fetcher. Парсинг (__preloadedState__→selectolax/JSON) НЕ меняется — только «как достать HTML».

Валидация (camoufox+прокси, реальные данные )

SERP пагинация multi-room detail house
avito p5 (50 объяв.)
cian p3 2room
n1 p2
yandex p2
domclick (#796) 40 карт.
  • Rate: avito 9/10 на НУЛЕВОЙ задержке (curl_cffi банился на ~23) → реальный браузер снимает IP-rate-стену. delay 1-2с достаточно (вежливость, не анти-бан).
  • Память: утечки НЕТ (RSS bounded, стабилизация ~800МБ, пик ~1.9ГБ). Нужен ~2.5ГБ headroom контейнеру.
  • ⚠️ Стабильность: интермиттентный TargetClosedError (1/2 длинных прогонов на одном browser) → обязательны recycle каждые ~15-20 стр + relaunch-on-crash.
  • Установка: camoufox[geoip] + python -m camoufox fetch (Firefox-build ~960МБ). Конфиг: os="windows", locale="ru-RU", proxy=..., geoip=True, humanize=True.

Фазы (отдельные issue)

  • Phase 0 — BrowserFetcher abstraction + Docker/deploy (foundation). status/ready.
  • Phase 1 — avito на BrowserFetcher (заменяет curl_cffi + _rotate_ip + firewall-detect). Depends Phase 0.
  • Phase 2 — cian/n1/yandex на BrowserFetcher, снять старые анти-бот костыли (cian DMIR_AUTH cookies + warm-up, yandex captcha). Depends Phase 1.

Trade-offs (принято)

  • Скорость ~5с/страница (vs 0.1с curl_cffi) — приемлемо для ночного sweep.
  • Образ +~1ГБ (Firefox-build), память +~2ГБ. Один браузер на sweep + recycle.
  • Concurrency-guard уже есть (#874) — два sweep'а не запустятся.

Связано

Разблокирует/делает ненужными: #843 (cian SERP dead), баны avito, #562/#726-хвост. Заменяет per-provider анти-бот костыли единым механизмом.

> 🔬 Обоснование — всё провалидировано локально 2026-05-31 (camoufox vs curl_cffi через тот же мобильный прокси). ## Проблема Avito (и остальные) отдают **soft-block заглушку** (HTTP 200, ~375КБ, без данных, `__preloadedState__` stub, captcha-маркеры) на флагнутых прокси-IP, потом hard-429. Это **JS-анти-бот**: `curl_cffi` JS не исполняет → получает заглушку/бан. Доказано A/B: тот же URL/метод — residential IP даёт реальные 1.42МБ, флагнутый proxy-IP даёт 375КБ stub. curl_cffi банится на ~23 запросах/IP. ## Решение **camoufox** (Firefox + C++-инжект fingerprint, Playwright-API) исполняет JS-challenge → отдаёт реальные данные через тот же прокси. Заменяем curl_cffi fetch-слой на browser-fetcher. **Парсинг (`__preloadedState__`→selectolax/JSON) НЕ меняется** — только «как достать HTML». ## Валидация (camoufox+прокси, реальные данные ✅) | | SERP | пагинация | multi-room | detail | house | |---|---|---|---|---|---| | avito | ✅ | ✅ p5 (50 объяв.) | ✅ | ✅ | ✅ | | cian | ✅ | ✅ p3 | ✅ 2room | ✅ | — | | n1 | ✅ | ✅ p2 | — | ✅ | — | | yandex | ✅ | ✅ p2 | — | ✅ | — | | domclick (#796) | ✅ 40 карт. | — | — | — | — | - **Rate:** avito 9/10 на НУЛЕВОЙ задержке (curl_cffi банился на ~23) → реальный браузер снимает IP-rate-стену. delay 1-2с достаточно (вежливость, не анти-бан). - **Память:** утечки НЕТ (RSS bounded, стабилизация ~800МБ, пик ~1.9ГБ). Нужен ~2.5ГБ headroom контейнеру. - **⚠️ Стабильность:** интермиттентный `TargetClosedError` (1/2 длинных прогонов на одном browser) → обязательны **recycle каждые ~15-20 стр + relaunch-on-crash**. - Установка: `camoufox[geoip]` + `python -m camoufox fetch` (Firefox-build ~960МБ). Конфиг: `os="windows", locale="ru-RU", proxy=..., geoip=True, humanize=True`. ## Фазы (отдельные issue) - **Phase 0 — BrowserFetcher abstraction + Docker/deploy** (foundation). `status/ready`. - **Phase 1 — avito на BrowserFetcher** (заменяет curl_cffi + `_rotate_ip` + firewall-detect). Depends Phase 0. - **Phase 2 — cian/n1/yandex на BrowserFetcher**, снять старые анти-бот костыли (cian DMIR_AUTH cookies + warm-up, yandex captcha). Depends Phase 1. ## Trade-offs (принято) - Скорость ~5с/страница (vs 0.1с curl_cffi) — приемлемо для ночного sweep. - Образ +~1ГБ (Firefox-build), память +~2ГБ. Один браузер на sweep + recycle. - Concurrency-guard уже есть (#874) — два sweep'а не запустятся. ## Связано Разблокирует/делает ненужными: #843 (cian SERP dead), баны avito, #562/#726-хвост. Заменяет per-provider анти-бот костыли единым механизмом.
lekss361 added the
scope/backend
scrapers
enhancement
priority/p1
tradein
labels 2026-05-31 10:36:15 +00:00
Author
Owner

Фазы:

  • #884 Phase 0 — BrowserFetcher + Docker (status/ready)
  • #885 Phase 1 — avito (blocked на #884)
  • #886 Phase 2 — cian/n1/yandex (blocked на #885)

Порядок: 884 → 885 → 886. Каждая фаза = отдельный PR, верифицируется на проде до следующей.

Фазы: - **#884** Phase 0 — BrowserFetcher + Docker (`status/ready`) - **#885** Phase 1 — avito (blocked на #884) - **#886** Phase 2 — cian/n1/yandex (blocked на #885) Порядок: 884 → 885 → 886. Каждая фаза = отдельный PR, верифицируется на проде до следующей.
Collaborator

analyst: дубликат #884 (то же — enable ночной scrape_schedules avito/cian/yandex + verify). Закрываю как dup, канонический — #884. Работа не теряется.

analyst: дубликат #884 (то же — enable ночной scrape_schedules avito/cian/yandex + verify). Закрываю как dup, канонический — #884. Работа не теряется.
Collaborator

↑ analyst: реоупен — мой коммент о «дубликате #884» ОШИБОЧЕН. #883 это родительский EPIC (миграция fetch на camoufox), #884 — его Phase 0. Не дубликаты. Прошу прощения за неверное закрытие. EPIC остаётся открытым как зонтик над #884/#885/#886.

↑ analyst: реоупен — мой коммент о «дубликате #884» **ОШИБОЧЕН**. #883 это родительский EPIC (миграция fetch на camoufox), #884 — его Phase 0. Не дубликаты. Прошу прощения за неверное закрытие. EPIC остаётся открытым как зонтик над #884/#885/#886.
Author
Owner

🔬 Rate-тест camoufox (нулевая задержка, свежий IP per provider) — добили cian+yandex:

поставщик результат rate-бан?
avito 9/10 ok НЕТ
cian 10/10 ok (3.4МБ) НЕТ
yandex реальные 4.2МБ когда грузит, но transient + browser-crash НЕТ (нестабильность)

Вывод: camoufox снимает rate-бан на всех 3. Сбои = не баны, а нестабильность (флейки прокси + TargetClosedError). yandex самый тяжёлый (4.2МБ/стр) → крэшится чаще. Подтверждает требования Phase 0 (#884): transient-retry + recycle + relaunch. delay 1-2с достаточно.

🔬 Rate-тест camoufox (нулевая задержка, свежий IP per provider) — добили cian+yandex: | поставщик | результат | rate-бан? | |---|---|---| | avito | 9/10 ok | НЕТ | | cian | **10/10 ok** (3.4МБ) | НЕТ | | yandex | реальные 4.2МБ когда грузит, но transient + browser-crash | НЕТ (нестабильность) | **Вывод:** camoufox снимает rate-бан на всех 3. Сбои = не баны, а нестабильность (флейки прокси + `TargetClosedError`). yandex самый тяжёлый (4.2МБ/стр) → крэшится чаще. Подтверждает требования Phase 0 (#884): transient-retry + recycle + relaunch. delay 1-2с достаточно.
Author
Owner

📌 Решение по IP-ротации (после rate-теста): camoufox не банится → проактивная ротация не нужна.

  • _rotate_ip-петля curl_cffi (rotate на каждый 403/429) → убрать (Phase 1/2).
  • Прокси ОСТАЁТСЯ (один стабильный IP): прод datacenter-IP хардбанится Avito («проблема с IP»), нужен RU-mobile/residential egress. Ротация ≠ прокси.
  • Ротация → дремлющий reactive-fallback: триггерится ТОЛЬКО если BrowserFetcher детектит soft-block/429 (с camoufox редко/никогда). Код не выкидываем, но проактивно не дёргаем.
  • ⚠️ Не довалидировано: ~10 запросов/IP без бана подтверждено; полный sweep ~80 запросов/IP на одном IP до отказа НЕ гоняли. Reactive-fallback страхует.
📌 **Решение по IP-ротации (после rate-теста):** camoufox не банится → **проактивная ротация не нужна**. - `_rotate_ip`-петля curl_cffi (rotate на каждый 403/429) → **убрать** (Phase 1/2). - **Прокси ОСТАЁТСЯ** (один стабильный IP): прод datacenter-IP хардбанится Avito («проблема с IP»), нужен RU-mobile/residential egress. Ротация ≠ прокси. - Ротация → **дремлющий reactive-fallback**: триггерится ТОЛЬКО если BrowserFetcher детектит soft-block/429 (с camoufox редко/никогда). Код не выкидываем, но проактивно не дёргаем. - ⚠️ Не довалидировано: ~10 запросов/IP без бана подтверждено; полный sweep ~80 запросов/IP на одном IP до отказа НЕ гоняли. Reactive-fallback страхует.
Author
Owner

Уточнение по прокси (для воркеров/окон): proxy-инфра УЖЕ ГОТОВА — в скоуп миграции НЕ входит.

Мобильный прокси куплен, настроен на проде и провалидирован (все camoufox-тесты шли через него, реальные данные по всем поставщикам):

  • mobileproxy.space (id 500825, mproxy.site:16928), куплен 2026-05-30;
  • на проде в tradein-mvp/backend/.env.runtime: AVITO_PROXY_URL + AVITO_PROXY_ROTATE_URL (задеплоено #623/#788);
  • креды — в vault meta/00_credentials.md → «Mobile Proxy».

Что это значит для исполнителя:

  • BrowserFetcher (Phase 0 #884) переиспользует существующий settings.scraper_proxy_url — НИЧЕГО нового не покупать/настраивать. _proxy_dict() просто парсит его в формат camoufox {server,username,password}.
  • AVITO_PROXY_ROTATE_URL остаётся, но из «дёргать на каждый бан» → в reactive-fallback (почти не используется, см. решение по ротации выше).

Чистый скоуп миграции = только код (BrowserFetcher + перевод скрейперов на него) + Docker (Firefox-build ~960МБ + headless-libs). Прокси/egress — done.

✅ **Уточнение по прокси (для воркеров/окон): proxy-инфра УЖЕ ГОТОВА — в скоуп миграции НЕ входит.** Мобильный прокси куплен, настроен на проде и провалидирован (все camoufox-тесты шли через него, реальные данные по всем поставщикам): - `mobileproxy.space` (id 500825, `mproxy.site:16928`), куплен 2026-05-30; - на проде в `tradein-mvp/backend/.env.runtime`: `AVITO_PROXY_URL` + `AVITO_PROXY_ROTATE_URL` (задеплоено #623/#788); - креды — в vault `meta/00_credentials.md` → «Mobile Proxy». **Что это значит для исполнителя:** - BrowserFetcher (Phase 0 #884) переиспользует существующий `settings.scraper_proxy_url` — НИЧЕГО нового не покупать/настраивать. `_proxy_dict()` просто парсит его в формат camoufox `{server,username,password}`. - `AVITO_PROXY_ROTATE_URL` остаётся, но из «дёргать на каждый бан» → в **reactive-fallback** (почти не используется, см. решение по ротации выше). **Чистый скоуп миграции = только код (BrowserFetcher + перевод скрейперов на него) + Docker (Firefox-build ~960МБ + headless-libs).** Прокси/egress — done.
Collaborator

Статус 2026-06-26: эпик substantially done. Phase 0 (#884) + Phase 0.5 sidecar (#905) + Phase 1 avito (#885) + /fetch-json & IMV (#915 Stage 2/3) — закрыты; browser-режим дефолт, tradein-browser sidecar live. Phase 2 (#886) cian/yandex — done, остаток: n1 (low-pri, не построен) + намеренно-оставленный curl_cffi за флагом-фолбэком. Coverage-matrix avito/cian/yandex (SERP+pagination+detail+newbuilding) — ✓. Держу open до закрытия #886.

Статус 2026-06-26: эпик **substantially done**. Phase 0 (#884) + Phase 0.5 sidecar (#905) + Phase 1 avito (#885) + /fetch-json & IMV (#915 Stage 2/3) — закрыты; browser-режим дефолт, tradein-browser sidecar live. Phase 2 (#886) cian/yandex — done, остаток: n1 (low-pri, не построен) + намеренно-оставленный curl_cffi за флагом-фолбэком. Coverage-matrix avito/cian/yandex (SERP+pagination+detail+newbuilding) — ✓. Держу open до закрытия #886.
Collaborator

Закрыто — эпик camoufox-миграции выполнен: Phase 0/0.5/1 (avito) + Phase 2 (cian/yandex) на BrowserFetcher, sidecar live, browser-режим дефолт, /fetch-json + IMV (#915). Coverage-matrix avito/cian/yandex (SERP+pagination+detail+newbuilding) — ✓. n1 не строим (#886 закрыт), curl_cffi оставлен за флагом-фолбэком намеренно. Миграция fetch-слоя завершена.

✅ Закрыто — эпик camoufox-миграции выполнен: Phase 0/0.5/1 (avito) + Phase 2 (cian/yandex) на BrowserFetcher, sidecar live, browser-режим дефолт, /fetch-json + IMV (#915). Coverage-matrix avito/cian/yandex (SERP+pagination+detail+newbuilding) — ✓. n1 не строим (#886 закрыт), curl_cffi оставлен за флагом-фолбэком намеренно. Миграция fetch-слоя завершена.
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#883
No description provided.