Сайдкар МЕРЫ: зона браузера по IP узла, якорь Домклика без Яндекса, автологин Циана через пул, время навигации в логах #3563

Merged
bot-backend merged 4 commits from fix/browser-sidecar into main 2026-09-17 09:24:13 +00:00
Collaborator

Четыре задачи по браузерному сайдкару МЕРЫ (tradein-browser), по коммиту на каждую. Три закрыты полностью. Для #3419 сделан только первый шаг: сайдкар начал писать в лог время навигации. Решение по порогу таймаута будет после 48 часов данных.

#3187: таймзона браузера теперь берётся из IP узла, а не задаётся жёстко как МСК

Что было. В _launch_browser стояли geoip=True и config={"timezone": "Europe/Moscow"}. Camoufox 0.5.5 (utils.launch_options) при geoip ставит координаты из GeoIP-записи exit-IP безусловно, а таймзону — через setdefault. В итоге ручная МСК перебивала зону из IP, а координаты оставались по IP. На ручную зону библиотека пишет LeakWarning: Please use the geoip parameter.

Улики.

  • Прод, docker logs --since 13h tradein-browser на 17.09: 4715 таких предупреждений на 665 запусков браузера.

  • Включённые узлы пула по RIPE whois, 17.09:

    • 13 и 15: T2 Russia IP Network (NSK), geoloc 55.03/82.97, Новосибирск, UTC+7;
    • 14: POOL-MEGAFON-AMS-3, Самара, UTC+4;
    • 1: Biterika, Москва, UTC+3.

    Получается, что заявленная МСК расходится с адресом у трёх узлов из четырёх.

  • Опасение из старого комментария («BackConnect ротирует IP на каждый запрос») к текущему пулу не подходит. Health-логи за 48 часов показывают, что ротация меняет адрес внутри подсети оператора: у узла 15 это 176.59.137.x и 176.59.144.x, у узла 14 — 178.176.78-79.x. При смене узла браузер перезапускается (_launched_proxy), поэтому зона, взятая при запуске, не устаревает.

Что сделано. Ручная таймзона убрана. Зону и координаты теперь ставит geoip из одной записи.

Тест test_server_proxy_override.py::test_launch_leaves_timezone_to_geoip подменяет camoufox.async_api и проверяет реальные kwargs запуска: geoip is True, прокси разобран, в config нет ключей timezone и geolocation:*. Это ровно те ключи, на которые camoufox пишет предупреждение.

Фальсификация: вернул строку "config": {"timezone": "Europe/Moscow"}.

>       assert manual_geo == []
E       AssertionError: assert ['timezone'] == []
1 failed, 9 deselected

Приёмка на проде (через час после деплоя, не раньше 18.09):

  • docker logs --since 1h tradein-browser 2>&1 | grep -c 'Please use the .geoip. parameter' должно дать 0 (сейчас около 360 в час);
  • доля блоков Авито и Яндекса в scrape_runs за сутки не выросла по сравнению с 16–17.09.

Closes #3187

#3263: якорь Домклика открывает свою выдачу напрямую, без поиска Яндекса

Что было. По умолчанию BROWSER_ANCHOR_VIA_SEARCH="domclick": якорная вкладка шла на yandex.ru/search и кликала по результату.

Почему плохо.

  • Замер владельца от 29.08 (комментарий в issue): капча Яндекса зависит от выходного узла. Узлы 1, 10 и 11 получают капчу, узел 9 и домашний IP — выдачу. Google капчит 5 запросов из 5.
  • В проде путь через поиск срабатывал примерно 2 раза из 9 и стоил около 15 с на каждый подъём якоря.
  • docker logs tradein-browser за 15–16.09: все 7 попыток закончились «капча на выдаче Яндекса», и каждая из них через 0,2 с — «якорная вкладка не поднялась (Error)». После этого карточка получала BanPageDetectedError.
  • Referer карточке и так даёт переход с выдачи самой площадки: providers/domclick/detail.py, referer=origin.

Что сделано. Значение BROWSER_ANCHOR_VIA_SEARCH по умолчанию теперь пустое. Путь через поиск остаётся, но включается только явным env. Владелец предлагал именно этот вариант. Тесты пути через Яндекс включают его явно через фикстуру.

Новый тест test_default_env_domclick_anchor_goes_straight_to_origin загружает модуль заново без env. В подделке на выдаче Яндекса стоит капча. Тест проверяет, что goto_calls == [{"url": origin, "referer": None}].

Фальсификация: вернул значение по умолчанию "domclick".

E       AssertionError: assert [{'url': 'htt...ferer': None}] == [{'url': 'htt...ferer': None}]
E         At index 0 diff: {'url': 'https://yandex.ru/search/?text=%D0%B4...', 'referer': None} != {'url': 'https://ekaterinburg.domclick.ru/pokupka/kvartiry/vtorichka', 'referer': None}
FAILED test_server_anchor_search.py::test_default_env_domclick_anchor_goes_straight_to_origin

Приёмка на проде (через 1–2 дня после деплоя, не раньше 19.09):

  • в логах tradein-browser 0 строк [domclick]: капча на выдаче Яндекса;
  • [domclick]: якорная вкладка не поднялась — 0 или единичные случаи;
  • между «создан переиспользуемый browser context» и «якорная вкладка открыта» проходит примерно на 15 с меньше.

Если якорь без Яндекса тоже не поднимается (Error), причина не в поиске. Это отдельный разбор.

Closes #3263

#3410: автологин в Циан идёт через узел пула, а не через мёртвый env-узел сайдкара

Что было.

  • login_handler вызывал _no_live_proxy(provider, None) и _ensure_browser(provider) без override.
  • BrowserFetcher.login не клал proxy в тело запроса.
  • Ручка cian_auto_login сознательно не передавала провайдер (#3197): аренда там была бы холостой.

В итоге логин всегда шёл через SCRAPER_PROXY_URL сайдкара. На проде 17.09 это 190.2.145.131:10313, то есть узел 9: в scrape_proxies у него enabled=false, 324 провала подряд, «НЕ ИСПОЛЬЗУЕТСЯ (владелец, 12.09.2026)». Ручка восстановления cian-сессии не работала вовсе.

Что сделано. Три места, каждое по образцу соседа:

  • сайдкар: /login берёт proxy из тела так же, как /fetch: guard и _ensure_browser(provider, proxy_override=…). При перезапуске после краша _do_login сохраняет _launched_proxy, то есть тот же узел.
  • scraper-kit: login() кладёт в тело узел текущей аренды (_current_proxy()), как это делает _post_fetch.
  • backend: ручка передаёт _kit_proxy_provider(), как debug-карточка DomClick в том же файле. Пустой пул на проде даёт 503 «Нет свободного узла прокси для cian в пуле — логин не запускался», а не 502 «Browser login failed» и не заход на мёртвый env-узел (#2616).

Совместимость при деплое в любом порядке. Старый сайдкар игнорирует proxy в теле /login, новый без proxy ведёт себя по-старому.

Тесты #3197, которые утверждали «логин без пула», переписаны на обратные утверждения.

Тесты:

  • сайдкар, test_login_handler_prod_uses_body_proxy (2 случая): с proxy в теле и без env-прокси — _ensure_browser("cian", "http://pool:8080"); без proxy в теле — override None;
  • kit, test_login_body_carries_lease_proxy (2 случая): при аренде в теле /login лежат proxy и proxy_kind аренды, аренда отпускается; без пула этих полей нет;
  • backend, test_cian_auto_login_logs_in_through_pool_lease: провайдер передан, acquire == ["cian"], логин видит узел аренды, released == [13];
  • backend, test_cian_auto_login_empty_pool_in_production_is_honest_503: 503 со словом «пул», логин не запускался;
  • backend, test_cian_auto_login_dev_without_browser_pool_uses_env: аренды нет, прокси None.

Фальсификация в трёх слоях сразу (сайдкар без override, kit без body["proxy"], ручка без провайдера):

E       AssertionError: assert [('cian', None)] == [('cian', 'http://pool:8080')]
FAILED test_server_no_proxy_refusal.py::test_login_handler_prod_uses_body_proxy[extra0-http://pool:8080]

E           AssertionError: assert (None, None) == ('http://u:p@...8080', 'http')
E       AssertionError: без провайдера логин идёт с env-узла
E           Failed: DID NOT RAISE HTTPException
FAILED tests/test_kit_browser_fetcher_proxy_pool.py::test_login_body_carries_lease_proxy[True]
FAILED tests/test_3197_admin_debug_browser_pool_wiring.py::test_cian_auto_login_logs_in_through_pool_lease
FAILED tests/test_3197_admin_debug_browser_pool_wiring.py::test_cian_auto_login_empty_pool_in_production_is_honest_503

Отдельно проверил ветку 503: заменил except NoProxyAvailableError на посторонний класс.

E       assert (502, False) == (503, True)

Приёмка на проде (после деплоя, когда в пуле есть свободный cian-узел; нужно до 27.09, когда истекает текущая cian-сессия):

  • ручной POST /api/v1/admin/scrape/cian/auto-login возвращает ok;
  • в логе tradein-browser есть [cian]: браузер запущен (proxy_override=True) рядом с login OK;
  • при пустом пуле — 503 с текстом про пул.

Запуск ручки сохраняет сессию в БД, поэтому это решение владельца, агент её не вызывал.

Closes #3410

#3419 (частично): в логах /fetch появилось время целевой навигации

Что было. Порог BROWSER_NAV_TIMEOUT_MS=60000 не с чем было сравнить:

  • «fetch OK» писался на DEBUG и без длительности;
  • в «fetch error» длительности тоже не было;
  • время в access-логе включает пейсинг (у cian 18 с) и ожидание лока.

Сейчас на проде (docker logs --since 24h, 17.09): у [cian] 54 TimeoutError: Page.goto на примерно 1350 страниц (90 перезапусков по порогу × 15), это около 4 %.

Что сделано. _fetch_once меряет только целевую page.goto через time.monotonic в finally, поэтому время пишется и при таймауте. Обе строки несут nav_ms, «fetch OK» переведён на INFO. nav_ms=None означает, что до цели не дошли (упал прогрев origin); прошлое значение в этом случае не наследуется.

Тест test_server_nav_timing.py подменяет часы только в модуле сервера:

  • успех — nav_ms == 12345;
  • таймаут цели — 60000;
  • следующий отказ до цели — None.

Фальсификация.

  • Без сброса перед навигацией: assert [60000, 60000] == [60000, None].
  • Запись не в finally, а только при успехе: assert [None, None] == [60000, None].

Что осталось (Refs, не Closes). Собрать 48 часов данных. Посчитать p50/p90/p99 nav_ms успешных cian-fetch по типу страницы (cat/sale/zhk по URL в той же строке) и сравнить с долей таймаутов. Решить: разметить отказ площадки, поднять порог до обоснованного значения или сменить wait_until. Замер не раньше 19.09 вечером.

Refs #3419

Тесты

  • tradein-mvp/browser: pytest -q -rs252 passed, rc=0. До правок было 246, добавлено 6.
  • tradein-mvp/backend: DATABASE_URL=… uv run python -m pytest tests/ -q -p no:cacheprovider6227 passed, 42 skipped, rc=0.
  • uv run ruff check app tests → All checks passed. ruff format --check по изменённым Python-файлам backend и kit → already formatted.
  • packages/scraper-kit: своих тестов нет (no tests ran, rc=5). Тесты kit лежат в backend/tests.

Деплой

  • Пересоздаются tradein-browser (browser/), tradein-backend и tradein-scraper (backend/ и packages/scraper-kit/** пересобирают backend-образ).
  • Пересоздание tradein-browser убивает все активные браузерные прогоны, tradein-scraper — тоже. Перед мержем проверить SELECT count(*) FROM scrape_runs WHERE status='running' в том же блоке, что и мерж.
  • Миграций нет, env менять не нужно.

🤖 Generated with Claude Code

Четыре задачи по браузерному сайдкару МЕРЫ (`tradein-browser`), по коммиту на каждую. Три закрыты полностью. Для #3419 сделан только первый шаг: сайдкар начал писать в лог время навигации. Решение по порогу таймаута будет после 48 часов данных. ## #3187: таймзона браузера теперь берётся из IP узла, а не задаётся жёстко как МСК **Что было.** В `_launch_browser` стояли `geoip=True` и `config={"timezone": "Europe/Moscow"}`. Camoufox 0.5.5 (`utils.launch_options`) при geoip ставит координаты из GeoIP-записи exit-IP безусловно, а таймзону — через `setdefault`. В итоге ручная МСК перебивала зону из IP, а координаты оставались по IP. На ручную зону библиотека пишет `LeakWarning: Please use the geoip parameter`. **Улики.** - Прод, `docker logs --since 13h tradein-browser` на 17.09: 4715 таких предупреждений на 665 запусков браузера. - Включённые узлы пула по RIPE whois, 17.09: - 13 и 15: `T2 Russia IP Network (NSK)`, geoloc 55.03/82.97, Новосибирск, UTC+7; - 14: `POOL-MEGAFON-AMS-3`, Самара, UTC+4; - 1: Biterika, Москва, UTC+3. Получается, что заявленная МСК расходится с адресом у трёх узлов из четырёх. - Опасение из старого комментария («BackConnect ротирует IP на каждый запрос») к текущему пулу не подходит. Health-логи за 48 часов показывают, что ротация меняет адрес внутри подсети оператора: у узла 15 это 176.59.137.x и 176.59.144.x, у узла 14 — 178.176.78-79.x. При смене узла браузер перезапускается (`_launched_proxy`), поэтому зона, взятая при запуске, не устаревает. **Что сделано.** Ручная таймзона убрана. Зону и координаты теперь ставит geoip из одной записи. **Тест** `test_server_proxy_override.py::test_launch_leaves_timezone_to_geoip` подменяет `camoufox.async_api` и проверяет реальные kwargs запуска: `geoip is True`, прокси разобран, в `config` нет ключей `timezone` и `geolocation:*`. Это ровно те ключи, на которые camoufox пишет предупреждение. **Фальсификация:** вернул строку `"config": {"timezone": "Europe/Moscow"}`. ``` > assert manual_geo == [] E AssertionError: assert ['timezone'] == [] 1 failed, 9 deselected ``` **Приёмка на проде** (через час после деплоя, не раньше 18.09): - `docker logs --since 1h tradein-browser 2>&1 | grep -c 'Please use the .geoip. parameter'` должно дать 0 (сейчас около 360 в час); - доля блоков Авито и Яндекса в `scrape_runs` за сутки не выросла по сравнению с 16–17.09. Closes #3187 ## #3263: якорь Домклика открывает свою выдачу напрямую, без поиска Яндекса **Что было.** По умолчанию `BROWSER_ANCHOR_VIA_SEARCH="domclick"`: якорная вкладка шла на `yandex.ru/search` и кликала по результату. **Почему плохо.** - Замер владельца от 29.08 (комментарий в issue): капча Яндекса зависит от выходного узла. Узлы 1, 10 и 11 получают капчу, узел 9 и домашний IP — выдачу. Google капчит 5 запросов из 5. - В проде путь через поиск срабатывал примерно 2 раза из 9 и стоил около 15 с на каждый подъём якоря. - `docker logs tradein-browser` за 15–16.09: все 7 попыток закончились «капча на выдаче Яндекса», и каждая из них через 0,2 с — «якорная вкладка не поднялась (Error)». После этого карточка получала `BanPageDetectedError`. - Referer карточке и так даёт переход с выдачи самой площадки: `providers/domclick/detail.py`, `referer=origin`. **Что сделано.** Значение `BROWSER_ANCHOR_VIA_SEARCH` по умолчанию теперь пустое. Путь через поиск остаётся, но включается только явным env. Владелец предлагал именно этот вариант. Тесты пути через Яндекс включают его явно через фикстуру. **Новый тест** `test_default_env_domclick_anchor_goes_straight_to_origin` загружает модуль заново без env. В подделке на выдаче Яндекса стоит капча. Тест проверяет, что `goto_calls == [{"url": origin, "referer": None}]`. **Фальсификация:** вернул значение по умолчанию `"domclick"`. ``` E AssertionError: assert [{'url': 'htt...ferer': None}] == [{'url': 'htt...ferer': None}] E At index 0 diff: {'url': 'https://yandex.ru/search/?text=%D0%B4...', 'referer': None} != {'url': 'https://ekaterinburg.domclick.ru/pokupka/kvartiry/vtorichka', 'referer': None} FAILED test_server_anchor_search.py::test_default_env_domclick_anchor_goes_straight_to_origin ``` **Приёмка на проде** (через 1–2 дня после деплоя, не раньше 19.09): - в логах `tradein-browser` 0 строк `[domclick]: капча на выдаче Яндекса`; - `[domclick]: якорная вкладка не поднялась` — 0 или единичные случаи; - между «создан переиспользуемый browser context» и «якорная вкладка открыта» проходит примерно на 15 с меньше. Если якорь без Яндекса тоже не поднимается (Error), причина не в поиске. Это отдельный разбор. Closes #3263 ## #3410: автологин в Циан идёт через узел пула, а не через мёртвый env-узел сайдкара **Что было.** - `login_handler` вызывал `_no_live_proxy(provider, None)` и `_ensure_browser(provider)` без override. - `BrowserFetcher.login` не клал `proxy` в тело запроса. - Ручка `cian_auto_login` сознательно не передавала провайдер (#3197): аренда там была бы холостой. В итоге логин всегда шёл через `SCRAPER_PROXY_URL` сайдкара. На проде 17.09 это `190.2.145.131:10313`, то есть узел 9: в `scrape_proxies` у него `enabled=false`, 324 провала подряд, «НЕ ИСПОЛЬЗУЕТСЯ (владелец, 12.09.2026)». Ручка восстановления cian-сессии не работала вовсе. **Что сделано.** Три места, каждое по образцу соседа: - **сайдкар:** `/login` берёт `proxy` из тела так же, как `/fetch`: guard и `_ensure_browser(provider, proxy_override=…)`. При перезапуске после краша `_do_login` сохраняет `_launched_proxy`, то есть тот же узел. - **scraper-kit:** `login()` кладёт в тело узел текущей аренды (`_current_proxy()`), как это делает `_post_fetch`. - **backend:** ручка передаёт `_kit_proxy_provider()`, как debug-карточка DomClick в том же файле. Пустой пул на проде даёт 503 «Нет свободного узла прокси для cian в пуле — логин не запускался», а не 502 «Browser login failed» и не заход на мёртвый env-узел (#2616). **Совместимость при деплое в любом порядке.** Старый сайдкар игнорирует `proxy` в теле `/login`, новый без `proxy` ведёт себя по-старому. Тесты #3197, которые утверждали «логин без пула», переписаны на обратные утверждения. **Тесты:** - сайдкар, `test_login_handler_prod_uses_body_proxy` (2 случая): с proxy в теле и без env-прокси — `_ensure_browser("cian", "http://pool:8080")`; без proxy в теле — override `None`; - kit, `test_login_body_carries_lease_proxy` (2 случая): при аренде в теле `/login` лежат `proxy` и `proxy_kind` аренды, аренда отпускается; без пула этих полей нет; - backend, `test_cian_auto_login_logs_in_through_pool_lease`: провайдер передан, `acquire == ["cian"]`, логин видит узел аренды, `released == [13]`; - backend, `test_cian_auto_login_empty_pool_in_production_is_honest_503`: 503 со словом «пул», логин не запускался; - backend, `test_cian_auto_login_dev_without_browser_pool_uses_env`: аренды нет, прокси `None`. **Фальсификация** в трёх слоях сразу (сайдкар без override, kit без `body["proxy"]`, ручка без провайдера): ``` E AssertionError: assert [('cian', None)] == [('cian', 'http://pool:8080')] FAILED test_server_no_proxy_refusal.py::test_login_handler_prod_uses_body_proxy[extra0-http://pool:8080] E AssertionError: assert (None, None) == ('http://u:p@...8080', 'http') E AssertionError: без провайдера логин идёт с env-узла E Failed: DID NOT RAISE HTTPException FAILED tests/test_kit_browser_fetcher_proxy_pool.py::test_login_body_carries_lease_proxy[True] FAILED tests/test_3197_admin_debug_browser_pool_wiring.py::test_cian_auto_login_logs_in_through_pool_lease FAILED tests/test_3197_admin_debug_browser_pool_wiring.py::test_cian_auto_login_empty_pool_in_production_is_honest_503 ``` Отдельно проверил ветку 503: заменил `except NoProxyAvailableError` на посторонний класс. ``` E assert (502, False) == (503, True) ``` **Приёмка на проде** (после деплоя, когда в пуле есть свободный cian-узел; нужно до 27.09, когда истекает текущая cian-сессия): - ручной `POST /api/v1/admin/scrape/cian/auto-login` возвращает `ok`; - в логе `tradein-browser` есть `[cian]: браузер запущен (proxy_override=True)` рядом с `login OK`; - при пустом пуле — 503 с текстом про пул. Запуск ручки сохраняет сессию в БД, поэтому это решение владельца, агент её не вызывал. Closes #3410 ## #3419 (частично): в логах /fetch появилось время целевой навигации **Что было.** Порог `BROWSER_NAV_TIMEOUT_MS=60000` не с чем было сравнить: - «fetch OK» писался на DEBUG и без длительности; - в «fetch error» длительности тоже не было; - время в access-логе включает пейсинг (у cian 18 с) и ожидание лока. Сейчас на проде (`docker logs --since 24h`, 17.09): у `[cian]` 54 `TimeoutError: Page.goto` на примерно 1350 страниц (90 перезапусков по порогу × 15), это около 4 %. **Что сделано.** `_fetch_once` меряет только целевую `page.goto` через `time.monotonic` в `finally`, поэтому время пишется и при таймауте. Обе строки несут `nav_ms`, «fetch OK» переведён на INFO. `nav_ms=None` означает, что до цели не дошли (упал прогрев origin); прошлое значение в этом случае не наследуется. **Тест** `test_server_nav_timing.py` подменяет часы только в модуле сервера: - успех — `nav_ms == 12345`; - таймаут цели — `60000`; - следующий отказ до цели — `None`. **Фальсификация.** - Без сброса перед навигацией: `assert [60000, 60000] == [60000, None]`. - Запись не в `finally`, а только при успехе: `assert [None, None] == [60000, None]`. **Что осталось (Refs, не Closes).** Собрать 48 часов данных. Посчитать p50/p90/p99 `nav_ms` успешных cian-fetch по типу страницы (cat/sale/zhk по URL в той же строке) и сравнить с долей таймаутов. Решить: разметить отказ площадки, поднять порог до обоснованного значения или сменить `wait_until`. Замер не раньше 19.09 вечером. Refs #3419 ## Тесты - `tradein-mvp/browser`: `pytest -q -rs` → **252 passed**, rc=0. До правок было 246, добавлено 6. - `tradein-mvp/backend`: `DATABASE_URL=… uv run python -m pytest tests/ -q -p no:cacheprovider` → **6227 passed, 42 skipped**, rc=0. - `uv run ruff check app tests` → All checks passed. `ruff format --check` по изменённым Python-файлам backend и kit → already formatted. - `packages/scraper-kit`: своих тестов нет (`no tests ran`, rc=5). Тесты kit лежат в `backend/tests`. ## Деплой - **Пересоздаются** `tradein-browser` (browser/**), `tradein-backend` и `tradein-scraper` (backend/** и packages/scraper-kit/** пересобирают backend-образ). - **Пересоздание `tradein-browser` убивает все активные браузерные прогоны**, `tradein-scraper` — тоже. Перед мержем проверить `SELECT count(*) FROM scrape_runs WHERE status='running'` в том же блоке, что и мерж. - **Миграций нет**, env менять не нужно. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
bot-backend added 4 commits 2026-09-17 08:05:14 +00:00
camoufox 0.5.5 при geoip=True кладёт координаты из GeoIP-записи exit-IP
безусловно, а timezone — через setdefault, поэтому ручной
config["timezone"]="Europe/Moscow" перебивал зону из IP. Узлы пула 13 и 15
(T2 NSK, UTC+7) и 14 (МегаФон Самара, UTC+4, RIPE 17.09) заявляли МСК при
сибирских/самарских координатах, а библиотека писала LeakWarning на каждом
запуске (4715 штук за 13 ч при 665 запусках). Ротация узла идёт внутри
подсети оператора, смена узла релончит браузер — зона с запуска не протухает.

Тест: в kwargs запуска нет ключей timezone/geolocation:* в config, geoip=True.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Выдача Яндекса капчит конкретные выходные узлы пула (замер владельца 29.08:
узлы 1/10/11 — капча, 9 и домашний IP — выдача), поэтому путь через поиск
срабатывал ~2 раза из 9 и стоил ~15 с на подъём якоря. В логах tradein-browser
15-16.09 все 7 попыток — капча, и все 7 через 0,2 с заканчивались «якорная
вкладка не поднялась (Error)». Referer карточке и так даёт переход с выдачи
площадки (detail.py: referer=origin).

Дефолт BROWSER_ANCHOR_VIA_SEARCH теперь пустой: путь через поиск остаётся,
но только по явному env. Тесты яндекс-пути включают его явно; новый тест
грузит модуль без env и проверяет, что якорь идёт прямым goto(origin).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
/login сайдкара не принимал proxy из тела (_no_live_proxy(provider, None),
_ensure_browser без override), BrowserFetcher.login его не клал, а ручка
cian auto-login сознательно не брала провайдер. Итог: логин всегда шёл с
SCRAPER_PROXY_URL сайдкара — на проде это выключенный узел 9 (InvalidIP),
то есть ручка восстановления cian-сессии не работала вовсе.

- сайдкар: /login берёт proxy из тела, как /fetch (guard и _ensure_browser
  с override; crash-relaunch в _do_login сохраняет _launched_proxy);
- scraper-kit: login() кладёт в тело узел текущей аренды, как _post_fetch;
- backend: ручка передаёт _kit_proxy_provider(), как debug-карточка DomClick;
  пустой пул на проде — 503 «нет свободного узла для cian», а не 502.

Тесты #3197 про «логин без пула» переписаны на обратные утверждения.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Сайдкар: в логах /fetch появилось время целевой навигации — замер хвоста Циана (#3419)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 17s
CI / changes (pull_request) Successful in 18s
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 / browser-tests (pull_request) Successful in 1m47s
CI Trade-In / backend-tests (pull_request) Successful in 5m54s
1a773554db
Порог BROWSER_NAV_TIMEOUT_MS=60000 не с чем было сравнить: «fetch OK» писался
на DEBUG и без длительности, «fetch error» — тоже без неё, а время из
access-лога включает пейсинг (18 с у cian) и ожидание лока. За сутки до 17.09
у cian 54 TimeoutError на ~1350 страниц (90 recycle × 15), ~4 %.

Теперь _fetch_once меряет только целевую page.goto (monotonic, в finally —
и на таймауте), обе строки несут nav_ms, «fetch OK» переведён на INFO.
nav_ms=None — до цели не дошли (упал прогрев origin), прошлое число не
наследуется. Решение по порогу — после 48 ч данных, это отдельный шаг.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
bot-backend merged commit a130303cc9 into main 2026-09-17 09:24:13 +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#3563
No description provided.