feat(tradein/avito): ротация exit-IP по счётчику попыток в доборе карточек #3302

Merged
lekss361 merged 2 commits from feat/avito-rotate-by-attempt-counter into main 2026-08-31 12:09:04 +00:00

2 commits

Author SHA1 Message Date
bot-backend
8cf31aba04 test(tradein/avito): дефолт порога ротации в _fake_settings warm-context тестов
All checks were successful
CI Trade-In / changes (pull_request) Successful in 10s
CI / changes (pull_request) Successful in 12s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
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
CI Trade-In / backend-tests (pull_request) Successful in 5m15s
CI поймал то, что не поймал локальный прогон tests/tasks: восемь тестов в
tests/test_3251_avito_warm_context.py держат собственную копию _fake_settings,
без нового avito_detail_backfill_rotate_after_attempts. Bare MagicMock отдаёт
на него дочерний мок, и сравнение attempts_since_rotation >= settings....
падает TypeError: '>=' not supported between 'int' and 'MagicMock'.

Ловушка в проекте известна — в этом же словаре уже лежат явные дефолты
detail_backfill_block_ratio_window/_threshold ровно по той же причине.
Добавлен реальный дефолт из config.py и комментарий, почему он тут нужен.
2026-08-31 14:58:11 +03:00
bot-backend
ef99ab2ea1 feat(tradein/avito): ротация exit-IP по счётчику попыток в доборе карточек
Some checks failed
CI Trade-In / changes (pull_request) Successful in 10s
CI / changes (pull_request) Successful in 12s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
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
CI Trade-In / backend-tests (pull_request) Failing after 5m22s
Один IP выдерживает ограниченное число карточек Авито, дальше выход падает до
нуля. Замер на 39 карточках: билайновский порт даёт 13/39, четверти
100/44/0/0, 24 бан-страницы, и после 18-20 карточки не восстанавливается.
Тот же порт со сменой IP каждые 15 карточек — 35/45, четверти 91/64/73/83:
обвал исчезает совсем. До этого пул не ротировал ни разу за всю историю.

Счётчик считает ПОПЫТКИ, а не успехи: неудачная карточка тратит бюджет
адреса так же, как удачная. По достижении порога зовём rotate_proxy (#3298),
ждём reconnect_delay_s и пересоздаём контекст через request_context_reset —
свежий адрес со старыми куками бесполезен.

Ожидание не косметическое: сразу после смены IP канал 2-12 секунд нерабочий,
в замере две карточки подряд ушли в таймаут, ещё одна получила 439. Без
паузы каждая ротация дарила бы два ложных блока, а 14 из 20 прерывают прогон.

Порог вынесен в настройку AVITO_DETAIL_BACKFILL_ROTATE_AFTER_ATTEMPTS: он
зависит от оператора. Билайну нужно ~15, настоящим мобильным операторам
хватает ~30 (МегаФон в том же замере дал 27/39 вообще без ротации).

Счётчик обнуляется ДО вызова, иначе исчерпанный лимит провайдера заставил бы
дёргать ротацию на каждой следующей попытке. Отказ ротации не роняет прогон и
не считается блокировкой площадки.
2026-08-31 14:37:58 +03:00