diff --git a/tradein-mvp/packages/scraper-kit/src/scraper_kit/providers/avito/detail.py b/tradein-mvp/packages/scraper-kit/src/scraper_kit/providers/avito/detail.py index de358d46..9147a730 100644 --- a/tradein-mvp/packages/scraper-kit/src/scraper_kit/providers/avito/detail.py +++ b/tradein-mvp/packages/scraper-kit/src/scraper_kit/providers/avito/detail.py @@ -40,6 +40,7 @@ from scraper_kit.avito_exceptions import ( AvitoRateLimitedError, ) from scraper_kit.geo import is_within_ekb_bbox_wide +from scraper_kit.providers._base import build_document_session from scraper_kit.providers.avito.serp import _clean_address, _is_firewall_page from scraper_kit.providers.avito.shared import RUS_MONTHS from scraper_kit.repair_state_normalizer import infer_repair_state_from_text @@ -265,22 +266,7 @@ def _build_detail_session(config: ScraperConfig | None = None) -> AsyncSession: (scraper_kit.contracts) вместо прямого импорта app.core.config.settings. """ proxy_url = config.scraper_proxy_url if config is not None else None - proxies = {"http": proxy_url, "https": proxy_url} if proxy_url else None - return AsyncSession( - impersonate="chrome120", - timeout=25, - proxies=proxies, - headers={ - "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", - "Accept-Language": "ru-RU,ru;q=0.9,en;q=0.8", - "Cache-Control": "max-age=0", - "Sec-Fetch-Dest": "document", - "Sec-Fetch-Mode": "navigate", - "Sec-Fetch-Site": "none", - "Sec-Fetch-User": "?1", - "Upgrade-Insecure-Requests": "1", - }, - ) + return build_document_session(proxy_url=proxy_url, timeout=25) async def warm_up_session(session: AsyncSession) -> bool: diff --git a/tradein-mvp/packages/scraper-kit/src/scraper_kit/providers/avito/imv.py b/tradein-mvp/packages/scraper-kit/src/scraper_kit/providers/avito/imv.py index 69173da2..ec688bd9 100644 --- a/tradein-mvp/packages/scraper-kit/src/scraper_kit/providers/avito/imv.py +++ b/tradein-mvp/packages/scraper-kit/src/scraper_kit/providers/avito/imv.py @@ -483,6 +483,23 @@ async def evaluate_via_imv( # seed anti-bot cookies — bare-session XHR Avito банит на server-IP. # Прокси: пул за флагом use_proxy_pool_curl (#2163), иначе env # scraper_proxy_url. proxy=None → прямое подключение (dev). + # + # NB (#2361 F4a): НЕ мигрировано на build_document_session() из + # scraper_kit.providers._base, хотя headers=_DOC_HEADERS — тот же + # дублированный dict, что и там. _base.py делает module-level + # `from curl_cffi.requests import AsyncSession` (биндится ОДИН раз + # при первом импорте _base) — tests/scrapers/test_avito_imv_kit_parity.py + # (#2334) патчит `curl_cffi.requests.AsyncSession` через unittest.mock, + # что работает ТОЛЬКО если конструктор вызывается через СВЕЖИЙ lookup + # атрибута на живом module-объекте (как этот локальный import ниже), + # а не через имя, захваченное в чужом module namespace на момент его + # импорта. Мигрировав, ловим 2 real failures + # (test_kit_evaluate_via_imv_{with,without}_config_uses_proxy) — НЕ + # тестовый шум, тест был зелёным на main. Production-поведение + # идентично в обоих вариантах (сеть не отличает откуда взят класс), + # но так как таск прямо запрещает "weaken or skip" parity-тесты — + # оставляю это единственное место немигрированным и репортю как + # finding, а не тихо чиню тест. _env = config.scraper_proxy_url if config is not None else None _proxy_url = _proxy_stack.enter_context( curl_proxy_url(config, proxy_provider, "avito", env_fallback_url=_env) diff --git a/tradein-mvp/packages/scraper-kit/src/scraper_kit/providers/avito/serp.py b/tradein-mvp/packages/scraper-kit/src/scraper_kit/providers/avito/serp.py index 4c0c8bf7..e52a6a84 100644 --- a/tradein-mvp/packages/scraper-kit/src/scraper_kit/providers/avito/serp.py +++ b/tradein-mvp/packages/scraper-kit/src/scraper_kit/providers/avito/serp.py @@ -47,6 +47,7 @@ from scraper_kit.base import BaseScraper, ScrapedLot from scraper_kit.browser_fetcher import BrowserFetcher from scraper_kit.price_brackets import get_price_seed_brackets from scraper_kit.pricing import BisectionConfig, ProbeFailPolicy, ProbeResult, walk_price_range +from scraper_kit.providers._base import build_browser_fetcher, build_document_session from scraper_kit.providers.avito.shared import _unix_to_date if TYPE_CHECKING: @@ -386,9 +387,7 @@ class AvitoScraper(BaseScraper): # FALLBACK — если браузер отдаёт firewall/пусто, _fetch_serp_html делает # один retry через curl_cffi. _build_cffi_session самодостаточен # (отдельный AsyncSession, не зависит от BaseScraper.__aenter__). - self._browser = BrowserFetcher( - source="avito", endpoint=self._config.browser_http_endpoint - ) + self._browser = build_browser_fetcher(self._config, "avito") await self._browser.__aenter__() self._cffi = self._build_cffi_session() logger.info( @@ -407,25 +406,9 @@ class AvitoScraper(BaseScraper): Вынесено из __aenter__ чтобы _reset_cffi мог пересоздать сессию (новый CONNECT-туннель) для escape 403 на залоченном backconnect exit-IP. """ - proxies = None if self._config.scraper_proxy_url: - proxies = {"http": self._config.scraper_proxy_url, "https": self._config.scraper_proxy_url} logger.info("avito: routing through mobile proxy egress (#623)") - return AsyncSession( - impersonate="chrome120", - timeout=25, - proxies=proxies, - headers={ - "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", - "Accept-Language": "ru-RU,ru;q=0.9,en;q=0.8", - "Cache-Control": "max-age=0", - "Sec-Fetch-Dest": "document", - "Sec-Fetch-Mode": "navigate", - "Sec-Fetch-Site": "none", - "Sec-Fetch-User": "?1", - "Upgrade-Insecure-Requests": "1", - }, - ) + return build_document_session(proxy_url=self._config.scraper_proxy_url, timeout=25) async def _reset_cffi(self) -> None: """Закрыть и пересоздать curl_cffi-сессию — новый CONNECT-туннель через