Свип ДомКлика ходит в тёплом браузерном контексте, а не поднимает камуфокс на каждый фетч #3595

Merged
lekss361 merged 1 commit from fix/3118-domclick-sweep-warm-context into main 2026-09-17 18:10:26 +00:00
Owner

Что случилось

Контрольный прогон 7389 domclick_city_sweep_moskva (17.09, 14:20-17:25 UTC) подтвердил, что инкрементальное сохранение из #3592 работает — 2177 лотов легли в listings по ходу прогона и пережили убийство по watchdog'у. Но заодно замер показал, почему Москва всё равно не собирается:

корзин пройдено 1 из 6
страниц 600 за 3 ч 05 мин
память сайдкара 426 МиБ → 1.79 ГиБ
корзины '5+', 'st', '1' легли за ~2 мин каждая на goto(origin) timeout 60s

В логе сайдкара строка запуск AsyncCamoufox (proxy=True, recycle_pages=15) стоит между почти каждым запросом.

Причина

serp.py:525 звал build_browser_fetcher(config, "domclick", proxy_provider=...), а у фабрики параметра reuse_context вообще не было → дефолт BrowserFetcher False → сайдкар на каждый /fetch делает browser.new_page() и поднимает новый камуфокс. Якорная вкладка (#3118) в таком режиме не переживает фетч, её поднимают заново через goto(origin), и эти goto валятся по таймауту, убивая корзину целиком.

Свип оказался единственным на холодном пути: domclick_detail_backfill.py:409 и avito_detail_backfill.py:430 давно ходят с reuse_context=True, и там же записан замер — 26 холодных фетчей подряд = 100% блок, те же карточки в тёплом контексте = 5/5 примерно по 2 с.

Почему ЕКБ это переживал: его корзины почти не требуют бисекции по цене, все шесть укладываются в ~1 час (прогоны 7116 — 4682 лота за 53 мин, 7219 — 6372 за 70 мин). Москва на холодной скорости не успевает и одну.

Что в правке

  1. providers/_base.pybuild_browser_fetcher получил keyword-only reuse_context: bool = False, прокинутый в оба конструктора BrowserFetcher. Дефолт False сохраняет поведение остальных шести вызывающих байт в байт.
  2. providers/domclick/serp.py:525 — свип зовёт фабрику с reuse_context=True.
  3. providers/domclick/serp.py, generic except — упавшая корзина зовёт fetcher.request_context_reset() перед continue: сгоревший тёплый контекст иначе переползает в следующую корзину, что и дало наблюдавшийся каскад из трёх.

Чего в правке СОЗНАТЕЛЬНО нет

В ветке DomClickBlockedError сброса нет. request_context_reset() лишь взводит _context_reset_pending (browser_fetcher.py:741), а тот уезжает в сайдкар ключом reset_context следующего fetch() (browser_fetcher.py:565); __aexit__ его не сливает. После break фетчей больше не будет — вызов был бы no-op'ом, который читается как защита.

Остаётся известная дыра: _contexts[provider] в сайдкаре (browser/server.py:826) — обычный dict без TTL, чистится только явным reset_context или вытеснением провайдера. Значит сожжённый QRATOR-блоком контекст достаётся следующему прогону. Закрыть можно только отдельной ручкой сброса в сайдкаре (сейчас там /fetch, /fetch-json, /login, /health, /pacing) — отдельной задачей.

Проверенные риски

  • Свип и detail-backfill теперь делят один контекст провайдера domclick. Драки за якорную вкладку не будет: backfill строит origin как {scheme}://{netloc}/pokupka/kvartiry/vtorichka (detail.py:642), для Москвы он совпадает со свиповым.
  • Отброшенная гипотеза: «корзины сыпались из-за конкуренции с domclick_detail_backfill». По scrape_runs backfill (run 7399) стартовал 17:18:50, а корзины падали в 16:04-16:08 — пересечения нет.

Тесты

Новый test_3118_domclick_reuse_context.py — 4 теста: дефолт и явный флаг у фабрики, свип строит фетчер именно с reuse_context=True, упавшая корзина сбрасывает контекст на каждой из шести.

Три существующих двойника фетчера дополнены (test_domclick_incremental_save.py, test_kit_serp_proxy_pool.py, test_2670_streak_and_partial_coverage.py) — без этого они падали TypeError/AttributeError, то есть проверяли собственную неполноту вместо поведения свипа.

Локально: 6473 passed, 74 skipped. Ruff чист.

Как проверять после деплоя

Ручной прогон Москвы и сравнение страниц в час против сегодняшних 600 за 3 ч 05 мин.

## Что случилось Контрольный прогон 7389 `domclick_city_sweep_moskva` (17.09, 14:20-17:25 UTC) подтвердил, что инкрементальное сохранение из #3592 работает — 2177 лотов легли в `listings` по ходу прогона и пережили убийство по watchdog'у. Но заодно замер показал, почему Москва всё равно не собирается: | | | |---|---| | корзин пройдено | 1 из 6 | | страниц | 600 за 3 ч 05 мин | | память сайдкара | 426 МиБ → 1.79 ГиБ | | корзины `'5+'`, `'st'`, `'1'` | легли за ~2 мин каждая на `goto(origin)` timeout 60s | В логе сайдкара строка `запуск AsyncCamoufox (proxy=True, recycle_pages=15)` стоит между почти каждым запросом. ## Причина `serp.py:525` звал `build_browser_fetcher(config, "domclick", proxy_provider=...)`, а у фабрики параметра `reuse_context` вообще не было → дефолт `BrowserFetcher` `False` → сайдкар на каждый `/fetch` делает `browser.new_page()` и поднимает новый камуфокс. Якорная вкладка (#3118) в таком режиме не переживает фетч, её поднимают заново через `goto(origin)`, и эти `goto` валятся по таймауту, убивая корзину целиком. Свип оказался единственным на холодном пути: `domclick_detail_backfill.py:409` и `avito_detail_backfill.py:430` давно ходят с `reuse_context=True`, и там же записан замер — **26 холодных фетчей подряд = 100% блок, те же карточки в тёплом контексте = 5/5 примерно по 2 с.** Почему ЕКБ это переживал: его корзины почти не требуют бисекции по цене, все шесть укладываются в ~1 час (прогоны 7116 — 4682 лота за 53 мин, 7219 — 6372 за 70 мин). Москва на холодной скорости не успевает и одну. ## Что в правке 1. `providers/_base.py` — `build_browser_fetcher` получил keyword-only `reuse_context: bool = False`, прокинутый в оба конструктора `BrowserFetcher`. Дефолт `False` сохраняет поведение остальных шести вызывающих байт в байт. 2. `providers/domclick/serp.py:525` — свип зовёт фабрику с `reuse_context=True`. 3. `providers/domclick/serp.py`, generic `except` — упавшая корзина зовёт `fetcher.request_context_reset()` перед `continue`: сгоревший тёплый контекст иначе переползает в следующую корзину, что и дало наблюдавшийся каскад из трёх. ## Чего в правке СОЗНАТЕЛЬНО нет В ветке `DomClickBlockedError` сброса нет. `request_context_reset()` лишь взводит `_context_reset_pending` (`browser_fetcher.py:741`), а тот уезжает в сайдкар ключом `reset_context` **следующего** `fetch()` (`browser_fetcher.py:565`); `__aexit__` его не сливает. После `break` фетчей больше не будет — вызов был бы no-op'ом, который читается как защита. **Остаётся известная дыра:** `_contexts[provider]` в сайдкаре (`browser/server.py:826`) — обычный dict без TTL, чистится только явным `reset_context` или вытеснением провайдера. Значит сожжённый QRATOR-блоком контекст достаётся следующему прогону. Закрыть можно только отдельной ручкой сброса в сайдкаре (сейчас там `/fetch`, `/fetch-json`, `/login`, `/health`, `/pacing`) — отдельной задачей. ## Проверенные риски - **Свип и detail-backfill теперь делят один контекст провайдера `domclick`.** Драки за якорную вкладку не будет: backfill строит origin как `{scheme}://{netloc}/pokupka/kvartiry/vtorichka` (`detail.py:642`), для Москвы он совпадает со свиповым. - **Отброшенная гипотеза:** «корзины сыпались из-за конкуренции с `domclick_detail_backfill`». По `scrape_runs` backfill (run 7399) стартовал 17:18:50, а корзины падали в 16:04-16:08 — пересечения нет. ## Тесты Новый `test_3118_domclick_reuse_context.py` — 4 теста: дефолт и явный флаг у фабрики, свип строит фетчер именно с `reuse_context=True`, упавшая корзина сбрасывает контекст на каждой из шести. Три существующих двойника фетчера дополнены (`test_domclick_incremental_save.py`, `test_kit_serp_proxy_pool.py`, `test_2670_streak_and_partial_coverage.py`) — без этого они падали `TypeError`/`AttributeError`, то есть проверяли собственную неполноту вместо поведения свипа. Локально: **6473 passed, 74 skipped**. Ruff чист. ## Как проверять после деплоя Ручной прогон Москвы и сравнение страниц в час против сегодняшних 600 за 3 ч 05 мин.
lekss361 added 1 commit 2026-09-17 18:04:21 +00:00
Свип ДомКлика ходит в тёплом браузерном контексте, а не поднимает камуфокс на каждый фетч
All checks were successful
CI Trade-In / changes (pull_request) Successful in 11s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / changes (pull_request) Successful in 15s
CI / backend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m7s
40eadfd169
build_browser_fetcher получил keyword-only reuse_context (дефолт False — остальные
шесть вызывающих не меняются), и свип домклика зовёт фабрику с reuse_context=True.

Почему. Свип был единственным, кто остался на холодном пути: сайдкар на каждый
/fetch делал browser.new_page() и поднимал новый камуфокс. Прод-замер 17.09,
прогон 7389 (Москва): 600 страниц за 3ч05м, 1 корзина из 6, память сайдкара
426 МиБ → 1.79 ГиБ, в логе «запуск AsyncCamoufox» между почти каждым запросом.
Якорная вкладка (#3118) в таком режиме не переживает фетч, её поднимают заново
через goto(origin), и именно эти goto валятся таймаутом 60с, убивая корзину
целиком — так подряд легли '5+', 'st', '1'. Оба detail-backfill'а давно ходят
с reuse_context=True; там же записан замер: 26 холодных фетчей = 100% блок, те же
карточки в тёплом контексте = 5/5 примерно по 2с.

Упавшая корзина сбрасывает контекст. Тёплый контекст, сгоревший на одной корзине,
иначе переползает в следующую — ровно этот каскад и наблюдался. Сброс стоит в
generic except перед continue, где следующий fetch() его и доставит.

В ветке DomClickBlockedError сброса СОЗНАТЕЛЬНО нет: request_context_reset() лишь
взводит _context_reset_pending, а тот уезжает в сайдкар ключом reset_context
следующего fetch(), которого после break не будет. Остаётся известная дыра —
_contexts[provider] в сайдкаре живёт без TTL, так что сожжённый блоком контекст
достаётся следующему прогону; закрыть её можно только отдельной ручкой сброса
в сайдкаре, это отдельная задача.

Три двойника фетчера в тестах дополнены request_context_reset / reuse_context —
без этого они падали TypeError/AttributeError, то есть проверяли собственную
неполноту вместо поведения свипа.

Сьют: 6473 passed, 74 skipped.
lekss361 merged commit baed8f75a3 into main 2026-09-17 18:10:26 +00:00
lekss361 deleted branch fix/3118-domclick-sweep-warm-context 2026-09-17 18:10:26 +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#3595
No description provided.