fix watchdog

This commit is contained in:
qananasikq
2026-04-23 09:56:18 +03:00
parent d5d6ba8b7c
commit 09fecdae31
3 changed files with 105 additions and 381 deletions

View File

@@ -11,6 +11,7 @@ from concurrent.futures import ThreadPoolExecutor, as_completed
from concurrent.futures import TimeoutError as FuturesTimeoutError
from datetime import datetime, timezone
from pathlib import Path
from threading import Event, Thread
from typing import Any, Callable
from urllib.parse import urlsplit, urlunsplit
from urllib.request import Request, urlopen
@@ -97,6 +98,33 @@ class _PageOpWatchdog:
return False
class _ProgressHeartbeat:
"""Периодически пульсует progress во время долгой навигации."""
def __init__(self, report_progress: Callable[[str, Any], None], stage: str, interval_s: float = 15.0, **meta: Any) -> None:
self._report_progress = report_progress
self._stage = stage
self._interval_s = max(5.0, float(interval_s))
self._meta = meta
self._stop = Event()
self._thread: Thread | None = None
def _run(self) -> None:
while not self._stop.wait(self._interval_s):
self._report_progress(self._stage, **self._meta)
def __enter__(self):
self._thread = Thread(target=self._run, name=f"progress-heartbeat-{self._stage}", daemon=True)
self._thread.start()
return self
def __exit__(self, exc_type, exc, tb):
self._stop.set()
if self._thread is not None:
self._thread.join(timeout=1.0)
return False
class IAAIScraper:
@staticmethod
@@ -453,16 +481,23 @@ class IAAIScraper:
)
for expected_page in range(2, target_page_number + 1):
# Heartbeat для watchdog: при deep-resume (100+ кликов) задача может
# идти несколько минут без batch_upserted, поэтому пульсуем прогресс.
if expected_page == 2 or expected_page == target_page_number or expected_page % 5 == 0:
self._report_progress(
"listing_resume_progress",
current_page=expected_page - 1,
target_page=target_page_number,
)
self._report_progress(
"listing_resume_progress",
current_page=expected_page - 1,
target_page=target_page_number,
next_page_number=expected_page,
)
if not self.listing_collector.go_to_next_page(page, expected_page_number=expected_page):
with _ProgressHeartbeat(
self._report_progress,
"listing_resume_progress",
current_page=expected_page - 1,
target_page=target_page_number,
next_page_number=expected_page,
):
next_ok = self.listing_collector.go_to_next_page(page, expected_page_number=expected_page)
if not next_ok:
self._report_progress(
"listing_resume_failed",
current_page=expected_page - 1,
@@ -471,6 +506,13 @@ class IAAIScraper:
page.close()
raise ListingResumeError(f"Failed to resume listing at page {target_page_number}")
self._report_progress(
"listing_resume_progress",
current_page=expected_page,
target_page=target_page_number,
next_page_number=min(target_page_number, expected_page + 1),
)
self._report_progress(
"listing_resume_completed",
current_page=target_page_number,
@@ -973,8 +1015,17 @@ class IAAIScraper:
cars_failed=cars_failed,
)
try:
with _PageOpWatchdog(_PAGE_NEXT_TIMEOUT_S, f"next page {page_number + 1}"):
next_ok = self.listing_collector.go_to_next_page(page, expected_page_number=page_number + 1)
with _ProgressHeartbeat(
self._report_progress,
"listing_next_page_started",
page_number=page_number,
next_page_number=page_number + 1,
pending_urls=len(pending_urls),
cars_upserted=cars_upserted,
cars_failed=cars_failed,
):
with _PageOpWatchdog(_PAGE_NEXT_TIMEOUT_S, f"next page {page_number + 1}"):
next_ok = self.listing_collector.go_to_next_page(page, expected_page_number=page_number + 1)
except (PageOperationTimeoutError, PlaywrightError) as nav_exc:
logger.warning(
"go_to_next_page stalled/failed at page %d (%s) — will reopen",