From f820c3eac58eb41a13e4fe63f4a482fe8ec769b5 Mon Sep 17 00:00:00 2001 From: bot-backend Date: Tue, 8 Sep 2026 23:50:36 +0300 Subject: [PATCH] =?UTF-8?q?fix(msk-collector):=20=D0=BE=D0=B3=D1=80=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D1=87=D0=B5=D0=BD=D0=BD=D1=8B=D0=B9=20=D1=80=D0=B5?= =?UTF-8?q?=D1=82=D1=80=D0=B0=D0=B9=20goto=20=D0=BD=D0=B0=20=D1=82=D0=B0?= =?UTF-8?q?=D0=B9=D0=BC=D0=B0=D1=83=D1=82=D0=B5=20=D0=BD=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=B3=D0=B0=D1=86=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Авито изредка держит соединение до упора, goto падает по 90-секундному таймауту и прогон умирает целиком. В наблюдавшемся случае это был не отказ: вкладка показывала нормальную выдачу, маркеров фаервола и PoW не было. Но тихий отказ выглядит так же, поэтому ретрай не слепой: перед каждым повтором читаем то, что есть в документе, и прогоняем через _guard — реальный блок останавливает прогон со своей причиной. Исчерпали 3 попытки — жёсткий стоп с причиной nav_timeout, а не бесконечный повтор. --- .../scripts/local-avito-msk/collect.py | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/tradein-mvp/scripts/local-avito-msk/collect.py b/tradein-mvp/scripts/local-avito-msk/collect.py index 7a2ff186..db6ddb6a 100644 --- a/tradein-mvp/scripts/local-avito-msk/collect.py +++ b/tradein-mvp/scripts/local-avito-msk/collect.py @@ -60,6 +60,11 @@ _BATCH_ID_RE = re.compile(r"^[A-Za-z0-9._-]+$") _POW_MARKERS = ("startpow", "доступ ограничен: проверка безопасности") +# Заполняется при входе в Loader.__aenter__ (playwright импортируется лениво, +# чтобы --help работал без установленного пакета). +PlaywrightTimeoutError: type[BaseException] = TimeoutError + + class Blocked(Exception): """Первый признак блока. Ретраев нет — только немедленный стоп.""" @@ -212,6 +217,10 @@ class Loader: async def __aenter__(self) -> "Loader": from playwright.async_api import async_playwright + from playwright.async_api import TimeoutError as _PwTimeout + + global PlaywrightTimeoutError + PlaywrightTimeoutError = _PwTimeout endpoint = os.environ.get("AVITO_CDP", "http://localhost:9222") self._pw = await async_playwright().start() @@ -262,7 +271,7 @@ class Loader: if not self.budget_left(): raise BudgetExhausted() await self._pause() - resp = await self._page.goto(url, wait_until="domcontentloaded", timeout=90_000) + resp = await self._goto(url) self.loads += 1 self._last_load = time.monotonic() status = resp.status if resp is not None else None @@ -274,6 +283,33 @@ class Loader: _guard(html, status) return html, status + async def _goto(self, url: str, attempts: int = 3): + """goto с ограниченным ретраем на таймаут навигации. + + Авито изредка держит соединение до упора и goto падает по timeout. Это + НЕ признак отказа: в наблюдавшемся случае вкладка показывала нормальную + выдачу, а маркеров фаервола/PoW не было. Но и молча ретраить бесконечно + нельзя — тихий отказ выглядит ровно так же. Поэтому: перед каждым + повтором пробуем прочитать то, что в документе, и прогнать через _guard, + чтобы настоящий блок остановил прогон с правильной причиной; исчерпали + попытки — жёсткий стоп с причиной nav_timeout. + """ + for i in range(attempts): + try: + return await self._page.goto(url, wait_until="domcontentloaded", + timeout=90_000) + except PlaywrightTimeoutError: + try: + partial = await self._page.content() + except Exception: # noqa: BLE001 — документа может не быть вовсе + partial = "" + if partial: + _guard(partial, None) # настоящий блок остановит прогон здесь + if i == attempts - 1: + raise Blocked("nav_timeout") from None + print(f" таймаут навигации, попытка {i + 2}/{attempts}", flush=True) + await asyncio.sleep(10.0) + async def _read_content(self, attempts: int = 4) -> str: """page.content() с узким ретраем на гонку клиентской перенавигации. -- 2.45.3