🎯 Um navegador em nuvem personalizável e anti-detecção alimentado por Chromium desenvolvido internamente, projetado para rastreadores web e agentes de IA. 👉Experimente agora
De volta ao blog

Dramaturgo + Navegador de Scraping Sem Resíduos: Capturar Dados WebSocket ao Vivo Através do CDP

Michael Lee
Michael Lee

Expert Network Defense Engineer

29-Jul-2026

Um ticker de preço ao vivo, um painel de odds de uma casa de apostas e um painel em tempo real compartilham uma característica: o número na tela nunca veio de uma recarga de página. O navegador abriu uma única conexão WebSocket uma vez, e o servidor tem enviado cada atualização por esse mesmo socket como um frame desde então — sem requisições repetidas, sem novo HTML para analisar. Uma ferramenta que apenas lê o DOM ou busca HTML nunca vê esse tráfego, porque os dados vivem em frames, não em marcação.

Conecte o Playwright ao Scrapeless Scraping Browser via wss://browser.scrapeless.com/api/v2/browser, e a sessão CDP por trás disso lhe dá duas maneiras separadas de ler esses frames à medida que chegam: a própria API de eventos WebSocket do Playwright e o evento bruto Network.webSocketFrameReceived do Chrome DevTools Protocol. Este guia se conecta a esse navegador na nuvem, abre um WebSocket para um fluxo de dados de mercado público, sem autenticação, e captura os frames de ambas as maneiras, com cada caminho de código executado contra o endpoint ao vivo.

Por Que o Tráfego WebSocket Precisa de um Caminho de Captura Diferente

page.query_selector_all() e chamadas semelhantes leem o que quer que o DOM contenha no momento em que são chamadas. Um widget alimentado por socket muta seu próprio nó de exibição à medida que os frames chegam, então uma única leitura do DOM captura apenas o valor que aconteceu de aterrissar por último. Ler o fluxo em si — os frames, não os pixels renderizados — é a única maneira de ver todas as atualizações que um ticker, um fluxo de odds ou um painel ao vivo realmente recebeu.

O Chrome DevTools Protocol expõe esse fluxo diretamente. Seu domínio Network dispara um evento para cada frame que o WebSocket de uma página envia ou recebe, independentemente do que a página faz com esse frame depois. O Scrapeless Scraping Browser é uma sessão Chromium na nuvem acessível apenas via CDP — Puppeteer, Playwright e outros clientes CDP se conectam a ele, mas não existe um endpoint Selenium/WebDriver para operá-lo. O Playwright acontece de expor duas camadas desse mesmo evento CDP: um evento websocket no nível da página com seus próprios callbacks de frame e, um nível abaixo, acesso direto à sessão CDP para que você possa assinar Network.webSocketFrameReceived você mesmo.

Pré-requisitos

Você precisa do Python 3.9 ou mais recente, do pacote playwright e de uma chave de API Scrapeless do plano gratuito em app.scrapeless.com. Nenhum binário local do Chrome é necessário — connect_over_cdp alcança um navegador que já existe na nuvem da Scrapeless, então playwright install chromium é opcional aqui.

O exemplo abaixo abre um WebSocket diretamente contra o fluxo de dados de mercado público da Binance, documentado em referência dos streams de mercado WebSocket da Binance. Ele não precisa de chave de API e nem de conta — é um dados de mercado público e não autenticada. Ambos os exemplos se inscrevem no fluxo de ticker de 24 horas, que envia uma atualização por segundo, e o script na página fecha o socket assim que o quinto frame chega, então nenhuma execução mantém uma conexão aberta por mais tempo do que o necessário para provar que a captura funciona.

Instalar

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

Conectar Via CDP

Reutilize o mesmo padrão de construtor de URL que qualquer script Playwright-para-Browser-de-Raspagem utiliza: três parâmetros de consulta em um endpoint 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 é importante aqui por um motivo específico para este alvo: uma conexão roteada através de um IP de saída dos Estados Unidos falha no handshake contra o fluxo spot da Binance — o socket abre e então fecha imediatamente com o código de fechamento do WebSocket 1006. Roteando através da Alemanha, ou a maioria de outras regiões fora dos EUA, completa o handshake normalmente e os frames começam a chegar. Os exemplos abaixo fixam proxyCountry como "DE" por esse motivo; troque-o pela região que seu próprio alvo realmente atende.

Capturar Frames Com os Eventos WebSocket do Playwright

O Playwright dispara um evento websocket no objeto Page sempre que a página abre um socket, seja esse socket proveniente da sua própria chamada page.evaluate() ou do próprio JavaScript do site alvo. O objeto WebSocket que ele lhe entrega dispara framereceived para cada frame recebido e framesent para cada um enviado.

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})
python Copy
return f"wss://browser.scrapeless.com/api/v2/browser?{params}"

frames = []

def handle_websocket(ws):
    print("websocket aberto:", ws.url)
    ws.on("framereceived", lambda payload: frames.append(payload))

with sync_playwright() as p:
    browser = p.chromium.connect_over_cdp(scraping_browser_url())
    page = browser.new_page()
    page.on("websocket", handle_websocket)

    page.goto("about:blank")
    page.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;
        }}"""
    )
    page.wait_for_function("window.__ws && window.__ws.readyState === 3", timeout=20000)
    page.wait_for_timeout(300)
    browser.close()

print(f"capturou {len(frames)} frames via page.on('websocket')")
print(json.dumps(json.loads(frames[0]), indent=2))

Cinco atualizações reais de ticker retornam, cada uma uma mensagem JSON completa:

text Copy
websocket aberto: wss://stream.binance.com:9443/ws/btcusdt@ticker
capturou 5 frames via page.on('websocket')
{
  "e": "24hrTicker",
  "E": 1785154788015,
  "s": "BTCUSDT",
  "p": "516.30000000",
  "P": "0.800",
  "w": "65153.97639938",
  "x": "64558.24000000",
  "c": "65074.53000000",
  "Q": "0.00022000",
  "b": "65074.53000000",
  "B": "0.00376000",
  "a": "65074.54000000",
  "A": "6.01063000",
  "o": "64558.23000000",
  "h": "65744.60000000",
  "l": "64414.00000000",
  "v": "12430.45583000",
  "q": "809893625.78133730",
  "O": 1785068388012,
  "C": 1785154788012,
  "F": 6534229591,
  "L": 6536075962,
  "n": 1846372
}

O manipulador onmessage da página conta os frames por conta própria e chama ws.close() assim que o quinto chega, então o limite é aplicado na fonte, em vez de ser feito por polling a partir do Python — um stream que empurra atualizações mais rápido do que uma vez por segundo permitiria que vários frames extras chegassem antes que uma verificação do lado do Python se atualizasse. page.wait_for_function então bloqueia até que readyState reporte fechado. about:blank mais page.evaluate é suficiente aqui porque o alvo é o socket, não a marcação de uma página; page.on("websocket") dispara da mesma forma quando uma página real abre a conexão através de seu próprio script.

Capturar Frames Brutos do Domínio de Rede CDP

O evento framereceived do Playwright já decodifica o frame para você. Indo um nível mais baixo, através da sessão CDP diretamente, expõe o mesmo evento que o painel de Rede do DevTools usa — incluindo o opcode que diz se um frame é texto ou binário.

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_frames = []

def on_frame(event):
    frame = event["response"]
    payload = frame["payloadData"] if frame["opcode"] == 1 else base64.b64decode(frame["payloadData"])
    cdp_frames.append((frame["opcode"], payload))

with sync_playwright() as p:
    browser = p.chromium.connect_over_cdp(scraping_browser_url())
    page = browser.new_page()

    cdp = page.context.new_cdp_session(page)
    cdp.send("Network.enable")
    cdp.on("Network.webSocketFrameReceived", on_frame)

    page.goto("about:blank")
    page.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;
        }}"""
    )
    page.wait_for_function("window.__ws && window.__ws.readyState === 3", timeout=20000)
    page.wait_for_timeout(300)
    browser.close()

print(f"capturou {len(cdp_frames)} frames brutos do CDP")
opcode, payload = cdp_frames[0]
print(f"opcode={opcode}")
print(json.dumps(json.loads(payload), indent=2))

O opcode confirma que estes são frames de texto, e o payload é o esquema de ticker idêntico do outro caminho de captura:

text Copy
capturou 5 frames brutos do CDP
opcode=1
{
  "e": "24hrTicker",
  "E": 1785154852015,
  "s": "BTCUSDT",
  "p": "525.37000000",
  "P": "0.814",
  "w": "65154.69931666",
  "x": "64529.61000000",
  "c": "65054.99000000",
  "Q": "0.00430000",
  "b": "65054.99000000",
  "B": "4.56302000",
  "a": "65055.00000000",
  "A": "1.44666000",
  "o": "64529.62000000",
  "h": "65744.60000000",
  "l": "64414.00000000",
  "v": "12421.62669000",
  "q": "809327352.01071850",
  "O": 1785068452012,
  "C": 1785154852012,
  "F": 6534232202,
  "L": 6536078668,
  "n": 1846467
}

page.context.new_cdp_session(page) abre uma sessão cuja interface de sessão CDP expõe send() para comandos de protocolo e on() para eventos de protocolo — Network.enable ativa o relatório de frames, e cada evento subsequente Network.webSocketFrameReceived é disparado com o mesmo requestId, timestamp e a forma de response que a aba de Rede do DevTools lê. O código de operação 1 marca um frame de texto no formato de frame próprio do protocolo WebSocket, então o payload já é uma string UTF-8; qualquer outro código de operação precisa da etapa base64.b64decode(), porque o protocolo transporta frames binários como base64 para manter todo o evento seguro em formato JSON.

O que você recebe de volta

Ambos os caminhos retornam o mesmo payload de ticker para este fluxo, porque ambos estão lendo os mesmos frames subjacentes — um através da abstração do Playwright, e o outro diretamente do protocolo.

Campo Tipo Significado
e string Tipo de evento ("24hrTicker" para este fluxo)
E inteiro Hora do evento, milissegundos da época
s string Símbolo de negociação
c string Último preço negociado
o string Preço há 24 horas
h / l string Alta / baixa de 24 horas
v string Volume do ativo base de 24 horas
b / a string Melhor lance / preço de venda atual
n inteiro Número de negociações na janela de 24 horas

Um fluxo de negociações na mesma conexão (btcusdt@trade em vez de btcusdt@ticker) retorna uma mensagem por negociação executada em vez de um resumo contínuo — payloads menores, com p (preço), q (quantidade) e t (ID da negociação) em lugar dos campos de ticker acima, chegando muito mais frequentemente do que uma vez por segundo em um par ativo. O código de captura não muda; apenas o nome do fluxo na URL do WebSocket muda.

Obtenha sua chave de API no plano gratuito: app.scrapeless.com

Lendo Frames de uma Página Real em vez de um Socket Direto

Todos os exemplos acima abrem o WebSocket em si, a partir de uma página em branco, porque isso mantém o alvo pequeno e público. Um quadro de odds ao vivo ou um dashboard de exchange abre seu próprio socket da mesma maneira — a partir de seu próprio JavaScript incorporado, assim que a página carrega — e page.on("websocket") dispara de forma idêntica, de qualquer forma. Anexe o mesmo manipulador antes de chamar page.goto() no alvo real, e os frames chegam à medida que o script da página os recebe; nada sobre a lógica de captura muda porque o socket pertence à página em vez de à sua chamada page.evaluate(). O que muda é a descoberta — abra o painel de Rede do alvo uma vez, localize a URL do socket e a forma de frame à mão, e então traduza isso em um manipulador framereceived em vez de adivinhar um nome de fluxo com antecedência.

Conclusão

Uma conexão WebSocket transporta dados que uma leitura DOM ou um fetch HTTP nunca vê, porque o servidor continua enviando frames por um socket aberto em vez de responder a solicitações discretas. O evento page.on("websocket") do Playwright e o evento bruto Network.webSocketFrameReceived do CDP leem o mesmo fluxo — um através de um wrapper de conveniência, e o outro diretamente do protocolo — e ambos funcionaram de forma idêntica contra um verdadeiro fluxo de dados de mercado público através da conexão CDP do Scrapeless Scraping Browser. Defina proxyCountry para uma região que seu alvo realmente atende, limite quantos frames você captura antes de fechar o socket, e o resto do script são as poucas chamadas do Playwright que você já conhece. Leia os limites de sessão e saída atuais na página do produto Scraping Browser e verifique os limites do plano na página de preços. Para a mecânica de conexão que este guia baseia-se, o explicador do Protocolo Chrome DevTools detalha o que o CDP expõe além do domínio de Rede.

Junte-se à nossa comunidade para reivindicar um plano gratuito e trocar anotações com outros desenvolvedores que constroem automação de navegador: Discord · Telegram.

FAQ

Q: Eu preciso do Selenium ou WebDriver para capturar frames WebSocket dessa maneira?

Não. O Scrapeless Scraping Browser é acessível apenas através do Protocolo Chrome DevTools, então qualquer cliente que fale CDP — Playwright aqui, ou Puppeteer — pode se conectar e ler eventos de frame. Não existe um endpoint WebDriver, então o Selenium não pode controlar essa conexão.

Q: Qual é a diferença entre page.on("websocket") e o evento bruto CDP Network.webSocketFrameReceived?
O evento websocket do Playwright e seu callback framereceived já decodificam um quadro em uma string, o que cobre streams JSON baseadas em texto como o do guia. O evento CDP correspondente adiciona o campo opcode, para que você possa distinguir um quadro de texto de um quadro binário antes de decidir como decodificá-lo.

P: Como posso ler os quadros que a página envia, não apenas os que recebe?

O objeto WebSocket do Playwright dispara framesent junto com framereceived, e a sessão CDP dispara o evento correspondente Network.webSocketFrameSent com o mesmo requestId, timestamp e formato de response que a variante recebida.

P: O que acontece com os quadros binários do WebSocket?

Um quadro binário chega através do CDP com opcode definido como algo diferente de 1, e payloadData contém bytes codificados em base64 em vez de uma string UTF-8 — decodifique-o com base64.b64decode() antes de analisar qualquer formato binário que o destino utilize. O próprio evento framereceived do Playwright passa quadros binários como bytes em vez de str.

P: Por que a conexão falhou com o código de fechamento 1006 quando proxyCountry era "US"?

Um código de fechamento de 1006 significa que a conexão terminou anormalmente, sem um quadro de fechamento apropriado — neste caso, o handshake para o stream da Binance nunca foi concluído quando o IP de saída estava nos Estados Unidos. Roteando a mesma solicitação através de um valor proxyCountry diferente, o handshake foi concluído e o envio de quadros foi iniciado.

P: Posso capturar o tráfego do WebSocket de uma página em vez de abrir o socket eu mesmo?

Sim. Anexe o handler page.on("websocket"), ou ative o domínio Network do CDP, antes de chamar page.goto() no destino real, e os mesmos eventos serão disparados para qualquer socket que o script da própria página abrir — nada sobre o código de captura muda.

P: Quanto tempo o WebSocket pode permanecer aberto?

O parâmetro de consulta sessionTTL limita a sessão do navegador em segundos. Um valor curto é suficiente para uma captura limitada como a deste guia; um valor mais longo mantém a sessão — e quaisquer sockets abertos — ativos por um stream mais longo.

P: É seguro utilizar isso contra qualquer endpoint do WebSocket que eu encontrar?

Apenas contra endpoints públicos e não autenticados que você está autorizado a ler, e apenas em um volume que a própria documentação do endpoint permite. O exemplo aqui é direcionado ao stream público de dados de mercado documentado da Binance, não precisa de chave e fecha a conexão após cinco quadros em vez de mantê-la aberta indefinidamente.

P: O esquema do quadro permanece o mesmo em diferentes tipos de streams?

Não — o stream de ticker neste guia retorna um resumo de 24 horas a cada segundo, enquanto um stream de trade na mesma conexão retorna uma mensagem menor por trade executado, muitas vezes várias vezes por segundo. O mecanismo de captura é idêntico de qualquer maneira; apenas a forma JSON dentro de cada quadro muda com o stream ao qual você se inscreve.

Na Scorretless, acessamos apenas dados disponíveis ao público, enquanto cumprem estritamente as leis, regulamentos e políticas de privacidade do site aplicáveis. O conteúdo deste blog é apenas para fins de demonstração e não envolve atividades ilegais ou infratoras. Não temos garantias e negamos toda a responsabilidade pelo uso de informações deste blog ou links de terceiros. Antes de se envolver em qualquer atividade de raspagem, consulte seu consultor jurídico e revise os termos de serviço do site de destino ou obtenha as permissões necessárias.

Artigos mais populares

Catálogo