fix(tradein/scraper): подключить браузерный путь Авито к пулу прокси #2637

Merged
lekss361 merged 1 commit from fix/tradein-avito-browser-proxy-pool into main 2026-08-02 14:37:40 +00:00
Owner

Summary

Root cause issue #2613/#2616 (52/53 banned-прогонов за 14 дней): Циан/Яндекс передают
proxy_provider в браузерный BrowserFetcher (build_browser_fetcher(config, source, proxy_provider=...)),
Авито — нет. AvitoScraper.__init__ не принимал proxy_provider вообще, поэтому браузерный
путь Avito SERP всегда шёл через мёртвый env-прокси BROWSER_PROXY_AVITO (407 auth failure,
подписка mobileproxy закрыта с начала июля) вместо пула scrape_proxies.

Прошито ровно по образцу Cian (CianScraper.__init__(..., proxy_provider=...)
build_browser_fetcher(config, "cian", proxy_provider=self._proxy_provider)):

  1. AvitoScraper.__init__ — новый keyword-only proxy_provider: ProxyProvider | None = None.
  2. __aenter__build_browser_fetcher(self._config, "avito", proxy_provider=self._proxy_provider).
  3. Все места конструирования AvitoScraper(...) в orchestration/pipeline.py +
    orchestration/scheduler.py + backend/app/api/v1/admin.py проверены индивидуально (см.
    таблицу ниже) — не единообразный паттерн, потому что у Avito ДВЕ разные архитектуры вызова.

Таблица: какие места прошиты, какие — нет и почему

Ключевое наблюдение: AvitoScraper конструируется в pipeline.py двумя разными паттернами.

Паттерн A (shared-browser, ручной override)run_avito_pipeline, run_avito_city_sweep,
run_avito_newbuilding_sweep: один BrowserFetcher строится ОДИН раз на весь sweep (не на
каждый anchor — экономия ресурсов), сразу с proxy_provider=proxy_provider, и присваивается
scraper._browser = shared_bf напрямую — AvitoScraper.__aenter__() для этих scraper'ов
вообще не вызывается. Пул там уже был подключён ДО этого PR (это НЕ баг).

Паттерн B (async with AvitoScraper(...) as scraper)run_avito_full_load и
backend/app/api/v1/admin.py's /scrape: __aenter__() реально исполняется →
build_browser_fetcher реально вызывается с тем, что передано в конструктор. Здесь
proxy_provider load-bearing — раньше отсутствовал совсем.

Место Паттерн proxy_provider в проде ДО этого PR Изменение
pipeline.py::run_avito_pipeline (~L555) A (shared, override) эффективно да (via shared BrowserFetcher) AvitoScraper(config, proxy_provider=proxy_provider) — не load-bearing, добавлен для консистентности/defense-in-depth
pipeline.py::run_avito_city_sweep (~L1112) A (shared, override) эффективно да то же — не load-bearing, добавлен для консистентности
pipeline.py::run_avito_newbuilding_sweep (~L1754) A (shared, override) эффективно да то же — не load-bearing, добавлен для консистентности
pipeline.py::run_avito_full_load (~L3469, async with) B (real __aenter__) НЕТ — параметра не было вовсе Реальный фикс: добавлен proxy_provider в сигнатуру функции + AvitoScraper(config, proxy_provider=proxy_provider)
scheduler.py::_job_avito_full_load / _job_avito_full_load_exhaustive вызывающие B НЕТ — ctx.proxy_provider не пробрасывался в run_avito_full_load(...), хотя _job_cian_full_load уже пробрасывал Реальный фикс: добавлено proxy_provider=ctx.proxy_provider в оба вызова
backend/app/api/v1/admin.py::/scrape (source=avito) B (real __aenter__) НЕТproxy_provider уже вычислен в scope (_kit_proxy_provider()) и передавался в Cian/Yandex на соседних строках, но не в Avito Реальный фикс: AvitoScraper(config, delay_provider=get_scraper_delay, proxy_provider=proxy_provider)
backend/app/tasks/avito_detail_backfill.py (detail-enrichment, НЕ SERP) ни A, ни B — свой раздельный BrowserFetcher(source="avito", endpoint=...) без proxy_provider/use_pool вообще нет Осознанно НЕ тронуто — отдельная фаза (detail, не SERP), не входит в диагностированный root cause (52/53 banned — все SERP-фазы: city_sweep/full_load/newbuilding_sweep); требует отдельного проброса proxy_provider через run_avito_detail_backfill + доп. параметр, вне границ этой задачи. Флагирую как follow-up.

Оценка эффекта (read-only, scrape_runs, последние 14 дней, прод)

source                          | status | n
avito_full_load                 | banned | 13
avito_city_sweep                | banned | 12
avito_city_sweep_nizhniy_tagil  | banned | 12
avito_newbuilding_sweep         | banned | 12
avito_full_load_exhaustive      | banned |  2
avito_city_sweep_serov          | banned |  1

52 banned-прогона за 14 дней — все Avito. cian_*/yandex_*/domclick_* = 0 banned
за тот же период. Разбивка по error-тексту: 51/52 = "...browser unavailable (proxy may be down)" (сидекар получил 503 от tradein-browser — Camoufox не смог поднять браузер через
дохлый прокси), 1/52 = реальный firewall-детект (avito_city_sweep_serov #2964,
"Avito SERP firewall (browser-mode) — IP banned"). (Задача цитирует 52+1=53 — у меня ровно
14×24ч от текущего момента дало 52 total; расхождение на 1 прогон — вероятно граница
временного окна, не противоречит выводу: подавляющее большинство banned = мёртвый прокси, не
реальный бан площадкой.)

run_avito_full_load (13 banned) и run_avito_newbuilding_sweep/run_avito_city_sweep*
(12+12+1=25 banned, паттерн A — пул уже был подключён через shared BrowserFetcher) —
все они ловили ОДИН и тот же сбой: browser unavailable (proxy may be down). Для паттерна A
это означает, что пул scrape_proxies тоже не спасал в моменты этих прогонов (пул on,
но узел не достался — см. предупреждение о конкуренции ниже) — иначе они бы не банились.
Для run_avito_full_load (паттерн B, 13 прогонов) причина прямая и однозначная: пул не был
подключён вовсе, поэтому 100% фетчей шли на дохлый env-прокси.

После этого PR, при условии живого узла в пуле в момент фетча: run_avito_full_load
получит рабочий прокси всегда (единственное исправление, актуальное для НЕЕ), а
city_sweep/newbuilding_sweep получат дополнительный шанс (они и так были подключены к пулу
через паттерн A) — реальный прирост зависит от текущей загрузки пула (см. ниже).

Конкуренция за узлы пула — предупреждение

Прод (scrape_proxies, только что проверено read-only):

id | label                 | affinity  | enabled | consecutive_fails
1  | asocks-residential-1  | domclick  | true    | 0   (зарезервирован под domclick, в общий fallback НЕ попадает пока жив)
9  | asocks-mobile-1       | any       | false   | 10
10 | asocks-mobile-2       | any       | true    | 0   ← единственный реально доступный "any"-узел
11 | asocks-mobile-3       | any       | false   | 10

Живых узлов 2 из 4, из которых реально доступен ОБЩЕМУ пулу avito/cian/yandex ровно ОДИН
(id=10, "any"-affinity; id=1 зарезервирован под domclick и не попадает в fallback, пока у
domclick-affinity нет второго живого узла — acquire()'s EXISTS-guard). Это значит:

  • В любой момент времени пул может обслужить ровно одного конкурентного потребителя
    (curl ИЛИ browser, avito ИЛИ cian ИЛИ yandex). Все три sweep-семейства теперь (после этого
    PR) реально претендуют на этот единственный узел — раньше Avito browser-путь на пул не
    претендовал вовсе (шёл мимо), значит после мержа конкуренция за id=10 вырастет.
  • Когда узел занят другим потребителем в момент фетча Avito: _pool_proxy()
    (browser_fetcher.py) сегодня (до PR #2634) молча fallback на env (yield None, None
    browser использует BROWSER_PROXY_AVITO) — который мёртв. Т.е. в моменты пиковой
    конкуренции Avito по-прежнему может банится тем же образом, что сейчас, просто РЕЖЕ
    (не на каждом anchor'е/бакете, а только когда узел занят).
  • PR #2634 (fix/tradein-no-dead-env-proxy-fallback, open, mergeable, база = тот же
    main@964b62d, файлы НЕ пересекаются с этим PR — конфликтов нет) добавляет
    NoProxyAvailableError вместо тихого env-fallback в prod при пустом пуле — но его текст
    явно указывает
    , что этот класс ошибок пока НЕ отделён от is_soft_ban в
    avito/serp.py:535-540 (любой status==503/"browser unavailable" безусловно = soft-ban →
    после исчерпания rotation-бюджета → mark_banned). Значит даже после обоих PR — при
    реальном исчерпании пула Avito всё ещё уйдёт в banned, просто по другой причине
    (NoProxyAvailableError вместо мёртвого env-прокси). Это осознанно не чиню здесь (out of
    scope, по границам задачи и по тексту самого PR #2634, который называет это отдельным
    следующим шагом).
  • Рекомендация (не в этом PR): docker-compose use_proxy_pool_browser=true уже включён
    на прод (tradein-scraper, docker-compose.prod.yml:217) — то есть фикс реально заработает
    сразу после деплоя. Но с 1 живым "any"-узлом на троих источников empирический эффект будет
    ЧАСТИЧНЫМ, пока пул не пополнят (issue за пределами этой задачи — оператору нужно
    реанимировать/добавить прокси-узлы).

Curl-путь Avito (шаг 4 диагностики)

AvitoScraper._build_cffi_session (curl_cffi fallback внутри browser-mode + весь
SCRAPER_FETCH_MODE=curl_cffi legacy-путь) использует self._config.scraper_proxy_url
(env, mobile backconnect-прокси mproxy.site/ard.mobileproxy.space) — НЕ ProxyProvider/
пул scrape_proxies. Это намеренно другой, отдельный механизм (backconnect с
self-rotation через avito_proxy_rotate_url/changeip), а не асимметрия с браузерным путём:
IMV-оценка Avito (avito/imv.py) уже пул-aware через curl_proxy_url() (#2163) за флагом
use_proxy_pool_curl — а вот SERP-curl-фоллбэк (_build_cffi_session) — нет, и остаётся
таким же, каким был. Асимметрия, которую чинит этот PR, — только браузерный путь
(единственный, где реально сидел мёртвый прокси и который реально генерил все 52 banned).
Curl-путь НЕ трогал.

Falsification

Для каждого нового теста подтверждено: подмена реализации (сигнатура/kwarg остаётся, но
proxy_provider не доходит до build_browser_fetcher/AvitoScraper/run_avito_full_load)
даёт AssertionError на значении (assert None is <sentinel>), НЕ TypeError — записано
построчно для каждого из 8 новых/дополненных сценариев (constructor→__aenter__ ×3,
run_avito_full_load ×2, scheduler _job_avito_full_load(_exhaustive) ×2, admin /scrape ×2,
run_avito_city_sweep/run_avito_newbuilding_sweep constructor-consistency ×2 — итого выполнено
7 ручных falsification-прогонов на затрагивающих код-путях, каждый упал на значении, затем
код возвращён к исходному состоянию).

Test plan

  • cd tradein-mvp/backend && uv run pytest -q3189 passed, 9 skipped, 1 known
    pre-existing fail
    (tests/test_search_api.py::test_search_cache_hit, 401 RBAC — не в
    scope этой задачи).
  • ruff check (select E,F,I,B,UP,N,RUF, ignore RUF001-3) — чисто на всех изменённых
    файлах (2 pre-existing E501 в avito/serp.py далеко от диффа, не мои строки).
  • Falsification для всех 7 затронутых код-путей — см. выше.
  • Прод read-only: scrape_runs (banned breakdown за 14д), scrape_proxies (live-nodes),
    docker exec tradein-scraper printenv | grep PROXY_POOL (подтверждено
    USE_PROXY_POOL_BROWSER=true уже на sched-контейнере — фикс сработает без доп. deploy-шагов).
  • Диффы с PR #2634 (fix/tradein-no-dead-env-proxy-fallback, issue #2616) НЕ пересекаются
    по файлам — проверено (git diff --stat обеих веток, оба от main@964b62d).

Файлы

  • tradein-mvp/packages/scraper-kit/src/scraper_kit/providers/avito/serp.py — конструктор + __aenter__
  • tradein-mvp/packages/scraper-kit/src/scraper_kit/orchestration/pipeline.py — 4 места создания AvitoScraper + сигнатура run_avito_full_load
  • tradein-mvp/packages/scraper-kit/src/scraper_kit/orchestration/scheduler.py — 2 вызова run_avito_full_load
  • tradein-mvp/backend/app/api/v1/admin.py/scrape endpoint
  • Тесты: tradein-mvp/backend/tests/test_kit_serp_proxy_pool.py,
    test_scraper_kit_pipeline_parity.py, test_scraper_kit_pipeline_parity2.py,
    test_scraper_kit_scheduler_parity.py, test_admin_scrape_avito_proxy_provider.py (новый)

Refs #2613, #2616

## Summary Root cause issue #2613/#2616 (52/53 `banned`-прогонов за 14 дней): Циан/Яндекс передают `proxy_provider` в браузерный `BrowserFetcher` (`build_browser_fetcher(config, source, proxy_provider=...)`), Авито — нет. `AvitoScraper.__init__` не принимал `proxy_provider` вообще, поэтому браузерный путь Avito SERP всегда шёл через мёртвый env-прокси `BROWSER_PROXY_AVITO` (407 auth failure, подписка mobileproxy закрыта с начала июля) вместо пула `scrape_proxies`. Прошито ровно по образцу Cian (`CianScraper.__init__(..., proxy_provider=...)` → `build_browser_fetcher(config, "cian", proxy_provider=self._proxy_provider)`): 1. `AvitoScraper.__init__` — новый keyword-only `proxy_provider: ProxyProvider | None = None`. 2. `__aenter__` — `build_browser_fetcher(self._config, "avito", proxy_provider=self._proxy_provider)`. 3. Все места конструирования `AvitoScraper(...)` в `orchestration/pipeline.py` + `orchestration/scheduler.py` + `backend/app/api/v1/admin.py` проверены индивидуально (см. таблицу ниже) — не единообразный паттерн, потому что у Avito ДВЕ разные архитектуры вызова. ## Таблица: какие места прошиты, какие — нет и почему Ключевое наблюдение: `AvitoScraper` конструируется в pipeline.py **двумя разными паттернами**. **Паттерн A (shared-browser, ручной override)** — `run_avito_pipeline`, `run_avito_city_sweep`, `run_avito_newbuilding_sweep`: один `BrowserFetcher` строится ОДИН раз на весь sweep (не на каждый anchor — экономия ресурсов), сразу с `proxy_provider=proxy_provider`, и присваивается `scraper._browser = shared_bf` напрямую — `AvitoScraper.__aenter__()` для этих scraper'ов **вообще не вызывается**. Пул там уже был подключён ДО этого PR (это НЕ баг). **Паттерн B (`async with AvitoScraper(...) as scraper`)** — `run_avito_full_load` и `backend/app/api/v1/admin.py`'s `/scrape`: `__aenter__()` реально исполняется → `build_browser_fetcher` реально вызывается с тем, что передано в конструктор. Здесь `proxy_provider` **load-bearing** — раньше отсутствовал совсем. | Место | Паттерн | proxy_provider в проде ДО этого PR | Изменение | |---|---|---|---| | `pipeline.py::run_avito_pipeline` (~L555) | A (shared, override) | эффективно да (via shared BrowserFetcher) | `AvitoScraper(config, proxy_provider=proxy_provider)` — не load-bearing, добавлен для консистентности/defense-in-depth | | `pipeline.py::run_avito_city_sweep` (~L1112) | A (shared, override) | эффективно да | то же — не load-bearing, добавлен для консистентности | | `pipeline.py::run_avito_newbuilding_sweep` (~L1754) | A (shared, override) | эффективно да | то же — не load-bearing, добавлен для консистентности | | `pipeline.py::run_avito_full_load` (~L3469, `async with`) | B (real `__aenter__`) | **НЕТ — параметра не было вовсе** | **Реальный фикс**: добавлен `proxy_provider` в сигнатуру функции + `AvitoScraper(config, proxy_provider=proxy_provider)` | | `scheduler.py::_job_avito_full_load` / `_job_avito_full_load_exhaustive` | вызывающие B | **НЕТ — `ctx.proxy_provider` не пробрасывался в `run_avito_full_load(...)`**, хотя `_job_cian_full_load` уже пробрасывал | **Реальный фикс**: добавлено `proxy_provider=ctx.proxy_provider` в оба вызова | | `backend/app/api/v1/admin.py::/scrape` (source=avito) | B (real `__aenter__`) | **НЕТ** — `proxy_provider` уже вычислен в scope (`_kit_proxy_provider()`) и передавался в Cian/Yandex на соседних строках, но не в Avito | **Реальный фикс**: `AvitoScraper(config, delay_provider=get_scraper_delay, proxy_provider=proxy_provider)` | | `backend/app/tasks/avito_detail_backfill.py` (detail-enrichment, НЕ SERP) | ни A, ни B — свой раздельный `BrowserFetcher(source="avito", endpoint=...)` без `proxy_provider`/`use_pool` вообще | нет | **Осознанно НЕ тронуто** — отдельная фаза (detail, не SERP), не входит в диагностированный root cause (52/53 banned — все SERP-фазы: city_sweep/full_load/newbuilding_sweep); требует отдельного проброса `proxy_provider` через `run_avito_detail_backfill` + доп. параметр, вне границ этой задачи. Флагирую как follow-up. | ## Оценка эффекта (read-only, `scrape_runs`, последние 14 дней, прод) ``` source | status | n avito_full_load | banned | 13 avito_city_sweep | banned | 12 avito_city_sweep_nizhniy_tagil | banned | 12 avito_newbuilding_sweep | banned | 12 avito_full_load_exhaustive | banned | 2 avito_city_sweep_serov | banned | 1 ``` **52 `banned`-прогона за 14 дней — все Avito.** `cian_*`/`yandex_*`/`domclick_*` = 0 banned за тот же период. Разбивка по error-тексту: **51/52 = `"...browser unavailable (proxy may be down)"`** (сидекар получил 503 от tradein-browser — Camoufox не смог поднять браузер через дохлый прокси), **1/52 = реальный firewall-детект** (`avito_city_sweep_serov` #2964, `"Avito SERP firewall (browser-mode) — IP banned"`). (Задача цитирует 52+1=53 — у меня ровно 14×24ч от текущего момента дало 52 total; расхождение на 1 прогон — вероятно граница временного окна, не противоречит выводу: подавляющее большинство banned = мёртвый прокси, не реальный бан площадкой.) **run_avito_full_load** (13 banned) и **run_avito_newbuilding_sweep**/**run_avito_city_sweep*** (12+12+1=25 banned, паттерн A — пул уже был подключён через shared BrowserFetcher) — все они ловили ОДИН и тот же сбой: `browser unavailable (proxy may be down)`. Для паттерна A это означает, что пул `scrape_proxies` **тоже не спасал** в моменты этих прогонов (пул on, но узел не достался — см. предупреждение о конкуренции ниже) — иначе они бы не банились. Для `run_avito_full_load` (паттерн B, 13 прогонов) причина прямая и однозначная: пул не был подключён вовсе, поэтому 100% фетчей шли на дохлый env-прокси. **После этого PR**, при условии живого узла в пуле в момент фетча: `run_avito_full_load` получит рабочий прокси всегда (единственное исправление, актуальное для НЕЕ), а city_sweep/newbuilding_sweep получат дополнительный шанс (они и так были подключены к пулу через паттерн A) — реальный прирост зависит от текущей загрузки пула (см. ниже). ## Конкуренция за узлы пула — предупреждение Прод (`scrape_proxies`, только что проверено read-only): ``` id | label | affinity | enabled | consecutive_fails 1 | asocks-residential-1 | domclick | true | 0 (зарезервирован под domclick, в общий fallback НЕ попадает пока жив) 9 | asocks-mobile-1 | any | false | 10 10 | asocks-mobile-2 | any | true | 0 ← единственный реально доступный "any"-узел 11 | asocks-mobile-3 | any | false | 10 ``` **Живых узлов 2 из 4, из которых реально доступен ОБЩЕМУ пулу avito/cian/yandex ровно ОДИН** (`id=10`, "any"-affinity; `id=1` зарезервирован под domclick и не попадает в fallback, пока у domclick-affinity нет второго живого узла — `acquire()`'s EXISTS-guard). Это значит: - В любой момент времени пул может обслужить **ровно одного** конкурентного потребителя (curl ИЛИ browser, avito ИЛИ cian ИЛИ yandex). Все три sweep-семейства теперь (после этого PR) реально претендуют на этот единственный узел — раньше Avito browser-путь на пул не претендовал вовсе (шёл мимо), значит после мержа конкуренция за id=10 **вырастет**. - Когда узел занят другим потребителем в момент фетча Avito: `_pool_proxy()` (`browser_fetcher.py`) сегодня (до PR #2634) молча **fallback на env** (`yield None, None` → browser использует `BROWSER_PROXY_AVITO`) — который мёртв. Т.е. в моменты пиковой конкуренции Avito **по-прежнему может банится** тем же образом, что сейчас, просто РЕЖЕ (не на каждом anchor'е/бакете, а только когда узел занят). - **PR #2634** (`fix/tradein-no-dead-env-proxy-fallback`, open, mergeable, база = тот же `main@964b62d`, файлы НЕ пересекаются с этим PR — конфликтов нет) добавляет `NoProxyAvailableError` вместо тихого env-fallback в prod при пустом пуле — **но его текст явно указывает**, что этот класс ошибок пока НЕ отделён от `is_soft_ban` в `avito/serp.py:535-540` (любой `status==503`/`"browser unavailable"` безусловно = soft-ban → после исчерпания rotation-бюджета → `mark_banned`). Значит даже после обоих PR — при реальном исчерпании пула Avito всё ещё уйдёт в `banned`, просто по другой причине (`NoProxyAvailableError` вместо мёртвого env-прокси). Это осознанно не чиню здесь (out of scope, по границам задачи и по тексту самого PR #2634, который называет это отдельным следующим шагом). - **Рекомендация** (не в этом PR): docker-compose `use_proxy_pool_browser=true` уже включён на прод (`tradein-scraper`, docker-compose.prod.yml:217) — то есть фикс реально заработает сразу после деплоя. Но с 1 живым "any"-узлом на троих источников empирический эффект будет ЧАСТИЧНЫМ, пока пул не пополнят (issue за пределами этой задачи — оператору нужно реанимировать/добавить прокси-узлы). ## Curl-путь Avito (шаг 4 диагностики) `AvitoScraper._build_cffi_session` (curl_cffi fallback внутри browser-mode + весь `SCRAPER_FETCH_MODE=curl_cffi` legacy-путь) использует `self._config.scraper_proxy_url` (env, mobile backconnect-прокси `mproxy.site`/`ard.mobileproxy.space`) — **НЕ** `ProxyProvider`/ пул `scrape_proxies`. Это **намеренно другой, отдельный механизм** (backconnect с self-rotation через `avito_proxy_rotate_url`/changeip), а не асимметрия с браузерным путём: IMV-оценка Avito (`avito/imv.py`) уже пул-aware через `curl_proxy_url()` (#2163) за флагом `use_proxy_pool_curl` — а вот SERP-curl-фоллбэк (`_build_cffi_session`) — нет, и остаётся таким же, каким был. Асимметрия, которую чинит этот PR, — **только браузерный путь** (единственный, где реально сидел мёртвый прокси и который реально генерил все 52 banned). Curl-путь НЕ трогал. ## Falsification Для каждого нового теста подтверждено: подмена реализации (сигнатура/kwarg остаётся, но `proxy_provider` не доходит до `build_browser_fetcher`/`AvitoScraper`/`run_avito_full_load`) даёт `AssertionError` на **значении** (`assert None is <sentinel>`), НЕ `TypeError` — записано построчно для каждого из 8 новых/дополненных сценариев (constructor→`__aenter__` ×3, `run_avito_full_load` ×2, scheduler `_job_avito_full_load(_exhaustive)` ×2, admin `/scrape` ×2, `run_avito_city_sweep`/`run_avito_newbuilding_sweep` constructor-consistency ×2 — итого выполнено 7 ручных falsification-прогонов на затрагивающих код-путях, каждый упал на значении, затем код возвращён к исходному состоянию). ## Test plan - [x] `cd tradein-mvp/backend && uv run pytest -q` — **3189 passed, 9 skipped, 1 known pre-existing fail** (`tests/test_search_api.py::test_search_cache_hit`, 401 RBAC — не в scope этой задачи). - [x] `ruff check` (select `E,F,I,B,UP,N,RUF`, ignore `RUF001-3`) — чисто на всех изменённых файлах (2 pre-existing E501 в `avito/serp.py` далеко от диффа, не мои строки). - [x] Falsification для всех 7 затронутых код-путей — см. выше. - [x] Прод read-only: `scrape_runs` (banned breakdown за 14д), `scrape_proxies` (live-nodes), `docker exec tradein-scraper printenv | grep PROXY_POOL` (подтверждено `USE_PROXY_POOL_BROWSER=true` уже на sched-контейнере — фикс сработает без доп. deploy-шагов). - [x] Диффы с PR #2634 (`fix/tradein-no-dead-env-proxy-fallback`, issue #2616) НЕ пересекаются по файлам — проверено (`git diff --stat` обеих веток, оба от `main@964b62d`). ## Файлы - `tradein-mvp/packages/scraper-kit/src/scraper_kit/providers/avito/serp.py` — конструктор + `__aenter__` - `tradein-mvp/packages/scraper-kit/src/scraper_kit/orchestration/pipeline.py` — 4 места создания `AvitoScraper` + сигнатура `run_avito_full_load` - `tradein-mvp/packages/scraper-kit/src/scraper_kit/orchestration/scheduler.py` — 2 вызова `run_avito_full_load` - `tradein-mvp/backend/app/api/v1/admin.py` — `/scrape` endpoint - Тесты: `tradein-mvp/backend/tests/test_kit_serp_proxy_pool.py`, `test_scraper_kit_pipeline_parity.py`, `test_scraper_kit_pipeline_parity2.py`, `test_scraper_kit_scheduler_parity.py`, `test_admin_scrape_avito_proxy_provider.py` (новый) Refs #2613, #2616
lekss361 added 1 commit 2026-08-02 14:33:05 +00:00
fix(tradein/scraper): подключить браузерный путь Авито к пулу прокси
All checks were successful
CI / changes (pull_request) Successful in 7s
CI Trade-In / changes (pull_request) Successful in 8s
CI / backend-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (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 2m40s
a95c38bdcc
lekss361 merged commit 8c0511e0bd into main 2026-08-02 14:37:40 +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#2637
No description provided.