Volver al blog

Dramaturgo + Navegador de Raspado Sin Residuos: Captura Datos WebSocket en Vivo a Través de CDP

Michael Lee
Michael Lee

Expert Network Defense Engineer

29-Jul-2026

Un ticker de precios en vivo, una tabla de odds de una casa de apuestas y un panel de control en tiempo real comparten una característica: el número en pantalla nunca provino de una recarga de página. El navegador abrió una única conexión WebSocket una vez, y el servidor ha estado enviando cada actualización por ese mismo socket como un marco desde entonces: sin solicitudes repetidas, sin nuevo HTML que analizar. Una herramienta que solo lee el DOM o obtiene HTML nunca ve ese tráfico, porque los datos viven en marcos, no en marcado.

Conecta Playwright al Navegador de Scraping Sin Esfuerzo a través de wss://browser.scrapeless.com/api/v2/browser, y la sesión CDP subyacente te da dos formas separadas de leer esos marcos a medida que llegan: la API de eventos WebSocket de Playwright y el evento crudo Network.webSocketFrameReceived del Protocolo de DevTools de Chrome. Esta guía se conecta a ese navegador en la nube, abre un WebSocket a un flujo de datos del mercado público y sin autenticación, y captura los marcos de ambas maneras, con cada ruta de código ejecutada contra el punto final en vivo.

Por Qué el Tráfico WebSocket Necesita un Camino de Captura Diferente

page.query_selector_all() y llamadas similares leen lo que sea que contenga el DOM en el momento en que las llamas. Un widget alimentado por socket muta su propia visualización nodo por nodo a medida que llegan los marcos, por lo que una única lectura del DOM solo captura el valor que haya aterrizado último. Leer el flujo en sí — los marcos, no los píxeles renderizados — es la única forma de ver cada actualización que un ticker, un flujo de cuotas o un panel de control en vivo realmente recibió.

El Protocolo de DevTools de Chrome expone ese flujo directamente. Su dominio de Red dispara un evento para cada marco que envía o recibe un WebSocket de la página, independiente de lo que la página haga con ese marco después. El Navegador de Scraping Sin Esfuerzo es una sesión de Chromium en la nube accesible solo a través de CDP: Puppeteer, Playwright y otros clientes de CDP se conectan a él, pero no hay un punto final de Selenium/WebDriver para controlarlo en su lugar. Playwright por casualidad expone dos capas de ese mismo evento CDP: un evento de websocket a nivel de página con sus propios callbacks de marco y, un nivel más abajo, acceso directo a la sesión CDP para que puedas suscribirte a Network.webSocketFrameReceived tú mismo.

Requisitos Previos

Necesitas Python 3.9 o superior, el paquete playwright, y una clave API de Scraping Sin Esfuerzo del plan gratuito en app.scrapeless.com. No se requiere un binario local de Chrome: connect_over_cdp accede a un navegador que ya existe en la nube de Scraping Sin Esfuerzo, así que playwright install chromium es opcional aquí.

El ejemplo a continuación abre un WebSocket directamente contra el flujo de datos del mercado spot público de Binance, documentado en Referencia de flujos de mercado WebSocket de Binance. No necesita clave API ni cuenta: es un dato del mercado público y no autenticado. Ambos ejemplos se suscriben al flujo del ticker de 24 horas, que envía una actualización por segundo, y el script en la página cierra el socket tan pronto como llega el quinto marco, por lo que ninguna ejecución mantiene una conexión abierta más tiempo del necesario para demostrar que la captura funciona.

Instalar

bash Copy
pip install playwright
bash Copy
export SCRAPELESS_API_KEY="tu_clave_api_de_scrapeless"

Conectar a través de CDP

Reutiliza el mismo patrón de construcción de URL que cualquier script de Playwright a Navegador de Scraping: tres parámetros de consulta en un único punto final WSS.

python Copy
import os
from urllib.parse import urlencode

API_KEY = os.environ["SCRAPELESS_API_KEY"]

def scraping_browser_url(proxy_country="DE", session_ttl=60):
    params = urlencode({"token": API_KEY, "sessionTTL": session_ttl, "proxyCountry": proxy_country})
    return f"wss://browser.scrapeless.com/api/v2/browser?{params}"

proxyCountry importa aquí por una razón específica a este destino: una conexión enrutada a través de una IP de salida de Estados Unidos falla el apretón de manos contra el flujo de spot de Binance: el socket se abre y luego se cierra inmediatamente con el código de cierre WebSocket 1006. Enrutar a través de Alemania, o la mayoría de otras regiones que no sean de EE. UU., completa el apretón de manos normalmente y los marcos comienzan a llegar. Los ejemplos a continuación fijan proxyCountry en "DE" por esa razón; cámbialo por la región que realmente sirva tu propio objetivo.

Capturar Marcos con los Eventos WebSocket de Playwright

Playwright dispara un evento websocket en el objeto Page cada vez que la página abre un socket, ya sea que ese socket provenga de tu propia llamada page.evaluate() o del propio JavaScript del sitio objetivo. El objeto WebSocket que te entrega dispara framereceived para cada marco entrante y framesent para cada salida.

python Copy
import json
import os
from urllib.parse import urlencode

from playwright.sync_api import sync_playwright

API_KEY = os.environ["SCRAPELESS_API_KEY"]
FRAME_LIMIT = 5

def scraping_browser_url(proxy_country="DE", session_ttl=60):
    params = urlencode({"token": API_KEY, "sessionTTL": session_ttl, "proxyCountry": proxy_country})

devuelve f"wss://browser.scrapeless.com/api/v2/browser?{params}"

marcos = []

def manejar_websocket(ws):
print("websocket abierto:", ws.url)
ws.on("framereceived", lambda payload: marcos.append(payload))

with sync_playwright() as p:
navegador = p.chromium.connect_over_cdp(scraping_browser_url())
pagina = navegador.new_page()
pagina.on("websocket", manejar_websocket)

Copy
pagina.goto("about:blank")
pagina.evaluate(
    f"""() => {{
        window.__frameCount = 0;
        const ws = new WebSocket("wss://stream.binance.com:9443/ws/btcusdt@ticker");
        ws.onmessage = () => {{
            window.__frameCount += 1;
            if (window.__frameCount >= {FRAME_LIMIT}) {{ ws.close(); }}
        }};
        window.__ws = ws;
    }}"""
)
pagina.wait_for_function("window.__ws && window.__ws.readyState === 3", timeout=20000)
pagina.wait_for_timeout(300)
navegador.close()

print(f"capturados {len(marcos)} marcos a través de page.on('websocket')")
print(json.dumps(json.loads(marcos[0]), indent=2))

Capturar Marcos Crudos Desde el Dominio de Red CDP

El evento framereceived de Playwright ya decodifica el marco por ti. Bajando un nivel más, a través de la sesión CDP directamente, se expone el mismo evento que utiliza el panel de la red de DevTools — incluyendo el opcode que indica si un marco es de texto o binario.

python Copy
import base64
import json
import os
from urllib.parse import urlencode

from playwright.sync_api import sync_playwright

API_KEY = os.environ["SCRAPELESS_API_KEY"]
FRAME_LIMIT = 5

def scraping_browser_url(proxy_country="DE", session_ttl=60):
    params = urlencode({"token": API_KEY, "sessionTTL": session_ttl, "proxyCountry": proxy_country})
    return f"wss://browser.scrapeless.com/api/v2/browser?{params}"

cdp_marcos = []

def en_marco(event):
    marco = event["response"]
    payload = marco["payloadData"] if marco["opcode"] == 1 else base64.b64decode(marco["payloadData"])
    cdp_marcos.append((marco["opcode"], payload))

with sync_playwright() as p:
    navegador = p.chromium.connect_over_cdp(scraping_browser_url())
    pagina = navegador.new_page()

    cdp = pagina.context.new_cdp_session(pagina)
    cdp.send("Network.enable")
    cdp.on("Network.webSocketFrameReceived", en_marco)

    pagina.goto("about:blank")
    pagina.evaluate(
        f"""() => {{
            window.__frameCount = 0;
            const ws = new WebSocket("wss://stream.binance.com:9443/ws/btcusdt@ticker");
            ws.onmessage = () => {{
                window.__frameCount += 1;
                if (window.__frameCount >= {FRAME_LIMIT}) {{ ws.close(); }}
            }};
            window.__ws = ws;
        }}"""
    )
    pagina.wait_for_function("window.__ws && window.__ws.readyState === 3", timeout=20000)
    pagina.wait_for_timeout(300)
    navegador.close()

print(f"capturados {len(cdp_marcos)} marcos CDP crudos")
opcode, payload = cdp_marcos[0]
print(f"opcode={opcode}")
print(json.dumps(json.loads(payload), indent=2))

El opcode confirma que estos son marcos de texto, y la carga útil es el esquema de ticker idéntico del otro camino de captura.
page.context.new_cdp_session(page) abre una sesión cuya interfaz de sesión CDP expone send() para comandos de protocolo y on() para eventos de protocolo — Network.enable activa el informe de marcos, y cada evento posterior Network.webSocketFrameReceived se genera con el mismo requestId, timestamp y forma de response que lee la pestaña de red de DevTools. El opcode 1 marca un marco de texto bajo el formato de marco del protocolo WebSocket, por lo que el payload ya es una cadena UTF-8; cualquier otro opcode necesita el paso base64.b64decode(), porque el protocolo transporta marcos binarios como base64 para mantener el JSON del evento seguro.

Lo Que Obtienes de Vuelta

Ambos caminos devuelven la misma carga útil del ticker para este stream, porque ambos están leyendo los mismos marcos subyacentes: uno a través de la abstracción de Playwright, el otro directamente del protocolo.

Campo Tipo Significado
e cadena Tipo de evento ("24hrTicker" para este stream)
E entero Hora del evento, milisegundos desde la época
s cadena Símbolo de trading
c cadena Último precio negociado
o cadena Precio hace 24 horas
h / l cadena Máximo / mínimo de 24 horas
v cadena Volumen de activo base en 24 horas
b / a cadena Mejor oferta / precio de demanda actual
n entero Número de negociaciones en la ventana de 24 horas

Un stream de negociación en la misma conexión (btcusdt@trade en vez de btcusdt@ticker) devuelve un mensaje por cada negociación ejecutada en lugar de un resumen acumulado — cargas útiles más pequeñas, con p (precio), q (cantidad) y t (ID de negociación) en lugar de los campos del ticker mencionados anteriormente, llegando con mucha más frecuencia que una vez por segundo en un par activo. El código de captura no cambia; sólo cambia el nombre del stream en la URL de WebSocket.

Obtén tu clave API en el plan gratuito: app.scrapeless.com

Leyendo Marcos Desde una Página Real En Lugar de un Socket Directo

Cada ejemplo mencionado abre el WebSocket en sí, desde una página de lo contrario en blanco, porque eso mantiene el objetivo pequeño y público. Una tabla de probabilidades en vivo o un panel de intercambio abre su propio socket de la misma manera — desde su propio JavaScript empaquetado, tan pronto como se carga la página — y page.on("websocket") se activa de manera idéntica de cualquier forma. Adjunta el mismo controlador antes de llamar a page.goto() en el objetivo real, y los marcos llegan a medida que el propio script de la página los recibe; nada sobre la lógica de captura cambia porque el socket pertenece a la página en lugar de a tu llamada page.evaluate(). Lo que cambia es el descubrimiento: abre una vez el panel de red del objetivo, localiza la URL del socket y la forma del marco manualmente, y luego traduce eso en un controlador framereceived en lugar de adivinar un nombre de stream de antemano.

Conclusión

Una conexión WebSocket transporta datos que una lectura DOM o una búsqueda HTTP nunca ve, porque el servidor sigue enviando marcos por un socket abierto en lugar de responder solicitudes discretas. El evento page.on("websocket") de Playwright y el evento CDP Network.webSocketFrameReceived en crudo ambos leen ese mismo stream — uno a través de un envoltorio de conveniencia, el otro directamente del protocolo — y ambos funcionaron de manera idéntica contra un verdadero stream de datos de mercado público a través de la conexión CDP del Scrapeless Scraping Browser. Fija proxyCountry a una región que tu objetivo realmente sirva, limita cuántos marcos capturas antes de cerrar el socket, y el resto del script son las pocas llamadas de Playwright que ya conoces. Lee los límites actuales de sesión y salida en la página del producto Scraping Browser y verifica los límites del plan en la página de precios. Para la mecánica de conexión en la que se basa esta guía, el explicador del Protocolo de DevTools de Chrome explica lo que CDP expone más allá del dominio de la red.

Únete a nuestra comunidad para reclamar un plan gratuito y comparar notas con otros desarrolladores que construyen automatización en el navegador: Discord · Telegram.

FAQ

P: ¿Necesito Selenium o WebDriver para capturar marcos WebSocket de esta manera?

No. El Scrapeless Scraping Browser es accesible solo a través del Protocolo de DevTools de Chrome, por lo que cualquier cliente que hable CDP — Playwright aquí, o Puppeteer — puede conectarse y leer eventos de marcos. No hay un endpoint de WebDriver, por lo que Selenium no puede manejar esta conexión.

P: ¿Cuál es la diferencia entre page.on("websocket") y el evento CDP crudo Network.webSocketFrameReceived?
El evento websocket de Playwright y su callback framereceived ya decodifican un marco a una cadena, lo que cubre flujos JSON basados en texto como el de esta guía. El evento CDP subyacente añade el campo opcode, por lo que puedes diferenciar un marco de texto de uno binario antes de decidir cómo decodificarlo.

P: ¿Cómo leo los marcos que envía la página, no solo los que recibe?

El objeto WebSocket de Playwright dispara framesent junto con framereceived, y la sesión CDP dispara el evento correspondiente Network.webSocketFrameSent con el mismo requestId, timestamp, y forma de response que el variante recibida.

P: ¿Qué sucede con los marcos WebSocket binarios?

Un marco binario llega a través de CDP con opcode configurado en algo diferente a 1, y payloadData contiene bytes codificados en base64 en lugar de una cadena UTF-8; decodifícalo con base64.b64decode() antes de analizar cualquier formato binario que utilice el destino. El propio evento framereceived de Playwright pasa los marcos binarios como bytes en lugar de str.

P: ¿Por qué falló la conexión con el código de cierre 1006 cuando proxyCountry era "US"?

Un código de cierre de 1006 significa que la conexión terminó anormalmente, sin un marco de cierre adecuado; en este caso, el apretón de manos con el flujo de Binance nunca se completó cuando la IP de salida estaba en los Estados Unidos. Enviar la misma solicitud a través de un valor diferente de proxyCountry completó el apretón de manos y comenzó a entregar marcos.

P: ¿Puedo capturar tráfico WebSocket de una página en lugar de abrir el socket yo mismo?

Sí. Adjunta el controlador page.on("websocket"), o habilita el dominio Network de CDP, antes de llamar a page.goto() en el objetivo real, y los mismos eventos se disparan para cualquier socket que abra el propio script de la página; nada del código de captura cambia.

P: ¿Cuánto tiempo puede permanecer abierto el WebSocket?

El parámetro de consulta sessionTTL limita la sesión del navegador en segundos. Un valor corto es suficiente para una captura acotada como la de esta guía; uno más largo mantiene la sesión —y cualquier socket abierto— activo por un flujo de mayor duración.

P: ¿Es seguro ejecutar esto contra cualquier punto final WebSocket que encuentre?

Solo contra puntos finales públicos y no autenticados que se te permita leer, y solo a un volumen que la propia documentación del punto final permita. El ejemplo aquí apunta al flujo de datos de mercado público documentado de Binance, no necesita clave, y cierra la conexión después de cinco marcos en lugar de mantenerla abierta indefinidamente.

P: ¿El esquema del marco se mantiene igual a través de diferentes tipos de flujos?

No — el flujo de tickers en esta guía devuelve un resumen móvil de 24 horas una vez por segundo, mientras que un flujo de comercio en la misma conexión devuelve un mensaje más pequeño por comercio ejecutado, a menudo varias veces por segundo. El mecanismo de captura es idéntico de cualquier manera; solo cambia la forma JSON dentro de cada marco con el flujo al que te suscribes.

En Scrapeless, solo accedemos a datos disponibles públicamente y cumplimos estrictamente con las leyes, regulaciones y políticas de privacidad del sitio web aplicables. El contenido de este blog es sólo para fines de demostración y no implica ninguna actividad ilegal o infractora. No ofrecemos garantías y renunciamos a toda responsabilidad por el uso de la información de este blog o enlaces de terceros. Antes de realizar cualquier actividad de scraping, consulte a su asesor legal y revise los términos de servicio del sitio web de destino u obtenga los permisos necesarios.

Artículos más populares

Catalogar