fix(msk-collector): ограниченный ретрай goto на таймауте навигации
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 10s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped

Авито изредка держит соединение до упора, goto падает по 90-секундному
таймауту и прогон умирает целиком. В наблюдавшемся случае это был не отказ:
вкладка показывала нормальную выдачу, маркеров фаервола и PoW не было.

Но тихий отказ выглядит так же, поэтому ретрай не слепой: перед каждым
повтором читаем то, что есть в документе, и прогоняем через _guard — реальный
блок останавливает прогон со своей причиной. Исчерпали 3 попытки — жёсткий
стоп с причиной nav_timeout, а не бесконечный повтор.
This commit is contained in:
bot-backend 2026-09-08 23:50:36 +03:00
parent 1e96a6f12d
commit f820c3eac5

View file

@ -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() с узким ретраем на гонку клиентской перенавигации.