import aiohttp import websockets import json import asyncio from .Tab import Tab from typing import Optional class Scrapper: def __init__(self, debugging_url: str = "http://127.0.0.1:9222"): self.debugging_url = debugging_url self.tabs: list[Tab] = [] async def _crear_tab_websocket_url(self, target_url: str = "about:blank") -> str: """ Crea una nueva pestaña usando el método oficial Target.createTarget y devuelve su WebSocketDebuggerUrl. """ # 1. Obtener el WebSocket general del browser (root) async with aiohttp.ClientSession() as session: async with session.get(f"{self.debugging_url}/json/version") as resp: if resp.status != 200: raise RuntimeError("No se pudo obtener información del navegador") data = await resp.json() browser_ws_url = data["webSocketDebuggerUrl"] # 2. Conectarse al WebSocket del browser async with websockets.connect(browser_ws_url) as websocket: # 3. Enviar comando para crear target msg_id = 1 await websocket.send(json.dumps({ "id": msg_id, "method": "Target.createTarget", "params": { "url": target_url, "newWindow": False } })) # 4. Esperar respuesta con el targetId while True: respuesta = await websocket.recv() data = json.loads(respuesta) if data.get("id") == msg_id: target_id = data["result"]["targetId"] break # 5. Esperar a que el target aparezca en /json for _ in range(30): # máximo ~3 segundos await asyncio.sleep(0.1) async with aiohttp.ClientSession() as session: async with session.get(f"{self.debugging_url}/json") as resp: if resp.status == 200: tabs = await resp.json() for tab in tabs: if tab.get("id") == target_id: return tab["webSocketDebuggerUrl"] raise RuntimeError("No se pudo obtener el WebSocket de la nueva pestaña") async def nueva_tab(self, url: str = "", wait_time: float = 5.0) -> Tab: websocket_url = await self._crear_tab_websocket_url() tab = await Tab.crear_desde_websocket(websocket_url) self.tabs.append(tab) if url: print(f"🌍 Navegando a: {url}") await tab.navegar(url, wait_time) else: print("⚠️ No se especificó URL. La pestaña se creó pero no se navegó a ninguna página.") return tab async def cerrar_todos(self): for tab in list(self.tabs): await tab.cerrar() self.tabs.clear() def get_tab(self, identifier: str) -> Optional[Tab]: """ Devuelve una instancia de Tab según su WebSocket URL o su ID final (extraído del WebSocket URL). Acepta: - ws_url completo: ws://127.0.0.1:9222/devtools/page/XYZ - id directo: XYZ """ for tab in self.tabs: # Comparar directamente contra ws_url if tab.ws_url == identifier: return tab # Comparar contra el ID extraído ws_id = tab.ws_url.rsplit("/", 1)[-1] if ws_id == identifier: return tab return None async def obtener_tabs_existentes(self) -> list[Tab]: """ Recupera todas las pestañas de tipo 'page' que no están ya en self.tabs, las conecta y devuelve como lista. Muestra resumen limpio por consola. """ async with aiohttp.ClientSession() as session: async with session.get(f"{self.debugging_url}/json") as resp: if resp.status != 200: raise RuntimeError("No se pudo obtener la lista de pestañas") tabs_info = await resp.json() print("\n🧾 Pestañas activas (filtradas: solo 'type': 'page'):\n") nuevas_tabs = [] for idx, tab_info in enumerate(tabs_info, start=1): tipo = tab_info.get("type") if tipo != "page": continue # Filtrar todo lo que no sea página visible ws_url = tab_info.get("webSocketDebuggerUrl") tab_id = tab_info.get("id") title = tab_info.get("title", "") url = tab_info.get("url", "") # Verifica si ya la tienes cargada if any(t.ws_url == ws_url for t in self.tabs): continue # Conectar try: tab = await Tab.crear_desde_websocket(ws_url) self.tabs.append(tab) nuevas_tabs.append(tab) except Exception as e: print(f"⚠️ No se pudo conectar a pestaña {tab_id}: {e}") if not nuevas_tabs: print("⚠️ No se encontraron nuevas pestañas para agregar.\n") return nuevas_tabs