From 9b9cc99c118ce080e2ef552d1c7277a5095139ee Mon Sep 17 00:00:00 2001 From: Light1YT Date: Fri, 26 Jun 2026 12:20:13 +0500 Subject: [PATCH] =?UTF-8?q?fix(avito):=20post-merge=20polish=20=E2=80=94?= =?UTF-8?q?=20unify=20fetch=5Fmode=20guard=20+=20empty-render=20guard=20(#?= =?UTF-8?q?1811)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Косметические минор'ы из ревью PR #1809 (avito SERP → BrowserFetcher), функционально без изменений: - __aenter__: settings.scraper_fetch_mode != "curl_cffi" → == "browser" (унификация с scrape_pipeline.py и avito_detail_backfill.py; consistency при гипотетическом 3-м значении режима). - _fetch_serp_html_browser: явный guard `if not html: return None` — пустой рендер (таймаут sidecar'а) больше не утекает как «валидный» HTML; caller (fetch_around) трактует None как конец пагинации. - scrape_pipeline.py: logger.debug на shared-browser sweep-путях — cffi-fallback пропускается (by design), логируем для observability при firewall. - test_avito_browser_mode.py: переформулирован вводящий в заблуждение коммент про _cffi is None (direct _browser assignment bypasses __aenter__). Refs #1811 --- tradein-mvp/backend/app/services/scrape_pipeline.py | 8 ++++++++ tradein-mvp/backend/app/services/scrapers/avito.py | 8 +++++++- tradein-mvp/backend/tests/test_avito_browser_mode.py | 3 ++- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/tradein-mvp/backend/app/services/scrape_pipeline.py b/tradein-mvp/backend/app/services/scrape_pipeline.py index 75e93425..5f75bf97 100644 --- a/tradein-mvp/backend/app/services/scrape_pipeline.py +++ b/tradein-mvp/backend/app/services/scrape_pipeline.py @@ -757,6 +757,11 @@ async def run_avito_city_sweep( scraper = AvitoScraper() if browser_mode: scraper._browser = shared_bf + # Shared-browser режим: _cffi=None → curl_cffi-fallback на + # firewall пропускается (by design). Логируем для observability. + logger.debug( + "avito pipeline-mode: shared browser only, no cffi fallback" + ) else: scraper._cffi = session @@ -1324,6 +1329,9 @@ async def run_avito_newbuilding_sweep( scraper = AvitoScraper() if browser_mode: scraper._browser = shared_bf + # Shared-browser режим: _cffi=None → curl_cffi-fallback на firewall + # пропускается (by design). Логируем для observability. + logger.debug("avito pipeline-mode: shared browser only, no cffi fallback") else: scraper._cffi = session diff --git a/tradein-mvp/backend/app/services/scrapers/avito.py b/tradein-mvp/backend/app/services/scrapers/avito.py index 9e8ec7f6..97633110 100644 --- a/tradein-mvp/backend/app/services/scrapers/avito.py +++ b/tradein-mvp/backend/app/services/scrapers/avito.py @@ -352,7 +352,7 @@ class AvitoScraper(BaseScraper): logger.warning( "avito: proactive IP rotate at sweep start raised — proceeding", exc_info=True ) - if settings.scraper_fetch_mode != "curl_cffi": + if settings.scraper_fetch_mode == "browser": # Default (#1805): SERP-фетч через camoufox (tradein-browser per-provider # + ротирующий backconnect-прокси). curl_cffi поднимаем тут же как # FALLBACK — если браузер отдаёт firewall/пусто, _fetch_serp_html делает @@ -546,6 +546,12 @@ class AvitoScraper(BaseScraper): raise AvitoRateLimitedError( f"avito SERP browser-sidecar error (page={page}): {error_text[:200]}" ) from exc + # Пустой рендер ("" / None) = таймаут рендера sidecar'а, НЕ firewall: + # _is_firewall_page("") вернул бы False → пустой HTML утёк бы как + # «валидный». Возвращаем None — caller (fetch_around) трактует это как + # конец пагинации (break), а не как бан. + if not html: + return None if not _is_firewall_page(html): return html if rot_done < max_rot and await self._rotate_ip(): diff --git a/tradein-mvp/backend/tests/test_avito_browser_mode.py b/tradein-mvp/backend/tests/test_avito_browser_mode.py index 95f3eb74..0311bb64 100644 --- a/tradein-mvp/backend/tests/test_avito_browser_mode.py +++ b/tradein-mvp/backend/tests/test_avito_browser_mode.py @@ -175,7 +175,8 @@ async def test_fetch_serp_html_browser_mode_returns_html() -> None: assert result == _NORMAL_HTML mock_browser.fetch.assert_awaited_once_with("https://www.avito.ru/test") - # _cffi was never set — assert remains None + # _cffi not armed (direct _browser assignment bypasses __aenter__, which would + # otherwise build the curl_cffi fallback session); the browser path never touches it. assert scraper._cffi is None