import asyncio from collections import deque from datetime import datetime import time from playwright.async_api import Page class NetworkIdleWaiter: """ Класс для продвинутого ожидания. Запускает таймер и сбрасывает его после каждого загруженного файла. Спустя заданное время после последней активности разблокирует поток. """ def __init__( self, page: Page, idle_time: float = 1.0, timeout: float = 10.0, ignore_resources: list = None, print_on: bool = False, ): self.page = page self.idle_time = idle_time self.timeout = timeout self.ignore_resources = ignore_resources or ['font', 'websocket'] self.pending_requests = {} self.last_activity = time.time() self.network_log = deque(maxlen=100) # Последние 100 запросов для отладки self.print_on = print_on async def wait(self): """Ждет пока сеть станет неактивной""" # Подписываемся на события self.page.on("request", self._on_request) self.page.on("response", self._on_response) self.page.on("requestfailed", self._on_request_failed) start_time = time.time() try: while True: await asyncio.sleep(0.1) current_time = time.time() idle_duration = current_time - self.last_activity # Проверяем условия выхода if len(self.pending_requests) == 0 and idle_duration >= self.idle_time: if self.print_on: print(f"✅ Сеть неактивна {idle_duration:.1f} сек. " f"Загрузка завершена!") break # Проверка таймаута if current_time - start_time > self.timeout: if self.print_on: print(f"⚠️ Таймаут {self.timeout} сек. " f"Остались запросы: {len(self.pending_requests)}") if self.pending_requests: print("Незавершенные запросы:") for url, info in list(self.pending_requests.items())[:5]: print(f" - {info['type']}: {url[:100]}...") break # Отладочная информация if self.print_on: if len(self.pending_requests) > 0: print(f"⏳ Ожидание... Активных запросов: {len(self.pending_requests)}, " f"Idle: {idle_duration:.1f}s") finally: # Отписываемся от событий self.page.remove_listener("request", self._on_request) self.page.remove_listener("response", self._on_response) self.page.remove_listener("requestfailed", self._on_request_failed) def get_stats(self): """Получить статистику по сетевой активности""" return { 'total_requests': len(self.network_log), 'pending': len(self.pending_requests), 'last_activity': self.last_activity, 'log': list(self.network_log) } # Обработчики событий def _on_request(self, request): # Игнорируем определенные типы ресурсов if request.resource_type in self.ignore_resources: return self.pending_requests[request.url] = { 'start': time.time(), 'type': request.resource_type, 'method': request.method } self.last_activity = time.time() self.network_log.append({ 'event': 'request', 'url': request.url, 'type': request.resource_type, 'time': datetime.now().isoformat() }) def _on_response(self, response): if response.url in self.pending_requests: request_info = self.pending_requests.pop(response.url) duration = time.time() - request_info['start'] self.network_log.append({ 'event': 'response', 'url': response.url, 'status': response.status, 'duration': duration, 'time': datetime.now().isoformat() }) self.last_activity = time.time() def _on_request_failed(self, request): if request.url in self.pending_requests: self.pending_requests.pop(request.url) self.network_log.append({ 'event': 'failed', 'url': request.url, 'failure': request.failure, 'time': datetime.now().isoformat() }) self.last_activity = time.time()