fix(tradein/cian): cian_history_backfill идёт через пул прокси + стоп на пустом пуле (#3197, точка 1 из 3) #3382

Merged
bot-backend merged 2 commits from fix/3197-cian-history-backfill-proxy-pool into main 2026-09-05 22:13:09 +00:00
Collaborator

Часть #3197 (шаг A2, часть 1, ПЕРВАЯ точка; раскатка по одной точке в сутки — yandex/newbuilding.py и admin.py не тронуты). Не закрывает issue.

Дефект. app/tasks/cian_history_backfill.py конструировал BrowserFetcher(source="cian", endpoint=…) без proxy_provider/use_pool/environment → мимо пула из 4 узлов через статический SCRAPER_PROXY_URL, и прод-отказ #2616 («пул пуст → не ходить на env/direct») на этом пути был мёртв: признак environment до конструктора не доезжал.

Фикс.

  • cian_history_backfill.py:217-224 — по образцу domclick_detail_backfill/house_imv_backfill: RealScraperConfig() + proxy_provider=RealProxyProvider(), use_pool=_cfg.use_proxy_pool_browser, environment=_cfg.environment. Фабрика build_browser_fetcher намеренно не взята: тесты (test_2725_heartbeat_in_batch.py:72, парити) патчат cian_history_backfill.BrowserFetcher; с фабрикой патч-поинт исчез бы и heartbeat-тест ушёл бы в сеть.
  • :84, :240-252, :310, :401 — «пул пуст» по цепочке __cause__/__context__ (scraper_kit/proxy_errors.py:68 caused_by_no_proxy) → no_proxy_stop, break, блоки домов/оценок пропускаются (тот же пул).
  • app/services/scheduler.py:164-181 (_execute_cian_backfill): counters.no_proxy_stop=1 + mark_failed("пул прокси пуст — к площадке не ходили (#3197)") и ранний return ДО ветки mark_banned — отказ нашей стороны не должен уезжать в banned (контракт #3367 для avito).
  • У avito (#3288) и domclick (#3283) остались приватные копии _caused_by_empty_pool — схлопывание в общий хелпер отдельной правкой (их тесты ссылаются на приватные имена).

Часть 2 issue (тёплый контекст переживает смену узла) для этой точки не нужна — проверено: browser/server.py:1118-1128 при смене proxy-url делает _close_browser(provider), который первой строкой _close_reusable_context(provider); плюс reuse_context на этом пути не включён (дефолт False).

Тесты: tests/test_3197_cian_history_proxy_pool_wiring.py (kwargs фетчера через захват патча класса; NoProxyAvailableError на первой строке → обработана 1 из 3, no_proxy_stop), обновлён точный assert captured == {...} в парити-тесте.

Фальсификация (git apply -R исходников, тесты оставлены): assert None is not None (в kwargs нет proxy_provider — ровно старая пара {'source','endpoint'}), assert 3 == 1 (батч прогонял все 3 строки при пустом пуле). Восстановлено — зелёное.

Прогоны: полный backend 5479 passed, 35 skipped (rc=0); ruff OK.

Приёмка на проде (маркер отсутствует в старом состоянии): в логе сайдкара docker logs tradein-browser | grep 'tradein-browser\[cian\]'прокси изменился (override=True) — relaunch перед fetch в окне первого cian_history_backfill после деплоя — override=True печатается только когда в теле /fetch пришёл proxy, до правки Циан его не слал. Отрицательный контроль: до деплоя строки нет. Дополнительно: прогон при пустом пуле обязан быть status='failed' + counters.no_proxy_stop=1, не banned.

Часть #3197 (шаг A2, часть 1, ПЕРВАЯ точка; раскатка по одной точке в сутки — `yandex/newbuilding.py` и `admin.py` не тронуты). Не закрывает issue. **Дефект.** `app/tasks/cian_history_backfill.py` конструировал `BrowserFetcher(source="cian", endpoint=…)` без `proxy_provider`/`use_pool`/`environment` → мимо пула из 4 узлов через статический `SCRAPER_PROXY_URL`, и прод-отказ #2616 («пул пуст → не ходить на env/direct») на этом пути был мёртв: признак `environment` до конструктора не доезжал. **Фикс.** - `cian_history_backfill.py:217-224` — по образцу `domclick_detail_backfill`/`house_imv_backfill`: `RealScraperConfig()` + `proxy_provider=RealProxyProvider(), use_pool=_cfg.use_proxy_pool_browser, environment=_cfg.environment`. Фабрика `build_browser_fetcher` намеренно не взята: тесты (`test_2725_heartbeat_in_batch.py:72`, парити) патчат `cian_history_backfill.BrowserFetcher`; с фабрикой патч-поинт исчез бы и heartbeat-тест ушёл бы в сеть. - `:84, :240-252, :310, :401` — «пул пуст» по цепочке `__cause__/__context__` (`scraper_kit/proxy_errors.py:68 caused_by_no_proxy`) → `no_proxy_stop`, `break`, блоки домов/оценок пропускаются (тот же пул). - `app/services/scheduler.py:164-181` (`_execute_cian_backfill`): `counters.no_proxy_stop=1` + `mark_failed("пул прокси пуст — к площадке не ходили (#3197)")` и ранний `return` ДО ветки `mark_banned` — отказ нашей стороны не должен уезжать в `banned` (контракт #3367 для avito). - У avito (#3288) и domclick (#3283) остались приватные копии `_caused_by_empty_pool` — схлопывание в общий хелпер отдельной правкой (их тесты ссылаются на приватные имена). **Часть 2 issue (тёплый контекст переживает смену узла) для этой точки не нужна — проверено:** `browser/server.py:1118-1128` при смене proxy-url делает `_close_browser(provider)`, который первой строкой `_close_reusable_context(provider)`; плюс `reuse_context` на этом пути не включён (дефолт False). **Тесты:** `tests/test_3197_cian_history_proxy_pool_wiring.py` (kwargs фетчера через захват патча класса; `NoProxyAvailableError` на первой строке → обработана 1 из 3, `no_proxy_stop`), обновлён точный `assert captured == {...}` в парити-тесте. **Фальсификация** (`git apply -R` исходников, тесты оставлены): `assert None is not None` (в kwargs нет `proxy_provider` — ровно старая пара `{'source','endpoint'}`), `assert 3 == 1` (батч прогонял все 3 строки при пустом пуле). Восстановлено — зелёное. **Прогоны:** полный backend `5479 passed, 35 skipped` (rc=0); ruff OK. **Приёмка на проде (маркер отсутствует в старом состоянии):** в логе **сайдкара** `docker logs tradein-browser | grep 'tradein-browser\[cian\]'` → `прокси изменился (override=True) — relaunch перед fetch` в окне первого `cian_history_backfill` после деплоя — `override=True` печатается только когда в теле `/fetch` пришёл `proxy`, до правки Циан его не слал. Отрицательный контроль: до деплоя строки нет. Дополнительно: прогон при пустом пуле обязан быть `status='failed'` + `counters.no_proxy_stop=1`, не `banned`.
bot-backend added 1 commit 2026-09-05 21:30:02 +00:00
fix(tradein/cian): суточный бэкфилл истории — в прокси-пул, а не в env-прокси (#3197)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 10s
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 4m51s
8129e21b95
BrowserFetcher(source="cian") в cian_history_backfill конструировался без
proxy_provider/use_pool/environment — трёх аргументов, которые кладут "proxy" в тело
POST /fetch. Сайдкар брал свой env-прокси (SCRAPER_PROXY_URL): пул из 4 узлов, его
баны и ротация проходили мимо, а прод-отказ «пул пуст → не ходить на env/direct»
(#2616) на этом пути был мёртв, потому что смотрит на environment. Проводка теперь
как у соседей — domclick_detail_backfill и house_imv_backfill.

Ожившему отказу нужен обработчик: NoProxyAvailableError ловился общим except на
объявление, и батч крутил впустую весь список (пул пуст с первого — значит пуст и на
1000-м). Распознаём по цепочке причин, обрываем прогон, counters.no_proxy_stop=1 и
mark_failed вместо mark_banned — отказ нашей стороны не должен записываться как бан
Циана. Дома и оценки после стопа пропускаем: они идут через тот же пул.

caused_by_no_proxy вынесен в scraper_kit.proxy_errors (у avito #3288 и domclick #3283
живут приватные копии — их схлопывание отдельной правкой).
Author
Collaborator

Code-review (06.09): критикал, проверен рабочим трактом — стоп на пустом пуле в cian_history_backfill не срабатывает: браузерная ветка scraper_kit/providers/cian/detail.py:129-133 глотает любое исключение и возвращает None, поэтому caused_by_no_proxy в бэкфилле не вызывается; настоящий backfill_cian_history на 5 строках с фетчером, поднимающим NoProxyAvailableError, прошёл все 5, no_proxy_stop=False, scheduler → mark_done, run_brought_data("done") → True (прогон, не ходивший к площадке, обновил свежесть). Тест test_empty_pool_stops_the_batch_on_first_listing подменял моком сам fetch_detail — зелёный по построению.

Доработка в этой же ветке: if caused_by_no_proxy(exc): raise на общей функции (cian/detail.py:131, закрывает и ожидание cian_price_history.py:142), тест через настоящий fetch_detail с подделкой на уровне BrowserFetcher.fetch() — обязан краснеть на 8129e21b и зеленеть после.

Проводка пула и caused_by_no_proxy ревью подтвердило: конфиг читается, а не зашит (мутация use_proxy_pool_browser=True валит [False-dev]); ProxyPoolExhaustedError под хелпер не попадает; ранний return через mark_failed пишет надмножество полей mark_done.

Отдельно заведён #3384 — «пул пуст на старте батча» (_acquire_lease в __aenter__) уходит мимо no_proxy_stop у cian и domclick, scheduler пишет failed со стухшими counters; общая дыра, не регрессия ветки.

Code-review (06.09): ❌ **критикал, проверен рабочим трактом** — стоп на пустом пуле в `cian_history_backfill` не срабатывает: браузерная ветка `scraper_kit/providers/cian/detail.py:129-133` глотает любое исключение и возвращает `None`, поэтому `caused_by_no_proxy` в бэкфилле не вызывается; настоящий `backfill_cian_history` на 5 строках с фетчером, поднимающим `NoProxyAvailableError`, прошёл все 5, `no_proxy_stop=False`, scheduler → `mark_done`, `run_brought_data("done") → True` (прогон, не ходивший к площадке, обновил свежесть). Тест `test_empty_pool_stops_the_batch_on_first_listing` подменял моком сам `fetch_detail` — зелёный по построению. Доработка в этой же ветке: `if caused_by_no_proxy(exc): raise` на общей функции (`cian/detail.py:131`, закрывает и ожидание `cian_price_history.py:142`), тест через настоящий `fetch_detail` с подделкой на уровне `BrowserFetcher.fetch()` — обязан краснеть на 8129e21b и зеленеть после. Проводка пула и `caused_by_no_proxy` ревью подтвердило: конфиг читается, а не зашит (мутация `use_proxy_pool_browser=True` валит `[False-dev]`); `ProxyPoolExhaustedError` под хелпер не попадает; ранний `return` через `mark_failed` пишет надмножество полей `mark_done`. Отдельно заведён #3384 — «пул пуст на старте батча» (`_acquire_lease` в `__aenter__`) уходит мимо `no_proxy_stop` у cian и domclick, scheduler пишет `failed` со стухшими counters; общая дыра, не регрессия ветки.
Light1YT added 1 commit 2026-09-05 22:06:51 +00:00
fix(#3197): пустой пул пробрасывается из cian fetch_detail; тест через настоящий fetch_detail
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 10s
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 4m58s
0c21e522c0
bot-backend merged commit b21b1a928e into main 2026-09-05 22:13:09 +00:00
Sign in to join this conversation.
No reviewers
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#3382
No description provided.