[tradein][scrapers] Avito прокси-ротация выключена на проде (AVITO_PROXY_MAX_ROTATIONS=0) → avito_full_load банится мгновенно #1790

Closed
opened 2026-06-19 15:36:19 +00:00 by lekss361 · 3 comments
Owner

Проблема

avito_full_load (new-discovery, полный сбор новых объявлений) массово банится: за 24ч 6 banned / 10 cancelled / 1 done. Единственный mobile-proxy IP не ротируется при бане → проход умирает на первой же 403/429.

Доказательства (2026-06-19)

  • Прод-env контейнера tradein-scraper: AVITO_PROXY_MAX_ROTATIONS=0 (при этом AVITO_PROXY_ROTATE_URL задан — changeip mobileproxy).
  • avito.py:327,346: max_rot = settings.avito_proxy_max_rotations if rotate_url else 0 → при =0 условие rot_done < max_rot (0 < 0) = False → мгновенный abort на бане без смены IP.
  • Живой пробинг через avito-прокси: IP 188.162.64.231HTTP 429 (сожжён).
  • Дефолт в коде config.py:211 = 2; runbook avito_mobile_proxy.md документирует ротацию как штатный путь восстановления после бана. Значит прод-=0 — забытый misconfig.
  • Контроль: на чистом резидентном IP Avito отдаёт всё (60 карточек выдачи, без капчи/firewall) → блок чисто по IP прокси, не по коду/фингерпринту.

Что сделать

  1. В прод .env.runtime (tradein-scraper) выставить AVITO_PROXY_MAX_ROTATIONS=2 (или больше), перезапустить scraper.
  2. Опционально: притормозить пейсинг avito_full_load (ловит 429 уже на page=1-2 — слишком агрессивно для одного IP) + рассмотреть пул IP вместо одного мобильного.

Усилие

XS (1 строка env + рестарт контейнера).

Cross-ref: #883/#885 (миграция на camoufox — снизит срабатывания fl-gpib), #1184 (tradein-browser camoufox proxy), runbook avito_mobile_proxy.md. Анализ: vault research/Avito_Scraper_Endpoint_Map_0619.md.

## Проблема `avito_full_load` (new-discovery, полный сбор новых объявлений) массово банится: за 24ч **6 banned / 10 cancelled / 1 done**. Единственный mobile-proxy IP не ротируется при бане → проход умирает на первой же 403/429. ## Доказательства (2026-06-19) - Прод-env контейнера `tradein-scraper`: **`AVITO_PROXY_MAX_ROTATIONS=0`** (при этом `AVITO_PROXY_ROTATE_URL` задан — changeip mobileproxy). - `avito.py:327,346`: `max_rot = settings.avito_proxy_max_rotations if rotate_url else 0` → при `=0` условие `rot_done < max_rot` (`0 < 0`) = False → мгновенный abort на бане без смены IP. - Живой пробинг через avito-прокси: IP `188.162.64.231` → **HTTP 429** (сожжён). - Дефолт в коде `config.py:211` = `2`; runbook `avito_mobile_proxy.md` документирует ротацию как штатный путь восстановления после бана. Значит прод-`=0` — забытый misconfig. - Контроль: на чистом резидентном IP Avito отдаёт всё (60 карточек выдачи, без капчи/firewall) → блок чисто по IP прокси, не по коду/фингерпринту. ## Что сделать 1. В прод `.env.runtime` (`tradein-scraper`) выставить `AVITO_PROXY_MAX_ROTATIONS=2` (или больше), перезапустить scraper. 2. Опционально: притормозить пейсинг `avito_full_load` (ловит 429 уже на page=1-2 — слишком агрессивно для одного IP) + рассмотреть пул IP вместо одного мобильного. ## Усилие XS (1 строка env + рестарт контейнера). **Cross-ref:** #883/#885 (миграция на camoufox — снизит срабатывания fl-gpib), #1184 (tradein-browser camoufox proxy), runbook `avito_mobile_proxy.md`. Анализ: vault `research/Avito_Scraper_Endpoint_Map_0619.md`.
lekss361 added the
bug
priority/p1
scope/backend
scope/devops
scrapers
status/ready
tradein
labels 2026-06-19 15:36:47 +00:00
Author
Owner

⚠️ Поправка от владельца (2026-06-19): ротацию IP делает сам прокси-провайдер (авто), а не наш код. Значит AVITO_PROXY_MAX_ROTATIONS=0 стоит намеренно — code-side changeip-ротацию включать НЕ надо (была бы дубль провайдерской + жгла бы квоту).

Переформулировка задачи:

  1. НЕ выставлять MAX_ROTATIONS=2 (исходная рекомендация снята).
  2. Реальный вопрос: почему провайдерская авто-ротация не даёт свежий IP — probe поймал стабильный 188.162.64.231 → 429. Проверить интервал/режим авто-ротации mobileproxy (per-request / по таймеру), не залип ли один IP.
  3. Стратегическое направление — миграция fetch на browser-scrape (camoufox) с per-provider concurrency ≤1 + параллелизм между поставщиками (#883). Это снижает fingerprint-детект и нагрузку на один IP.

Снимаю status/ready — это уже не 1-строчный env-фикс.

⚠️ Поправка от владельца (2026-06-19): **ротацию IP делает сам прокси-провайдер (авто), а не наш код**. Значит `AVITO_PROXY_MAX_ROTATIONS=0` стоит **намеренно** — code-side changeip-ротацию включать НЕ надо (была бы дубль провайдерской + жгла бы квоту). Переформулировка задачи: 1. НЕ выставлять `MAX_ROTATIONS=2` (исходная рекомендация снята). 2. Реальный вопрос: почему провайдерская авто-ротация не даёт свежий IP — probe поймал стабильный `188.162.64.231` → 429. Проверить интервал/режим авто-ротации mobileproxy (per-request / по таймеру), не залип ли один IP. 3. Стратегическое направление — миграция fetch на **browser-scrape (camoufox)** с per-provider concurrency ≤1 + параллелизм между поставщиками (#883). Это снижает fingerprint-детект и нагрузку на один IP. Снимаю `status/ready` — это уже не 1-строчный env-фикс.
lekss361 removed the
status/ready
label 2026-06-19 15:45:30 +00:00
Collaborator

Верификация (2026-06-25)

Часть 1 (прокси-ротация) — исправлена и подтверждена на проде.

Прод-env обоих контейнеров теперь содержит штатное значение:

tradein-scraper:  AVITO_PROXY_MAX_ROTATIONS=4
tradein-backend:  AVITO_PROXY_MAX_ROTATIONS=4

(дефолт кода — 4 после #1731, прод-=0 был misconfig.)

Бан-шторм прекратился. scrape_runs (source=avito_full_load):

дата banned прочее
2026-06-18 (окно из issue) 6 + 10 cancelled + 3 failed + 2 zombie
2026-06-19 … 06-25 0 см. ниже

С момента флипа env ни одного banned у avito_full_load. Ротация работает.

Новый доминирующий отказ — НЕ прокси-бан (browser-sidecar 500)

avito_full_load теперь падает в failed иначе: tradein-browser:3000/fetch отдаёт HTTP 500, а _fetch_serp_html_browser (avito.py:442) не ловит HTTPStatusError от sidecar — он обрабатывает только rendered firewall-HTML. Исключение всплывает в generic except Exceptionmark_failed + re-raise (scrape_pipeline.py:2860).

Доказательство — run #356 (06-24): unique_fetched=3207, saved_inserted=466, saved_updated=2720, затем error='Server error 500 ... http://tradein-browser:3000/fetch' → run помечен failed, хотя 3186 листингов уже сохранены через инкрементальный on_bucket. Один транзиентный 500 в конце и нукает статус, и обрывает остаток bucket-walk.

Живые 500 в логах sidecar за 24ч — преимущественно Cian: NS_ERROR_PROXY_TOO_MANY_REQUESTS и Timeout 60000ms (единый мобильный IP перегружен параллельным cian+avito+yandex browser-трафиком). tradein-browser был OOMKilled (лимит 5G, сейчас 2.1G/42%).

Корень: sidecar-ошибки не классифицируются. NS_ERROR_PROXY_TOO_MANY_REQUESTS семантически = soft rate-limit (должен идти в AvitoRateLimitedError → graceful mark_banned + ротация), а не в mark_failed.

План follow-up (отдельный PR fix/avito-full-load-browser-500)

  1. _fetch_serp_html_browser: ловить HTTPStatusError от BrowserFetcher, читать тело {"error": ...}, классифицировать:
    • proxy-too-many-requests / 503 unavailable → ротация IP (уже рядом в while/max_rot), при исчерпании → AvitoRateLimitedError (graceful, partial сохранён).
    • транзиентный (timeout/crash) → bounded-retry с backoff, при исчерпании → soft-fail (mark_banned partial), НЕ mark_failed.
  2. Не классифицировать blindly «continue» — это добьёт rate-limited прокси.
  3. Тесты в test_avito_browser_mode.py (proxy-too-many → ротация → AvitoRateLimitedError; transient → bounded retry; 500-then-success-after-rotation → HTML).

Часть 1 закрыта; оставляю issue открытой под browser-resilience follow-up до merge PR выше.

## Верификация (2026-06-25) **Часть 1 (прокси-ротация) — исправлена и подтверждена на проде.** Прод-env обоих контейнеров теперь содержит штатное значение: ``` tradein-scraper: AVITO_PROXY_MAX_ROTATIONS=4 tradein-backend: AVITO_PROXY_MAX_ROTATIONS=4 ``` (дефолт кода — `4` после #1731, прод-`=0` был misconfig.) Бан-шторм прекратился. `scrape_runs` (source=`avito_full_load`): | дата | banned | прочее | |---|---|---| | **2026-06-18** (окно из issue) | **6** + 10 cancelled + 3 failed + 2 zombie | — | | 2026-06-19 … 06-25 | **0** | см. ниже | С момента флипа env **ни одного `banned`** у `avito_full_load`. Ротация работает. ## Новый доминирующий отказ — НЕ прокси-бан (browser-sidecar 500) `avito_full_load` теперь падает в `failed` иначе: `tradein-browser:3000/fetch` отдаёт **HTTP 500**, а `_fetch_serp_html_browser` (avito.py:442) **не ловит** `HTTPStatusError` от sidecar — он обрабатывает только rendered firewall-HTML. Исключение всплывает в generic `except Exception` → `mark_failed` + re-raise (scrape_pipeline.py:2860). Доказательство — run #356 (06-24): `unique_fetched=3207, saved_inserted=466, saved_updated=2720`, затем `error='Server error 500 ... http://tradein-browser:3000/fetch'` → run помечен `failed`, **хотя 3186 листингов уже сохранены** через инкрементальный `on_bucket`. Один транзиентный 500 в конце и нукает статус, и обрывает остаток bucket-walk. Живые 500 в логах sidecar за 24ч — преимущественно **Cian**: `NS_ERROR_PROXY_TOO_MANY_REQUESTS` и `Timeout 60000ms` (единый мобильный IP перегружен параллельным cian+avito+yandex browser-трафиком). `tradein-browser` был `OOMKilled` (лимит 5G, сейчас 2.1G/42%). **Корень:** sidecar-ошибки не классифицируются. `NS_ERROR_PROXY_TOO_MANY_REQUESTS` семантически = soft rate-limit (должен идти в `AvitoRateLimitedError` → graceful `mark_banned` + ротация), а не в `mark_failed`. ## План follow-up (отдельный PR `fix/avito-full-load-browser-500`) 1. `_fetch_serp_html_browser`: ловить `HTTPStatusError` от `BrowserFetcher`, читать тело `{"error": ...}`, классифицировать: - proxy-too-many-requests / 503 unavailable → ротация IP (уже рядом в `while`/`max_rot`), при исчерпании → `AvitoRateLimitedError` (graceful, partial сохранён). - транзиентный (timeout/crash) → bounded-retry с backoff, при исчерпании → soft-fail (`mark_banned` partial), НЕ `mark_failed`. 2. Не классифицировать blindly «continue» — это добьёт rate-limited прокси. 3. Тесты в `test_avito_browser_mode.py` (proxy-too-many → ротация → AvitoRateLimitedError; transient → bounded retry; 500-then-success-after-rotation → HTML). Часть 1 закрыта; оставляю issue открытой под browser-resilience follow-up до merge PR выше.
Collaborator

Закрыто — обе части решены и подтверждены на проде.

Ч.1 (прокси-ротация): AVITO_PROXY_MAX_ROTATIONS=0→4 на проде (дефолт кода после #1731). Бан-шторм 2026-06-18 (6 banned) → ноль банов с тех пор.

Ч.2 (новый доминирующий отказ — sidecar 500): PR #1896_fetch_serp_html_browser классифицирует HTTP 500/503 от tradein-browser (NS_ERROR_PROXY_TOO_MANY_REQUESTS / timeout) → graceful AvitoRateLimitedError/mark_banned вместо mark_failed. Подтверждено живым: ночные прогоны 25→26 (avito_full_load done 3323, newbuilding_sweep done) — без failed/banned.

✅ **Закрыто — обе части решены и подтверждены на проде.** Ч.1 (прокси-ротация): `AVITO_PROXY_MAX_ROTATIONS=0→4` на проде (дефолт кода после #1731). Бан-шторм 2026-06-18 (6 banned) → **ноль банов** с тех пор. Ч.2 (новый доминирующий отказ — sidecar 500): PR #1896 — `_fetch_serp_html_browser` классифицирует HTTP 500/503 от tradein-browser (NS_ERROR_PROXY_TOO_MANY_REQUESTS / timeout) → graceful `AvitoRateLimitedError`/`mark_banned` вместо `mark_failed`. Подтверждено живым: ночные прогоны 25→26 (`avito_full_load` done 3323, `newbuilding_sweep` done) — без `failed`/`banned`.
Sign in to join this conversation.
No milestone
No project
No assignees
2 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#1790
No description provided.