feat(tradein): avito curl_cffi 429 short-retry — enables backconnect curl SERP (×50 faster than browser) #1762

Merged
lekss361 merged 1 commit from feat/avito-curl-429-retry into main 2026-06-18 16:26:29 +00:00
Owner

Прорыв

avito SERP можно тянуть curl_cffi (chrome-impersonation) через backconnect rotating mobile-прокси БЕЗ браузера — провалидировано на проде:

  • 126 запросов, 5-параллельно: 0 DataDome-блоков (ротация mobile-IP держит), парсер _parse_html корректно вытащил 36 лотов из curl-HTML (source_id/price/rooms/floor/address/segment/url).
  • Скорость: curl ~1-2с/стр (5× парал → ~0.3с/стр) против браузера 25-90с/стр = ×50.

avito.py УЖЕ поддерживает curl-режим (settings.scraper_fetch_mode="curl_cffi") — прод был форсирован в browser (#901, когда curl банился на datacenter-IP). Backconnect (rotating mobile + curl_cffi chrome-TLS) снимает бан.

Проблема, которую чинит этот PR

Backconnect имеет лимит 5 одновременных соединений → при параллельной пагинации отдаёт 429 (transient — проходит на коротком retry). Текущий код трактует 429 как ban: _rotate_ip() (9с!) или raise AvitoRateLimitedError → весь прогон mark_banned. Объёмный тест: 8% запросов ловили 429 от лимита соединений.

Решение

В curl-пути _fetch_serp_html429 short-retry ОТДЕЛЬНО от 403/firewall:

  • sc == 429 → до _AVITO_429_MAX_RETRIES=4 коротких retry (sleep _AVITO_429_BACKOFF_SEC=1.5с + jitter, re-GET), БЕЗ ротации. Backconnect conn-limit = transient.
  • Только если 429 не прошёл после 4× → старый fallback (rotation / AvitoRateLimitedError).
  • 403 / firewall (реальный IP-ban) — поведение БЕЗ изменений (rotation → AvitoBlockedError).
  • Счётчики transient_left/rot_done/r429 независимы и ограничены (нет вечного цикла). Browser-режим/_parse_html/seed-grid/proxy не тронуты.

Изменения

  • scrapers/avito.py (+22) — константы _AVITO_429_MAX_RETRIES/_AVITO_429_BACKOFF_SEC + 429-retry ветка в curl while-цикле.
  • tests/scrapers/test_avito_anti_bot.py (+136, 5 тестов) — 429×2→200 без rotate; 429-persistent→AvitoRateLimitedError после 4×; 429+rotate-url→rotation; 403→немедленный AvitoBlockedError (не через 429-путь).

Тесты

Full suite: 2004 passed, 1 skipped, 0 failed.

Деплой-конфиг (применяю после merge, .env.runtime — изолировано от cian/yandex)

  • SCRAPER_FETCH_MODE=curl_cffi (avito → curl; cian/yandex не зависят от него).
  • SCRAPER_PROXY_URL=<backconnect http endpoint> (avito curl egress; cian=CIAN_PROXY_URL/kf, yandex=YANDEX_PROXY_URL/ti — НЕ тронуты).
  • avito_full_load concurrency=4 (headroom под 5-конн backconnect).
  • BROWSER_PROXY_AVITO — остаётся (browser для avito detail/newbuilding путей).

Прод-смоук после merge+deploy

Запустить avito_full_load (curl-режим) → полный свип: измерить покрытие (active вторичка) + темп (ожидается минуты vs часы) + watch 429/DataDome.

## Прорыв avito SERP можно тянуть **curl_cffi (chrome-impersonation) через backconnect rotating mobile-прокси БЕЗ браузера** — провалидировано на проде: - **126 запросов, 5-параллельно: 0 DataDome-блоков** (ротация mobile-IP держит), парсер `_parse_html` корректно вытащил 36 лотов из curl-HTML (source_id/price/rooms/floor/address/segment/url). - Скорость: **curl ~1-2с/стр (5× парал → ~0.3с/стр)** против браузера 25-90с/стр = **×50**. avito.py УЖЕ поддерживает curl-режим (`settings.scraper_fetch_mode="curl_cffi"`) — прод был форсирован в `browser` (#901, когда curl банился на datacenter-IP). Backconnect (rotating mobile + curl_cffi chrome-TLS) снимает бан. ## Проблема, которую чинит этот PR Backconnect имеет лимит **5 одновременных соединений** → при параллельной пагинации отдаёт **429 (transient — проходит на коротком retry)**. Текущий код трактует 429 как ban: `_rotate_ip()` (9с!) или `raise AvitoRateLimitedError` → весь прогон mark_banned. Объёмный тест: 8% запросов ловили 429 от лимита соединений. ## Решение В curl-пути `_fetch_serp_html` — **429 short-retry ОТДЕЛЬНО от 403/firewall**: - `sc == 429` → до `_AVITO_429_MAX_RETRIES=4` коротких retry (`sleep _AVITO_429_BACKOFF_SEC=1.5с` + jitter, re-GET), БЕЗ ротации. Backconnect conn-limit = transient. - Только если 429 не прошёл после 4× → старый fallback (rotation / `AvitoRateLimitedError`). - **403 / firewall (реальный IP-ban) — поведение БЕЗ изменений** (rotation → `AvitoBlockedError`). - Счётчики `transient_left`/`rot_done`/`r429` независимы и ограничены (нет вечного цикла). Browser-режим/`_parse_html`/seed-grid/proxy не тронуты. ## Изменения - `scrapers/avito.py` (+22) — константы `_AVITO_429_MAX_RETRIES`/`_AVITO_429_BACKOFF_SEC` + 429-retry ветка в curl while-цикле. - `tests/scrapers/test_avito_anti_bot.py` (+136, 5 тестов) — 429×2→200 без rotate; 429-persistent→AvitoRateLimitedError после 4×; 429+rotate-url→rotation; 403→немедленный AvitoBlockedError (не через 429-путь). ## Тесты Full suite: **2004 passed, 1 skipped, 0 failed**. ## Деплой-конфиг (применяю после merge, .env.runtime — изолировано от cian/yandex) - `SCRAPER_FETCH_MODE=curl_cffi` (avito → curl; cian/yandex не зависят от него). - `SCRAPER_PROXY_URL=<backconnect http endpoint>` (avito curl egress; cian=`CIAN_PROXY_URL`/kf, yandex=`YANDEX_PROXY_URL`/ti — НЕ тронуты). - avito_full_load `concurrency=4` (headroom под 5-конн backconnect). - `BROWSER_PROXY_AVITO` — остаётся (browser для avito detail/newbuilding путей). ## Прод-смоук после merge+deploy Запустить avito_full_load (curl-режим) → полный свип: измерить покрытие (active вторичка) + темп (ожидается минуты vs часы) + watch 429/DataDome.
lekss361 added 1 commit 2026-06-18 16:25:20 +00:00
feat(tradein): avito curl_cffi 429 short-retry (backconnect conn-limit transient, no rotate/ban)
All checks were successful
CI / changes (pull_request) Successful in 6s
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
2c6d0e4c71
lekss361 added the
enhancement
priority/p1
scope/backend
scrapers
status/review
labels 2026-06-18 16:26:01 +00:00
lekss361 merged commit f82ee32f50 into main 2026-06-18 16:26:29 +00:00
lekss361 deleted branch feat/avito-curl-429-retry 2026-06-18 16:26:29 +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#1762
No description provided.