Páginas estáticas e dinâmicas no web scraping

Publicado:

16 min de leitura

Acar Diveroli
Autor: Acar Diveroli
Um nó de bot entre uma janela estática cheia de linhas de código e uma janela dinâmica com placeholders de carregamento

Quase todo mundo que começa no scraping passa pelo mesmo momento: produtos, preços e avaliações estão ali no navegador, mas, quando você busca o mesmo endereço no Python com requests, os seletores não encontram nada. Olhando o HTML da página, você vê um texto "Carregando..." e algumas tags <script> em vez de uma lista de produtos. O problema não é o seu código; a página é dinâmica, e o JavaScript que roda no navegador busca o conteúdo depois.

Neste artigo, explicamos a diferença entre páginas estáticas e dinâmicas, como saber em poucos minutos se uma página é dinâmica e o que é um navegador headless. A parte em que mais nos concentramos é como chegar aos dados sem navegador headless na maioria dos casos: encontrando a requisição de API que a página faz em segundo plano e lendo o JSON embutido no HTML. Quando um navegador é realmente necessário, também mostramos as estratégias de espera certas no Playwright e formas de reduzir o custo. Rodamos os exemplos contra uma página de teste local que carrega o conteúdo com JavaScript.

O que é uma página estática?

Em uma página estática, tudo o que o navegador mostra está no HTML da primeira resposta do servidor. O servidor pode ler esse HTML de um arquivo pronto ou gerá-lo a partir de um banco de dados a cada requisição; o que importa para o scraping é que o conteúdo já está dentro do HTML quando chega ao navegador.

Você não precisa de navegador para obter dados de uma página estática:

python
import requests
from bs4 import BeautifulSoup

html = requests.get("https://example.com/deals", timeout=20).text
cards = BeautifulSoup(html, "html.parser").select("div.product")
print("Cards de produto encontrados:", len(cards))

Sites de notícias, blogs, muitos sites corporativos e lojas online renderizadas no servidor fazem parte desse grupo.

O que é uma página dinâmica (carregada com JavaScript)?

Em uma página dinâmica, a primeira resposta do servidor é um esqueleto: o layout da página, um contêiner de lista vazio e arquivos JavaScript. O conteúdo chega depois, nestas etapas:

  1. O navegador recebe o esqueleto HTML e desenha na tela um placeholder como "Carregando...".
  2. Os arquivos JavaScript são baixados e executados. Muitas vezes um framework como React ou Vue.
  3. O JavaScript envia uma requisição de API. Por exemplo, com fetch para /api/products?category=headphones&page=1.
  4. O servidor devolve os dados em JSON. Nomes de produto, preços, informações de estoque.
  5. O JavaScript monta o HTML a partir desse JSON e o insere na página. Os cards de produto só aparecem nesta etapa.
  6. Rolar ou clicar dispara novas requisições. Rolagem infinita, um botão "Ver mais", filtros.

Um cliente HTTP como o requests do Python só faz a primeira etapa; ele não executa JavaScript. Por isso o HTML recebido não tem cards de produto. Na nossa página de teste local, o código requests acima encontrou zero cards; a mesma página mostrava dois produtos no navegador.

Existe também uma estrutura híbrida: frameworks como Next.js e Nuxt renderizam no servidor o primeiro estado da página e também embutem os mesmos dados no HTML como um bloco JSON para o JavaScript usar. Nessas páginas, os dados estão no HTML sem executar JavaScript, mas às vezes dentro de uma tag <script>, e não nos cards visíveis.

Como saber se uma página é dinâmica?

Alguns minutos de diagnóstico antes de montar um navegador headless ajudam a escolher o caminho certo:

  1. Veja o código-fonte. Ctrl + U na página (Cmd + Option + U no macOS) mostra o HTML bruto que o servidor enviou. Procure ali um nome de produto ou um preço que aparece na página. Se encontrar, a página provavelmente é estática.
  2. Compare com o painel Elements. O painel Elements das ferramentas do desenvolvedor mostra a página depois que o JavaScript rodou. Se o texto está em Elements, mas não no código-fonte, o conteúdo veio por JavaScript.
  3. Desative o JavaScript e recarregue. Nas ferramentas do desenvolvedor do Chrome, abra o menu de comandos com Ctrl + Shift + P, digite "Disable JavaScript" e recarregue a página. Se o conteúdo sumir, a página é dinâmica.
  4. Verifique com código. Procure no HTML obtido com requests um texto que você espera. Se não estiver lá e o HTML for muito curto, você recebeu um esqueleto.
  5. Olhe o filtro Fetch/XHR no painel Network. Se, ao recarregar a página, aparecerem respostas JSON nesse filtro, os dados provavelmente estão em uma dessas requisições.

A etapa quatro tem uma armadilha: se o conteúdo não está no HTML, o motivo nem sempre é JavaScript. O site pode ter devolvido uma página de verificação ou outro conteúdo. Confira o código de status e o conteúdo da resposta; explicamos esse diagnóstico em detalhe em Como fazer web scraping sem ser bloqueado.

O que é um navegador headless?

Um navegador headless é um navegador de verdade que roda sem abrir janela na tela. O mecanismo do Chromium, do Firefox ou do WebKit carrega a página como um navegador normal, executa JavaScript, envia requisições de API e monta a página; você lê essa página com código. As ferramentas mais comuns são Playwright, Puppeteer e Selenium.

Um navegador headless faz mais do que um cliente HTTP, mas também custa mais:

  • CPU e memória. Cada aba de navegador usa muitas vezes os recursos de uma requisição HTTP. O número de páginas que você roda ao mesmo tempo na mesma máquina é limitado por núcleos e memória, não pela rede.
  • Tempo. Baixar e executar todo o JavaScript de uma página pode levar segundos.
  • Número de requisições e banda. Uma única página gera dezenas de requisições extras de imagens, fontes, folhas de estilo, scripts de analytics e anúncios. Isso aumenta tanto o seu tráfego quanto a carga do site de destino.
  • Manutenção. Versões de navegador, drivers e lógica de espera adicionam complexidade.

Por isso o navegador headless deve ser o último recurso, não a primeira escolha. Para comparar as ferramentas, veja Web scraping: JavaScript ou Python?, e para configurar o Selenium, Como usar proxy com Selenium.

Primeiro, encontre a requisição de API/XHR

Os dados de uma página dinâmica chegam ao navegador de algum lugar em JSON. Se você encontrar essa requisição, obtém os dados direto, sem renderizar a página. A referência do painel Network do Chrome explica os filtros e as opções de cópia em detalhe; o caminho curto é:

  1. Abra as ferramentas do desenvolvedor (F12) e vá para a aba Network.
  2. Marque Fetch/XHR na barra de filtros.
  3. Recarregue a página ou faça a ação que carrega os dados (rolar a página, escolher um filtro, ir para a próxima página).
  4. Clique nas requisições cujo nome contém palavras como api, graphql, search ou products e veja o JSON na aba Response.
  5. Ao encontrar a requisição com os dados de que precisa, anote o endereço, os parâmetros e os cabeçalhos necessários na aba Headers. Dá para testá-la na linha de comando clicando com o botão direito na requisição e escolhendo Copy > Copy as cURL.

Repetir no Python a requisição encontrada costuma levar poucas linhas:

python
import requests

session = requests.Session()
session.headers.update({
    "User-Agent": "ExamplePriceBot/1.0 (+https://example.com/about-our-bot)",
    "Accept": "application/json",
})

response = session.get(
    "https://example.com/api/products",
    params={"category": "headphones", "page": 1},
    timeout=20,
)
response.raise_for_status()
for product in response.json()["products"]:
    print(product["name"], product["price"])

As vantagens desse caminho são grandes: os dados chegam estruturados, você não precisa de seletores HTML, o seu código não quebra quando o design da página muda e uma única requisição é uma fração pequena da carga de uma página inteira. A paginação geralmente é feita com um parâmetro page, offset ou cursor.

Pontos de atenção:

  • Autenticação e tokens. Algumas requisições de API precisam de cookies, tokens de sessão ou assinaturas de curta duração. Copiar isso à mão funciona por um tempo e depois quebra.
  • Termos do site. Uma API interna que o site usa no próprio front-end não é uma API pública. Aplique também a essas requisições os termos de uso e as regras de robots.txt do site; se houver uma API oficial, prefira-a. Para o enquadramento legal, veja Web scraping é legal?.
  • Velocidade. Como as requisições de API são leves, dá para enviá-las muito rápido, o que também facilita ultrapassar os limites de um site. Aplique as mesmas regras de velocidade.

Ler o JSON embutido no HTML

Em sites feitos com Next.js, Nuxt e frameworks parecidos, os dados costumam estar prontos em uma tag <script> dentro do HTML. Veja o código-fonte e procure __NEXT_DATA__, __NUXT__, __INITIAL_STATE__ ou application/ld+json.

python
import json

import requests
from bs4 import BeautifulSoup

html = requests.get("https://example.com/deals", timeout=20).text
soup = BeautifulSoup(html, "html.parser")

data = json.loads(soup.select_one("script#__NEXT_DATA__").string)
for product in data["props"]["pageProps"]["products"]:
    print(product["name"], product["price"])

Os blocos application/ld+json trazem dados estruturados como nome do produto, preço, estoque e avaliação no formato schema.org e, como são preparados para os buscadores, costumam ser estáveis. Tratamos de como tornar seletores robustos contra mudanças de design em Seletor CSS ou XPath.

Estratégias de espera no Playwright

Se os dados não estão nem em uma API nem em JSON embutido, ou se chegar ao conteúdo exige interações como clicar, rolar e preencher formulários, usa-se um navegador headless. O erro mais comum nesse caso é o momento de ler: se você lê assim que a página abre, o conteúdo ainda não chegou; se espera um tempo fixo, ou perde tempo ou, com uma resposta lenta, recebe um resultado vazio do mesmo jeito.

As ferramentas de espera certas do Playwright são:

  • A espera automática dos locators. Operações como page.locator("div.product h2").inner_text() esperam o elemento aparecer na página, até o timeout padrão. A documentação de verificações de ação do Playwright lista o que é esperado.
  • Esperar um elemento específico. page.locator("div.product").first.wait_for() espera o primeiro card de produto aparecer.
  • Esperar uma resposta específica. page.expect_response(...) espera a requisição de dados da página voltar e entrega o JSON direto.
  • Não usar esperas fixas. page.wait_for_timeout(5000) é útil em testes, mas pouco confiável em produção.

A documentação do Playwright desaconselha o estado de carregamento networkidle, ou seja, esperar o tráfego de rede parar por um tempo; em páginas cujos scripts de analytics e anúncios continuam enviando requisições, esse estado pode nunca chegar.

O exemplo abaixo abre a página por um proxy, bloqueia imagens e fontes para reduzir a carga, captura a resposta da requisição de dados e depois lê os cards renderizados:

python
from playwright.sync_api import sync_playwright

PROXY = {"server": "http://pr.proxynet.io:8000", "username": "user", "password": "pass"}

with sync_playwright() as p:
    browser = p.chromium.launch(proxy=PROXY)
    page = browser.new_page()

    # Não baixar imagens e fontes, para economizar tempo e tráfego
    page.route("**/*.{png,jpg,jpeg,webp,gif,woff2}", lambda route: route.abort())

    with page.expect_response(lambda r: "/api/products" in r.url and r.ok) as response:
        page.goto("https://example.com/deals")
    api_data = response.value.json()           # pegar o JSON direto

    page.locator("div.product").first.wait_for()  # ou esperar os cards renderizados
    names = page.locator("div.product h2").all_inner_texts()

    print(len(api_data["products"]), names)
    browser.close()

Neste exemplo, o JSON capturado com expect_response muitas vezes basta sozinho; nem é preciso ler os cards. No fim, você usa o navegador só para fazer a página enviar essa requisição com os parâmetros e cookies certos.

No Playwright, os dados do proxy são informados ao iniciar o navegador, com usuário e senha em campos separados. Para fluxos em que o mesmo IP precisa ser mantido durante a sessão, pode-se usar um endereço de saída fixo; para muitas páginas independentes, um Proxies rotativos, que dá um IP diferente a cada conexão. Explicamos por que telas de verificação aparecem na automação de navegador em Puppeteer e CAPTCHA.

Custo: quando o navegador headless é desnecessário?

AbordagemQuando funcionaVelocidade e recursosFragilidadeAtenção a
Cliente HTTP + HTMLA página é estática, o conteúdo está no código-fonteMuito rápida, muito leveSensível a mudanças de designAncorar seletores em atributos estáveis
JSON embutido no HTMLPáginas híbridas como Next.js, NuxtMuito rápida, leveA estrutura de dados pode mudarTratamento de erro que confira o caminho do JSON
Requisição de API em segundo planoO conteúdo chega em JSONMuito rápida, a mais leveIndepende do design, a API pode mudarTermos do site, tokens, limites de taxa
Navegador headlessHá interação, os dados não são acessíveis de outro jeitoLenta, pesadaSensível à lógica de esperaBloquear imagens, manter a concorrência baixa

Casos típicos em que o navegador headless é desnecessário:

  • O conteúdo já está no código-fonte da página.
  • Os dados vêm de uma requisição JSON que pode ser repetida com parâmetros simples.
  • Os dados estão em um bloco __NEXT_DATA__ ou JSON-LD.
  • O JavaScript só é usado para navegar entre páginas, e o conteúdo vem do servidor em cada página.

Casos em que o navegador headless é realmente necessário:

  • O conteúdo só carrega com rolagem, clique ou interação com formulário, e a requisição de API correspondente não pode ser repetida.
  • As requisições precisam de assinaturas ou tokens de curta duração gerados pelo JavaScript da página.
  • A saída visual da página (uma captura de tela, uma verificação de layout) é o próprio trabalho.

Se usar navegador headless, ajuste o número de páginas simultâneas aos recursos da máquina; tratamos disso em Concorrência e paralelismo.

Casos de uso

  • Monitoramento de preços no e-commerce: a página de categoria é dinâmica, mas a lista de produtos vem de uma única requisição /api/products. A requisição de API em vez do navegador headless; para ver preços em países diferentes, um Proxies residenciais que sai por conexões domésticas reais. A configuração está na nossa página de solução de proxy para e-commerce.
  • Um site de classificados feito com Next.js: os dados estão em __NEXT_DATA__. Um cliente HTTP e a análise do JSON bastam.
  • Uma lista de avaliações com rolagem infinita: encontra-se a requisição de paginação enviada ao rolar e percorre-se em loop com o parâmetro cursor.
  • Um painel de conta que exige interação (a sua própria conta): login, escolha de filtros e download de relatórios com navegador headless; o mesmo IP durante a sessão. A configuração geral está na nossa página de solução de extração de dados.

Erros comuns

  • Partir primeiro para o navegador headless. A requisição JSON no painel Network muitas vezes entrega os mesmos dados de forma muito mais barata.
  • Esperar um tempo fixo. time.sleep(5) desperdiça tempo em respostas rápidas e ainda dá resultado vazio nas lentas.
  • Esperar por networkidle. Pode nunca chegar em páginas que continuam enviando requisições.
  • Baixar todos os recursos. Imagens, fontes e vídeos não são necessários para os dados; bloqueá-los economiza tempo e tráfego.
  • Confundir resultado vazio com página dinâmica. Uma página de verificação ou um bloqueio também dá resultado vazio; confira o código de status e o conteúdo.
  • Usar uma API interna como se fosse pública. Os termos e os limites de taxa do site também valem para essas requisições.
  • Recorrer a ferramentas de evasão de detecção. Plugins que tentam fazer o navegador "parecer humano" escondem a origem do problema; revisar velocidade, escopo e permissão é um caminho mais sólido. Discutimos por que essas ferramentas são problemáticas em Como usar o undetected ChromeDriver para web scraping.

Guia de decisão

Resultado do diagnósticoRecomendação
O texto está no código-fonte da páginaCliente HTTP + análise de HTML
O código-fonte tem __NEXT_DATA__ ou JSON-LDCliente HTTP + análise de JSON
O painel Network tem uma requisição JSON com os dadosRepetir a requisição diretamente
A requisição JSON precisa de assinatura de curta duraçãoNavegador headless + expect_response
O conteúdo só chega com interaçãoNavegador headless + espera com locators
O navegador headless está lento e pesadoBloquear imagens, reduzir a concorrência
Sem conteúdo no HTML e código de status 403/429Não é dinâmica, é bloqueio; diagnostique

Perguntas frequentes

Por que o requests não traz o conteúdo que vejo no navegador?

Porque o requests só busca o primeiro HTML que o servidor envia e não executa JavaScript. Se o conteúdo da página é carregado depois com JavaScript, esse HTML só contém o esqueleto. Procure no painel Network a requisição de onde vêm os dados, ou JSON embutido no HTML.

Qual é a diferença entre navegador headless e navegador normal?

O mecanismo é o mesmo; a diferença é que no modo headless nenhuma janela é desenhada na tela. O carregamento da página, a execução de JavaScript e as requisições de rede funcionam como em um navegador normal. Alguns sites conseguem distinguir navegadores headless por pequenas diferenças.

Playwright ou Selenium?

Os dois rodam páginas dinâmicas. O Playwright oferece em uma única API recursos que ajudam no scraping, como espera automática, captura de respostas de rede e bloqueio de requisições; o Selenium é mais antigo, multilinguagem e tem um ecossistema amplo. Continuar com o que o seu projeto atual já usa costuma ser o mais prático.

Uma requisição ser tecnicamente pública não torna o uso dela irrestrito. Considere os termos de uso do site, as regras do robots.txt e as leis de proteção de dados pessoais; se houver API oficial, prefira-a. Se tiver dúvida, busque orientação jurídica.

Como usar navegador headless com proxy?

No Playwright, o proxy é informado com o parâmetro proxy ao iniciar o navegador; endereço do servidor, usuário e senha são campos separados. No Puppeteer e no Selenium, ele é passado ao navegador como argumento de inicialização, e a autenticação é tratada separadamente.

Como obter dados de páginas com rolagem infinita?

Primeiro, encontre no painel Network a requisição de paginação enviada ao rolar. Ela geralmente traz um número de página ou um valor cursor e pode ser repetida em loop. Se a requisição não puder ser repetida, role a página aos poucos em um navegador headless e espere, a cada passo, a chegada de novos cards.

Em resumo

Em uma página estática, o conteúdo está pronto no HTML e uma requisição HTTP simples basta; em uma página dinâmica, o conteúdo chega depois por JavaScript e uma requisição HTTP devolve um esqueleto. Antes de partir para um navegador headless, veja o código-fonte, procure no painel Network a requisição JSON com os dados e confira se há dados embutidos no HTML. Quando o navegador for realmente necessário, use a espera com locators e expect_response em vez de esperas fixas, bloqueie imagens e mantenha a concorrência baixa. Tipos de proxy adequados ao seu trabalho de coleta de dados estão nos nossos serviços de proxy.