fix(tradein/scrapers): wire proxy_pool into Avito legacy pipeline, rotate on ban (#2202) #2328

Merged
lekss361 merged 1 commit from fix/2202-proxy-pool-rotate-exclude into main 2026-07-03 21:48:16 +00:00
Owner

Summary

Issue #2202 (P0): proxy_pool.py в шапке прямо писал, что ни один боевой скрейпер не подключён — git grep "acquire(" по scrapers/scrape_pipeline.py давал 0 хитов. Скрейперы читали статический settings.scraper_proxy_url — один mobile-IP на все источники, бан на общем IP выносил avito+cian+yandex+domklik разом.

Подключён pool-aware acquisition/rotation в 3 curl-режим Avito entry points (run_avito_pipeline, run_avito_city_sweep, run_avito_newbuilding_sweep), за флагом use_proxy_pool_curl (default False — ship-dark, нулевое изменение поведения пока явно не включат).

Прошёл 2 раунда review. Раунд 1 нашёл реальный баг (MAJOR): _rotate_pool_proxy мог молча вернуть тот же прокси, который только что забанил — release(current_lease) вызывался ДО acquire(), и один бан не достигает порога исключения (consecutive_fails=1 < MAX_CONSECUTIVE_FAILS=3), так что leased_by IS NULL фильтр не исключал только что освобождённый прокси. Исходный acceptance-тест проходил случайно (id tie-break в фикстуре). Fix: release() перенесён строго ПОСЛЕ acquire() — пока current_lease не освобождён, фильтр гарантированно его исключает независимо от id/last_ok_at. Добавлен зеркальный regression-тест с обратным id-порядком; раунд 2 review независимо воспроизвёл регресс (временно откатил порядок, увидел тест красным, откатил обратно) — подтверждено, не на слово.

Out of scope (задокументировано, не эта задача)

  • Cian/Yandex/DomClick SERP — browser-mode, proxy-выбор в отдельном сервисе (tradein-mvp/browser/server.py), уже имеют per-source dedicated proxy-аккаунты для avito/cian/yandex; DomClick всё ещё падает на общий SCRAPER_PROXY_URL там — отдельный follow-up.
  • run_avito_full_load — использует AvitoScraper напрямую (много вызывающего кода за пределами scrape_pipeline: admin endpoints, backfill) — материально больший blast radius, follow-up при необходимости.
  • Фича полностью выключена по умолчанию — для реальной SPOF-митигации нужен ещё proxy-inventory (наполнение scrape_proxies, сейчас ~5 строк только для health-checker) — ops-задача, не код.

Test plan

  • 11 новых тестов (test_scrape_pipeline_proxy_pool.py) + regression-guard на именно этот MAJOR-баг
  • Full backend suite: 3181 passed, 6 skipped, 1 pre-existing unrelated failure (test_search_api.py::test_search_cache_hit)
  • Pre-push review round 1: MAJOR → fixup → round 2: APPROVE (независимая репродукция регресса и её фикса)
  • post-merge: smoke — фича выключена по флагу, low-risk, но проверить что импорт proxy_pool не ломает обычный запуск скрейпера

Refs #2202

## Summary Issue #2202 (P0): `proxy_pool.py` в шапке прямо писал, что ни один боевой скрейпер не подключён — `git grep "acquire("` по scrapers/scrape_pipeline.py давал 0 хитов. Скрейперы читали статический `settings.scraper_proxy_url` — один mobile-IP на все источники, бан на общем IP выносил avito+cian+yandex+domklik разом. Подключён pool-aware acquisition/rotation в 3 curl-режим Avito entry points (`run_avito_pipeline`, `run_avito_city_sweep`, `run_avito_newbuilding_sweep`), за флагом `use_proxy_pool_curl` (default `False` — ship-dark, нулевое изменение поведения пока явно не включат). Прошёл 2 раунда review. Раунд 1 нашёл реальный баг (MAJOR): `_rotate_pool_proxy` мог молча вернуть тот же прокси, который только что забанил — `release(current_lease)` вызывался ДО `acquire()`, и один бан не достигает порога исключения (`consecutive_fails=1 < MAX_CONSECUTIVE_FAILS=3`), так что `leased_by IS NULL` фильтр не исключал только что освобождённый прокси. Исходный acceptance-тест проходил случайно (id tie-break в фикстуре). Fix: `release()` перенесён строго ПОСЛЕ `acquire()` — пока current_lease не освобождён, фильтр гарантированно его исключает независимо от id/last_ok_at. Добавлен зеркальный regression-тест с обратным id-порядком; раунд 2 review независимо воспроизвёл регресс (временно откатил порядок, увидел тест красным, откатил обратно) — подтверждено, не на слово. ## Out of scope (задокументировано, не эта задача) - Cian/Yandex/DomClick SERP — browser-mode, proxy-выбор в отдельном сервисе (`tradein-mvp/browser/server.py`), уже имеют per-source dedicated proxy-аккаунты для avito/cian/yandex; DomClick всё ещё падает на общий `SCRAPER_PROXY_URL` там — отдельный follow-up. - `run_avito_full_load` — использует `AvitoScraper` напрямую (много вызывающего кода за пределами scrape_pipeline: admin endpoints, backfill) — материально больший blast radius, follow-up при необходимости. - Фича полностью выключена по умолчанию — для реальной SPOF-митигации нужен ещё proxy-inventory (наполнение `scrape_proxies`, сейчас ~5 строк только для health-checker) — ops-задача, не код. ## Test plan - [x] 11 новых тестов (`test_scrape_pipeline_proxy_pool.py`) + regression-guard на именно этот MAJOR-баг - [x] Full backend suite: 3181 passed, 6 skipped, 1 pre-existing unrelated failure (`test_search_api.py::test_search_cache_hit`) - [x] Pre-push review round 1: MAJOR → fixup → round 2: APPROVE (независимая репродукция регресса и её фикса) - [ ] post-merge: smoke — фича выключена по флагу, low-risk, но проверить что импорт `proxy_pool` не ломает обычный запуск скрейпера Refs #2202
lekss361 added 1 commit 2026-07-03 21:45:54 +00:00
fix(tradein/scrapers): wire proxy_pool into Avito legacy pipeline, rotate on ban (#2202)
All checks were successful
CI / changes (pull_request) Successful in 10s
CI Trade-In / changes (pull_request) Successful in 10s
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 1m42s
47880bfab6
Боевой оркестратор (scrape_pipeline.py) читал только статический
settings.scraper_proxy_url — ни один боевой скрейпер не использовал
proxy_pool/scrape_proxies (git grep "acquire(" = 0 хитов). Бан на общем
IP выносил avito+cian+yandex+domklik разом.

Подключён pool-aware proxy acquisition/rotation в 3 curl-режим Avito
entry points (run_avito_pipeline, run_avito_city_sweep,
run_avito_newbuilding_sweep), за флагом use_proxy_pool_curl (default
False — ship-dark, поведение не меняется пока явно не включат):
- _pool_proxy_dict(): health-filtered acquire() lease, fallback на
  статический прокси при пустом пуле/ошибке/флаге off.
- _rotate_pool_proxy(): на бан — mark_health(ok=False) → acquire()
  замены → ТОЛЬКО ПОСЛЕ release(current_lease). Порядок принципиален:
  release ДО acquire() позволял пулу молча вернуть тот же только что
  забаненный прокси (один бан не достигает MAX_CONSECUTIVE_FAILS=3,
  leased_by IS NULL фильтр не исключал бы его) — найдено на pre-push
  review, воспроизведено и закрыто регрессионным тестом с CURRENT id
  ниже GOOD id (оригинальный тест проходил случайно из-за id tie-break).
- run_id прокинут в acquire() на обоих sweep call sites — scrape_proxies
  .leased_by несёт реальный run id для диагностики, не NON_RUN_LEASE_MARKER.

Cian/Yandex/DomClick (browser-mode, отдельный сервис) и
run_avito_full_load (использует AvitoScraper напрямую, много вызывающих
кода за пределами scrape_pipeline) — вне скоупа, задокументировано как
follow-up в PR description.

Refs #2202
lekss361 merged commit 308eeac214 into main 2026-07-03 21:48:16 +00:00
lekss361 deleted branch fix/2202-proxy-pool-rotate-exclude 2026-07-03 21:48:17 +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#2328
No description provided.