Stage 1 of #915. run_avito_pipeline / run_avito_city_sweep acquire a shared
BrowserFetcher when scraper_fetch_mode==browser (instead of injecting a curl
_cffi session); fetch_detail / fetch_house_catalog gain a browser_fetcher param.
Fixes#901's gap where the pipeline bypassed browser-mode. IMV stays on curl
(Stage 3). Default scraper_fetch_mode unchanged.
Refs #883, #901
Avito switched the houses-catalog state blob from JSON.parse("<js-escaped>")
to a URL-encoded string literal, wrapped the 10 widgets in
placeholders[0].widgets, и re-nested developmentData под props.housePage.
JS-unescape path silently failed → houses rows перестали наполняться
(starve'ит #6 same-building + #569 deal centroids).
Extract decode в pure _extract_preloaded_state (urllib.parse.unquote,
JSON.parse fallback для старой формы), add _flatten_widgets, и сделать
_parse_house_page tolerant к обеим developmentData/ratingPreview формам.
Real-HTML fixture (Yumasheva-6 EKB) + 4 regression-теста.
code-reviewer: ✅ APPROVE no critical (regex anchoring verified, decode
safe — no eval/unicode_escape, backward-compat реальный, delisted-house
envelope degrade'ит gracefully, fixture без PII/secrets). Tests: 22 passed.
Closes#566