🎯 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: Intercepte a API JSON Oculta

Alex Johnson
Alex Johnson

Senior Web Scraping Engineer

29-Jul-2026

Uma página que diz "cotações" não retorna nenhuma citação. Solicite https://quotes.toscrape.com/scroll com um cliente HTTP simples e o corpo da resposta contém um <div class="quotes"></div> vazio — o texto real, autores e tags chegam depois, através de uma chamada GET /api/quotes?page=N que o próprio JavaScript da página dispara ao carregar e a cada rolagem. Ler essa chamada diretamente, em vez de esperar que um navegador a renderize em HTML e depois analisar o HTML, é a interceptação de solicitação de rede: conecte um navegador real através do Protocolo DevTools do Chrome, escute o tráfego que ele gera e leia o JSON que o site já produz para si mesmo.

Este guia conecta o Playwright ao Scrapeless Scraping Browser através do CDP, e então intercepta esse endpoint oculto de duas maneiras — eventos de resposta do próprio Playwright e o domínio Network bruto do CDP por baixo deles — antes de reproduzir o endpoint diretamente via HTTP simples, uma vez que sua forma é conhecida. Cada comando abaixo foi executado contra o alvo ao vivo.

Por Que Ler o Tráfego em vez do DOM

quotes.toscrape.com/scroll é um alvo público de prática de raspagem criado especificamente para demonstrar rolagem infinita, e sua marcação justifica a interceptação por si só. Busque a página e procure os dados:

bash Copy
curl -s https://quotes.toscrape.com/scroll | grep -o 'class="quote"' | wc -l

Isso retorna zero todas as vezes, porque as citações nunca vivem no HTML que o servidor envia. Um pequeno bloco jQuery chama $.get('/api/quotes', {page: page}) uma vez ao carregar e novamente sempre que a posição de rolagem se aproxima do fundo, em seguida, anexa as linhas retornadas ao contêiner vazio manualmente. Um navegador que executa o JavaScript e espera o tempo suficiente acabará mostrando as mesmas 10 citações por página em seu DOM — mas quando você voltar a contar elementos .quote e extrair texto de nós <span class="text"> e <small class="author">, você terá reconstruído, à mão, dados que a página já tinha como JSON limpo e tipado: um array de quotes, cada um com text, um objeto author, e uma lista de tags, além de uma flag has_next que lhe diz exatamente quando parar. Ler a resposta diretamente ignora a etapa de reconstrução e lhe dá campos em vez de nós.

Pré-requisitos

Você precisa do Python 3.9 ou mais recente — playwright 1.59.0 declara Requires-Python >=3.9 no PyPI — o pacote playwright em si, e uma chave API do Scrapeless obtida no plano gratuito em app.scrapeless.com. A chave viaja como o parâmetro de consulta token no endpoint CDP do Scraping Browser, então mantenha-a em uma variável de ambiente, em vez de literal em seu script. Nenhuma instalação local do Chrome é necessária: connect_over_cdp alcança um navegador que já existe na nuvem.

Conectar o Playwright ao Scraping Browser Através do CDP

Instale o cliente e configure a chave:

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

O endpoint CDP do Scraping Browser é wss://browser.scrapeless.com/api/v2/browser, alcançado com três parâmetros de consulta — token, sessionTTL e proxyCountry — o mesmo construtor que todo script Playwright sobre Scraping Browser nesta série usa:

python Copy
import os
from urllib.parse import urlencode

API_KEY = os.environ["SCRAPELESS_API_KEY"]

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

chromium.connect_over_cdp(scraping_browser_url()) retorna um objeto Browser padrão do Playwright. Nada sobre os padrões de interceptação abaixo é específico do Scraping Browser — eles rodam contra qualquer Chromium acessível via CDP — mas executá-los no Scraping Browser significa que a renderização ocorre em uma infraestrutura que já carrega saídas residenciais e um Chromium anti-deteção, então sites que fazem fingerprinting agressivo ainda hidratam normalmente enquanto você lê seu tráfego.

Capturar a API Oculta com um Listener de Resposta

O page.expect_response() do Playwright vincula a espera à ação que a aciona — sem sono arbitrário, sem verificar um contador de seletor até parecer estável. Vincule-o ao redor de page.goto() para a primeira chamada /api/quotes, e depois em torno da rolagem que aciona a segunda:

python Copy
import os
from urllib.parse import urlencode
from playwright.sync_api import sync_playwright

API_KEY = os.environ["SCRAPELESS_API_KEY"]

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

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

    with page.expect_response("**/api/quotes*") as first_page:
        page.goto("https://quotes.toscrape.com/scroll", wait_until="domcontentloaded")
    data = first_page.value.json()
python Copy
print("Contagem de citações no DOM antes da primeira resposta chegar:", page.locator(".quote").count())
    print(f"página interceptada {data['page']} -> {len(data['quotes'])} citações, has_next={data['has_next']}")

    with page.expect_response("**/api/quotes*") as segunda_pagina:
        page.evaluate("window.scrollTo(0, document.body.scrollHeight)")
    data = segunda_pagina.value.json()
    print("Contagem de citações no DOM uma vez que a rolagem se estabiliza:", page.locator(".quote").count())
    print(f"página interceptada {data['page']} -> {len(data['quotes'])} citações, has_next={data['has_next']}")

    browser.close()

Executando contra o site ao vivo, imprime:

text Copy
Contagem de citações no DOM antes da primeira resposta chegar: 10
página interceptada 1 -> 10 citações, has_next=True
Contagem de citações no DOM uma vez que a rolagem se estabiliza: 20
página interceptada 2 -> 10 citações, has_next=True

O padrão glob "**/api/quotes*" corresponde ao endpoint pela forma da URL, que é o padrão documentado do Playwright para esperar uma resposta de rede específica, em vez de um tempo limite fixo. Como a espera está atrelada à ação desencadeadora, data['page'], data['quotes'] e data['has_next'] retornam como os mesmos valores tipados do Python que o próprio script do site consome — sem .author.name reconstruído de uma tag <small>, sem lista de tags reconstruída a partir do texto âncora.

Vá Mais Baixo: Leia Quadros Brutos Com o Domínio de Rede CDP

Os eventos de resposta do Playwright estão sobre o domínio de rede do protocolo DevTools do Chrome, e você pode falar com esse domínio diretamente através de uma CDPSession. Isso é importante quando você não está utilizando o Playwright de forma alguma — um cliente CDP nu em outra linguagem, uma ferramenta que só expõe eventos de protocolo, ou um caso onde você quer cabeçalhos de resposta e tempos que um vínculo específico não apresenta — porque Network.responseReceived e Network.getResponseBody funcionam da mesma maneira, independentemente de qual biblioteca cliente esteja acima delas:

python Copy
import os, json
from urllib.parse import urlencode
from playwright.sync_api import sync_playwright

API_KEY = os.environ["SCRAPELESS_API_KEY"]

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

request_ids, bodies = {}, []

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")

    def on_response_received(event):
        if "/api/quotes" in event["response"]["url"]:
            request_ids[event["requestId"]] = event["response"]["url"]

    def on_loading_finished(event):
        rid = event["requestId"]
        if rid in request_ids:
            raw = cdp.send("Network.getResponseBody", {"requestId": rid})
            data = json.loads(raw["body"])
            bodies.append((request_ids[rid], data["page"], len(data["quotes"]), data["quotes"][0]["author"]["name"]))

    cdp.on("Network.responseReceived", on_response_received)
    cdp.on("Network.loadingFinished", on_loading_finished)

    page.goto("https://quotes.toscrape.com/scroll", wait_until="domcontentloaded")
    page.evaluate("window.scrollTo(0, document.body.scrollHeight)")
    for _ in range(20):
        if len(bodies) >= 2:
            break
        page.wait_for_timeout(300)

    for url, page_no, count, author in bodies:
        print(f"CDP Network.getResponseBody em {url}: página={page_no}, citações={count}, primeiro_autor={author}")

    browser.close()
text Copy
CDP Network.getResponseBody em https://quotes.toscrape.com/api/quotes?page=1: página=1, citações=10, primeiro_autor=Albert Einstein
CDP Network.getResponseBody em https://quotes.toscrape.com/api/quotes?page=2: página=2, citações=10, primeiro_autor=Marilyn Monroe

Network.responseReceived é disparado com cabeçalhos e um requestId assim que a resposta começa; o corpo em si não está disponível até Network.loadingFinished confirmar que a transferência foi concluída, razão pela qual o manipulador se divide em dois eventos, em vez de ler o corpo a partir do primeiro. getResponseBody retorna os bytes exatos que o navegador recebeu, além de uma flag base64Encoded para payloads binários — a camada sobre a qual response.json() do Playwright constrói e oculta de você.

Obtenha um tempo de execução gratuito do Scraping Browser ao se inscrever em app.scrapeless.com e executando ambos os scripts acima contra seu próprio alvo.

Copy
Ambas as interceptações acima provaram a mesma coisa: `/api/quotes?page=N` é um GET público e não autenticado que retorna `quotes`, `page` e `has_next`. Uma vez que você conhece esse formato, um navegador não é mais necessário para percorrê-lo — um cliente HTTP simples pode navegar por toda a coleção diretamente:

```python
import json
import urllib.error
import urllib.request

def fetch_page(n):
    url = f"https://quotes.toscrape.com/api/quotes?page={n}"
    req = urllib.request.Request(url, headers={"User-Agent": "network-interception-demo/1.0"})
    with urllib.request.urlopen(req, timeout=10) as resp:
        if resp.status != 200:
            raise urllib.error.HTTPError(url, resp.status, "status inesperado", resp.headers, None)
        return json.loads(resp.read())

all_quotes, page = [], 1
while True:
    data = fetch_page(page)
    all_quotes.extend(data["quotes"])
    if not data["has_next"]:
        break
    page += 1

print("páginas buscadas:", page)
print("total de citações:", len(all_quotes))
print("autor da primeira citação:", all_quotes[0]["author"]["name"])
print("autor da última citação:", all_quotes[-1]["author"]["name"])
text Copy
páginas buscadas: 10
total de citações: 100
autor da primeira citação: Albert Einstein
autor da última citação: George R.R. Martin

Dez requisições HTTP simples puxam a coleção completa de 100 citações através do mesmo contrato JSON que a sessão do navegador já confirmou, sem nenhum processo de navegador em execução. Este é o verdadeiro benefício da interceptação: o único trabalho do navegador aqui foi revelar o endpoint. Uma vez que você o tenha, a maneira mais rápida de navegar pela coleção é geralmente parar de renderizar e chamar o endpoint diretamente, seguindo o padrão Fetch que tanto navegadores quanto clientes HTTP simples implementam no final.

Nem todo endpoint oculto é tão cooperativo. Muitos exigem um cookie de sessão que o servidor define durante um carregamento de página anterior, um CSRF ou um token de requisição assinado incorporado ao pacote JavaScript da página, ou um corpo de requisição montado a partir de um estado que só existe do lado do cliente, e alguns são enviados através de um quadro WebSocket ou um POST GraphQL em vez de um GET no formato REST como o padrão XMLHttpRequest descreve. Nesses casos, a etapa de replay direto na seção anterior não se aplica — você não pode reconstruir um cabeçalho de autenticação que nunca capturou — mas a etapa de interceptação ainda se aplica. page.expect_response() e o domínio Network do CDP leem o tráfego de uma página independentemente de como ela é autenticada ou de qual formato o payload assume, porque eles observam o que o navegador realmente enviou e recebeu, em vez de assumir um formato de requisição particular antecipadamente. Nessas sites, mantenha o navegador na loop para cada página: deixe-o gerar a sessão, conduzir a navegação e entregar cada resposta conforme ela chega.

Para um exemplo prático do mesmo padrão de renderização e leitura da rede aplicado a um site completo em vez de um alvo de prática, veja o guia de scraping do TikTok, que captura comentários e postagens XHR da mesma maneira enquanto rola um feed real.

A interceptação é uma etapa de descoberta tanto quanto uma de extração. A primeira vez que você acessa um site desconhecido, abra seu painel de Rede do DevTools, filtre por Fetch/XHR e observe o que acontece enquanto você interage com a página — essa passagem manual é o que te diz qual endpoint vincular ao page.expect_response() antes mesmo de você escrever o script. Uma vez que o endpoint é conhecido, tudo acima — o listener de resposta, a sessão bruta do CDP e o replay direto — é essa mesma descoberta codificada como código que se executa automaticamente.

Inscreva-se em app.scrapeless.com para um runtime gratuito do Scraping Browser, ou veja a página do produto Scraping Browser e preços para execuções em escala.

Perguntas Frequentes

P: O que é a interceptação de requisições de rede na raspagem de dados?
É ler o tráfego XHR/fetch que o próprio JavaScript de uma página gera — geralmente uma chamada JSON API — em vez de esperar que esse tráfego se transforme em HTML e então fazer o parsing da marcação renderizada.

P: É legal interceptar as próprias chamadas de API de um site?
Ler respostas que o seu navegador já recebe ao visitar uma página pública envolve considerações diferentes do que acessar conteúdo por trás de autenticação ou acessar dados não públicos. Limite qualquer fluxo de trabalho a páginas públicas, respeite os termos de serviço do alvo e as diretrizes de robôs, e mantenha o volume de solicitações controlado — trate a interceptação como uma maneira de ler o tráfego com mais precisão, não como uma licença para ignorar regras de acesso.

P: Você ainda precisa de um navegador uma vez que conhece o endpoint oculto?
Apenas se o endpoint exigir algo que o navegador fornece — um cookie de sessão, um token assinado, um estado computado em JavaScript. Um endpoint público e não autenticado, como o que está neste guia, pode ser reproduzido com um cliente HTTP simples, como o exemplo de reprodução direta mostra.

P: Qual é a diferença entre page.expect_response() e ouvir o domínio Network do CDP?
page.expect_response() é o wrapper de nível superior do Playwright: vincule-o a um padrão de URL, acione a ação, obtenha um objeto Response analisado de volta. O domínio Network do CDP é o protocolo por trás disso — Network.responseReceived e Network.getResponseBody — útil quando você não está usando um binding do Playwright ou precisa de detalhes em nível de protocolo que uma biblioteca cliente específica não expõe.

P: Isso funciona em endpoints que retornam algo diferente de JSON?
Ambos os padrões de interceptação leem qualquer byte que a resposta carrega — response.text() ou response.body() no Playwright, o campo body bruto de Network.getResponseBody no CDP — então fragmentos HTML, XML ou qualquer outro payload chegam da mesma maneira. JSON é simplesmente o caso comum para a própria API interna de uma página.

P: page.expect_response() pode capturar requisições disparadas antes da página que você está assistindo carregar?
Não — ele precisa estar ouvindo antes que a ação desencadeadora seja executada, razão pela qual ele envolve a específica page.goto() ou interação que causa a requisição, em vez de estar anexado após o fato.

P: A interceptação precisa do Scrapeless Scraping Browser especificamente, ou funciona com qualquer Chromium acessível pelo CDP?
A técnica em si é um comportamento genérico do CDP e funciona contra qualquer Chromium que você possa acessar via connect_over_cdp, local ou remoto. Executá-lo no Scrapeless Scraping Browser adiciona um Chromium anti-detecção e saída residencial, o que importa quando o alvo que você está interceptando tem impressões digitais agressivas o suficiente para que um navegador local simples não conseguisse passar pela renderização para produzir o tráfego em primeiro lugar.

P: O que acontece se o site mudar seu endpoint ou forma de resposta?
O código de interceptação continua funcionando desde que o padrão de URL ainda corresponda; um campo renomeado ou um payload reestruturado quebra o código que lê data['quotes'], da mesma forma que um seletor CSS quebra quando um nome de classe muda. Nenhuma abordagem está imune a um redesenho — ler a API apenas significa que você está rastreando um contrato JSON em vez de uma estrutura de marcação, que tende a mudar com menos frequência.

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