refactor(tradein/scraper-kit): yandex providers on shared _base.py (#2363) #2384

Merged
lekss361 merged 1 commit from feat/tradein-scraper-kit-f4c-yandex-v2 into main 2026-07-04 06:43:18 +00:00
2 changed files with 24 additions and 14 deletions

View file

@ -11,13 +11,19 @@ proxy_provider + use_pool=config.use_proxy_pool_browser в BrowserFetcher.
- proxy_provider задан + флаг on BrowserFetcher получает провайдер + use_pool=True. - proxy_provider задан + флаг on BrowserFetcher получает провайдер + use_pool=True.
BrowserFetcher монки-патчится на recording-заглушку, так что реального HTTP к BrowserFetcher монки-патчится на recording-заглушку, так что реального HTTP к
tradein-browser не происходит. Yandex SERP всё ещё конструирует BrowserFetcher tradein-browser не происходит.
напрямую в своём модуле патчим `yandex_serp.BrowserFetcher`. Cian SERP
(#2362, F4b) переведён на общую фабрику `scraper_kit.providers._base. После F4-миграции (#2362 F4b cian + #2363 F4c yandex) ОБА скрапера
build_browser_fetcher()` конструктор теперь вызывается ИЗ _base.py, поэтому (`yandex/serp.py::__aenter__` и `cian/serp.py::__aenter__`) больше не конструируют
патчим `BrowserFetcher` там, а не в `cian_serp` (там больше нет своей ссылки на `BrowserFetcher(...)` напрямую оба вызывают shared `build_browser_fetcher()` из
класс). Проверяемый инвариант (какие kwargs долетают до BrowserFetcher) `scraper_kit.providers._base`, который ВНУТРИ себя ссылается на СВОЙ собственный
не меняется меняется только точка патча, отражающая реальное место вызова. импорт `BrowserFetcher`. Патч локального имени `yandex_serp.BrowserFetcher` /
`cian_serp.BrowserFetcher` больше не перехватывает вызов (реальный
`BrowserFetcher.__init__` конструировался бы, а `calls` оставался пуст) поэтому
для обоих провайдеров патчим `scraper_kit.providers._base.BrowserFetcher`
(реальную зависимость `build_browser_fetcher`), сохраняя все assertions как есть.
Проверяемый инвариант (какие kwargs долетают до BrowserFetcher) не меняется
меняется только точка патча, отражающая реальное место вызова.
""" """
from __future__ import annotations from __future__ import annotations
@ -91,7 +97,7 @@ def _config(*, use_pool: bool) -> types.SimpleNamespace:
async def test_yandex_serp_wires_pool_when_flag_on(monkeypatch: pytest.MonkeyPatch) -> None: async def test_yandex_serp_wires_pool_when_flag_on(monkeypatch: pytest.MonkeyPatch) -> None:
"""proxy_provider + флаг on → BrowserFetcher получает провайдер + use_pool=True.""" """proxy_provider + флаг on → BrowserFetcher получает провайдер + use_pool=True."""
rec_fetcher, calls = _make_recorder() rec_fetcher, calls = _make_recorder()
monkeypatch.setattr(yandex_serp, "BrowserFetcher", rec_fetcher) monkeypatch.setattr("scraper_kit.providers._base.BrowserFetcher", rec_fetcher)
provider = _FakeProxyProvider() provider = _FakeProxyProvider()
scraper = yandex_serp.YandexRealtyScraper(_config(use_pool=True), proxy_provider=provider) scraper = yandex_serp.YandexRealtyScraper(_config(use_pool=True), proxy_provider=provider)
@ -108,7 +114,7 @@ async def test_yandex_serp_wires_pool_when_flag_on(monkeypatch: pytest.MonkeyPat
async def test_yandex_serp_no_pool_by_default(monkeypatch: pytest.MonkeyPatch) -> None: async def test_yandex_serp_no_pool_by_default(monkeypatch: pytest.MonkeyPatch) -> None:
"""Без provider/флага → BrowserFetcher(proxy_provider=None, use_pool=False) — ship-dark.""" """Без provider/флага → BrowserFetcher(proxy_provider=None, use_pool=False) — ship-dark."""
rec_fetcher, calls = _make_recorder() rec_fetcher, calls = _make_recorder()
monkeypatch.setattr(yandex_serp, "BrowserFetcher", rec_fetcher) monkeypatch.setattr("scraper_kit.providers._base.BrowserFetcher", rec_fetcher)
scraper = yandex_serp.YandexRealtyScraper(_config(use_pool=False)) scraper = yandex_serp.YandexRealtyScraper(_config(use_pool=False))
async with scraper: async with scraper:
@ -125,7 +131,7 @@ async def test_yandex_serp_provider_without_flag_stays_dark(
) -> None: ) -> None:
"""provider задан, но флаг off → use_pool=False (BrowserFetcher пул не трогает).""" """provider задан, но флаг off → use_pool=False (BrowserFetcher пул не трогает)."""
rec_fetcher, calls = _make_recorder() rec_fetcher, calls = _make_recorder()
monkeypatch.setattr(yandex_serp, "BrowserFetcher", rec_fetcher) monkeypatch.setattr("scraper_kit.providers._base.BrowserFetcher", rec_fetcher)
provider = _FakeProxyProvider() provider = _FakeProxyProvider()
scraper = yandex_serp.YandexRealtyScraper(_config(use_pool=False), proxy_provider=provider) scraper = yandex_serp.YandexRealtyScraper(_config(use_pool=False), proxy_provider=provider)

View file

@ -51,6 +51,7 @@ from scraper_kit.browser_fetcher import BrowserFetcher
from scraper_kit.house_type_normalizer import normalize_house_type from scraper_kit.house_type_normalizer import normalize_house_type
from scraper_kit.price_brackets import get_price_seed_brackets from scraper_kit.price_brackets import get_price_seed_brackets
from scraper_kit.pricing import BisectionConfig, ProbeFailPolicy, ProbeResult, walk_price_range from scraper_kit.pricing import BisectionConfig, ProbeFailPolicy, ProbeResult, walk_price_range
from scraper_kit.providers._base import build_browser_fetcher
if TYPE_CHECKING: if TYPE_CHECKING:
from scraper_kit.contracts import ProxyProvider, ScraperConfig from scraper_kit.contracts import ProxyProvider, ScraperConfig
@ -506,13 +507,16 @@ class YandexRealtyScraper(BaseScraper):
fetch_timeout_s=_YANDEX_BROWSER_FETCH_TIMEOUT_S (30s) ограничивает потери fetch_timeout_s=_YANDEX_BROWSER_FETCH_TIMEOUT_S (30s) ограничивает потери
на один тайм-аутовый запрос: worst-case 30s×(1+retries)=90s на combo вместо 360s. на один тайм-аутовый запрос: worst-case 30s×(1+retries)=90s на combo вместо 360s.
Собрано через shared `build_browser_fetcher()` (#2358 Foundation, #2363
миграция) endpoint/use_pool берутся из `config` mandatory, footgun-класс
#2322/#2330 (пропущенный endpoint/use_pool) закрыт структурно.
""" """
self._browser = BrowserFetcher( self._browser = build_browser_fetcher(
self._config,
source="yandex", source="yandex",
fetch_timeout_s=_YANDEX_BROWSER_FETCH_TIMEOUT_S,
endpoint=self._config.browser_http_endpoint,
proxy_provider=self._proxy_provider, proxy_provider=self._proxy_provider,
use_pool=self._config.use_proxy_pool_browser, fetch_timeout_s=_YANDEX_BROWSER_FETCH_TIMEOUT_S,
) )
await self._browser.__aenter__() await self._browser.__aenter__()
return self return self