In this Article
Aprender a fazer scraping de páginas web dinâmicas significa lidar com conteúdo que não existe no HTML que a sua primeira requisição baixa. Os sites modernos constroem as grades de produtos, os preços, os comentários e os feeds no navegador usando JavaScript, então uma simples requisição HTTP retorna uma casca vazia onde os dados deveriam estar.
Este guia percorre o fluxo de trabalho completo em Python. Começa por detectar conteúdo dinâmico comparando a resposta bruta com o DOM renderizado e depois ordena as opções de extração da mais leve à mais pesada: encontrar a API JSON oculta, controlar Playwright ou Selenium, lidar com scroll infinito e botões de carregar mais, reduzir a largura de banda do proxy, adicionar retentativas, fazer scraping de forma concorrente e exportar JSON e CSV limpos.
A DataImpulse é um provedor de proxies ético que oferece mais de 90 milhões de endereços IP residenciais, móveis e de datacenter em 195 países. Usa um modelo de pagamento conforme o uso a partir de 1 dólar por GB com tráfego que não expira, e é usada para web scraping, verificação de anúncios, monitoramento de preços, pesquisa de mercado e gestão de múltiplas contas.
Fatos principais
- Scraping dinâmico: o conteúdo construído por JavaScript não está no HTML bruto, então você chama a API JSON subjacente que a página consulta ou renderiza a página em um navegador headless.
- Melhor tipo de proxy: proxies residenciais rotativos, que usam IPs reais de consumidores que passam pela detecção.
- Preço: a partir de 1 dólar por GB, pagamento conforme o uso, com tráfego que não expira e sem assinatura.
- Cobertura: mais de 90M de IPs de origem ética em 195 países.
- Confiabilidade: 99,51% de taxa de sucesso, avaliada em 4,8 de 5 no G2.
- Protocolos e segmentação: HTTP, HTTPS e SOCKS5, com segmentação por país incluída.

O que torna uma página web dinâmica?
Uma página é dinâmica quando o conteúdo relevante é gerado por JavaScript no navegador em vez de ser entregue na resposta HTML inicial. O servidor envia um esqueleto leve e, em seguida, o código do lado do cliente busca os dados e os injeta no DOM depois que a página abre.
A verificação manual mais rápida é comparar duas visões da mesma página. Clique com o botão direito e escolha Ver código-fonte para ver o HTML bruto que o servidor retornou; depois abra as ferramentas de desenvolvedor e inspecione o painel Elements, que mostra o DOM ao vivo depois que os scripts rodaram. Se o preço ou a listagem que você quer aparece no painel Elements mas está ausente em Ver código-fonte, o conteúdo é dinâmico e uma única requisição não o capturará.
- O conteúdo estático está presente em Ver código-fonte e pode ser parseado diretamente de uma resposta HTTP.
- O conteúdo dinâmico aparece apenas no DOM renderizado e é carregado por meio de requisições em segundo plano.
Você pode automatizar esse diagnóstico em código. Primeiro, conte os elementos-alvo no HTML bruto que um cliente HTTP normal baixa:
import requests
from bs4 import BeautifulSoup
url = "https://example.com/products"
headers = {"User-Agent": "Mozilla/5.0"}
resp = requests.get(url, headers=headers, timeout=30)
soup = BeautifulSoup(resp.text, "html.parser")
raw_cards = soup.select("div.product-card")
print("Cards in raw HTML:", len(raw_cards))
# If this prints 0 but the page clearly shows products in a browser,
# the content is injected by JavaScript and the page is dynamic.
Depois, renderize a mesma URL em um navegador headless e conte de novo. A diferença entre os dois números é a prova:
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
page.goto("https://example.com/products", wait_until="networkidle")
rendered_cards = page.query_selector_all("div.product-card")
print("Cards after JavaScript runs:", len(rendered_cards))
browser.close()
# Raw HTML 0, rendered DOM 48 means the page builds its content dynamically.
Antes de escrever um scraper completo, também vale confirmar se o alvo permite acesso automatizado. Nosso guia sobre como verificar se um site permite scraping aborda a leitura das regras de robots e dos termos de serviço.
Qual método você deve usar para fazer scraping de páginas dinâmicas?
Escolha o método mais leve que retorne os dados de forma confiável: chame uma API JSON oculta quando existir uma e recorra a um navegador headless somente quando não houver. Cada opção equilibra velocidade e custo em relação a quanto da página consegue reproduzir.
A tabela abaixo compara as quatro abordagens comuns para que você possa escolher antes de escrever código. Na prática, a maioria dos projetos combina duas delas, usando uma API oculta para a carga em massa e um navegador para as páginas que resistem.
| Método | Velocidade | Custo | Melhor para | Limitação principal |
|---|---|---|---|---|
| API JSON oculta | A mais rápida | O mais baixo | Dados por trás de uma chamada XHR limpa | O endpoint pode mudar ou exigir tokens |
| Playwright | Moderada | Mais alto | Renderização completa, controle assíncrono moderno | Usa CPU, memória e largura de banda |
| Selenium | Moderada | Mais alto | Stacks legados, amplo suporte a linguagens | Configuração mais lenta, esperas mais verbosas |
| requests-html | Lenta | Baixo | JavaScript leve em páginas simples | Praticamente sem manutenção, renderização frágil |
O custo importa porque um navegador headless roteado por um proxy medido baixa muito mais bytes do que uma única chamada de API. Se você faz scraping em escala, o método que escolher muda a conta tanto quanto o código. Para a disciplina mais ampla de ritmo e cabeçalhos, veja nossas melhores práticas de web scraping.
Como encontrar a API oculta por trás de uma página dinâmica?
Procure a requisição em segundo plano que a página faz para buscar seus dados, porque esse endpoint normalmente retorna JSON limpo que você pode chamar diretamente. É a abordagem mais rápida e confiável, e evita completamente executar um navegador.
Abra as ferramentas de desenvolvedor, vá para a aba Network, filtre por Fetch/XHR e recarregue a página. Conforme o conteúdo aparece, observe as requisições que retornam JSON com os valores que você quer. Inspecione a URL da requisição, o método, os parâmetros de consulta e quaisquer cabeçalhos ou tokens que ela envia; depois clique com o botão direito e copie como cURL para ver o formato completo. Assim que puder reproduzir essa chamada com um cliente HTTP, você pula a renderização por completo e obtém dados estruturados por uma fração do custo.
Aqui está um exemplo mínimo que chama um endpoint JSON através de um proxy e lê os campos diretamente:
import requests
proxy = "http://USERNAME:[email protected]:823"
proxies = {"http": proxy, "https": proxy}
headers = {
"User-Agent": "Mozilla/5.0",
"Accept": "application/json",
"Referer": "https://example.com/products",
}
params = {"category": "laptops", "page": 1}
r = requests.get(
"https://example.com/api/v2/products",
headers=headers,
params=params,
proxies=proxies,
timeout=30,
)
r.raise_for_status()
data = r.json()
for item in data["items"]:
print(item["name"], item["price"])
Se o endpoint for paginado, incremente o parâmetro de página ou siga o próximo cursor que a resposta retorna. Quando uma requisição precisa de um token, capture onde a página o emite pela primeira vez e reproduza-o dentro da mesma sessão. Rotear a chamada pela DataImpulse faz o endpoint ver um IP residencial em vez do endereço do seu servidor, o que importa quando a API limita a taxa por origem. Se você esbarrar em uma barreira de autenticação no próprio proxy, nossa nota sobre autenticação de proxy explica o formato das credenciais.
Como fazer scraping de páginas dinâmicas com Playwright?
Quando não há uma API limpa para chamar, controle um navegador headless como o Playwright para que o JavaScript execute exatamente como executaria para um usuário e, então, leia o DOM finalizado. O Playwright é o padrão moderno porque traz espera automática confiável, suporte assíncrono de primeira classe e uma opção de proxy simples.
Primeiro instale-o e baixe um binário do navegador:
pip install playwright
playwright install chromium
A disciplina central é esperar pelo elemento específico de que você precisa em vez de adivinhar com sleeps fixos. O exemplo abaixo inicia o Chromium, roteia-o através de um proxy usando a forma de dicionário, espera pela grade de produtos e extrai os valores renderizados:
from playwright.sync_api import sync_playwright
PROXY = {
"server": "http://gw.dataimpulse.com:823",
"username": "USERNAME",
"password": "PASSWORD",
}
with sync_playwright() as p:
browser = p.chromium.launch(headless=True, proxy=PROXY)
context = browser.new_context(user_agent="Mozilla/5.0")
page = context.new_page()
page.goto("https://example.com/products", wait_until="domcontentloaded")
page.wait_for_selector("div.product-card", timeout=15000)
cards = page.query_selector_all("div.product-card")
for card in cards:
name = card.query_selector(".title").inner_text()
price = card.query_selector(".price").inner_text()
print(name, price)
browser.close()
A espera é onde a maioria dos scrapers dinâmicos tem sucesso ou falha, então ajuda conhecer as quatro estratégias e quando cada uma se encaixa. Prefira esperar por um seletor ou condição concreta em vez de networkidle, que pode travar em páginas que mantêm conexões de longa duração abertas:
# Wait for a specific element (most reliable)
page.wait_for_selector("div.product-card", timeout=15000)
# Wait for the initial DOM to be built, before every script finishes
page.goto(url, wait_until="domcontentloaded")
# Wait until there are no network connections for 500 ms
page.goto(url, wait_until="networkidle")
# Wait for a custom condition, such as a minimum item count
page.wait_for_function(
"document.querySelectorAll('div.product-card').length > 20"
)
Se o seu alvo depende fortemente de scripts executados no navegador, nosso artigo complementar sobre web scraping com JavaScript aprofunda a renderização do lado do cliente que torna essas páginas difíceis.
Como lidar com scroll infinito e botões de carregar mais?
Dispare os mesmos eventos que a página usa para carregar mais conteúdo e depois espere cada novo lote renderizar antes de lê-lo. O scroll infinito e os botões de carregar mais só buscam dados conforme o usuário age, então um único carregamento de página captura apenas a primeira tela.
Antes de recorrer ao navegador, verifique novamente a aba Network, porque o scroll infinito quase sempre é alimentado por uma chamada XHR paginada, e iterar sobre esse endpoint com a abordagem de requests acima é muito mais barato do que rolar. Se você precisar rolar, faça-o em um laço controlado que pare assim que nenhum item novo aparecer:
def scroll_until_stable(page, item_selector, max_rounds=30):
seen = 0
for _ in range(max_rounds):
page.evaluate("window.scrollTo(0, document.body.scrollHeight)")
page.wait_for_timeout(1500)
count = len(page.query_selector_all(item_selector))
if count == seen:
break # no new items loaded, stop
seen = count
return seen
Páginas que usam um botão explícito em vez de scroll precisam de um laço de cliques. Leia a contagem de itens antes de cada clique, clique no botão e espere a contagem crescer para nunca ficar à frente da renderização:
def click_load_more(page, button_selector, item_selector, max_clicks=50):
for _ in range(max_clicks):
button = page.query_selector(button_selector)
if button is None or not button.is_visible():
break
before = len(page.query_selector_all(item_selector))
button.click()
page.wait_for_function(
"([sel, n]) => document.querySelectorAll(sel).length > n",
arg=[item_selector, before],
)
return len(page.query_selector_all(item_selector))
Remova duplicatas conforme coleta, já que reler os mesmos nós a cada passagem é comum. Mantenha um conjunto de IDs ou URLs únicos e adicione apenas os registros que você ainda não viu.
Você pode usar Selenium em vez de Playwright?
Sim, o Selenium controla um navegador real da mesma forma e é uma opção sólida quando sua equipe já o usa ou precisa do seu amplo suporte a linguagens. O padrão espelha o do Playwright: carregue a página, espere por uma condição com WebDriverWait e depois leia os elementos do DOM renderizado.
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
options = Options()
options.add_argument("--headless=new")
options.add_argument("--proxy-server=http://gw.dataimpulse.com:823")
driver = webdriver.Chrome(options=options)
driver.get("https://example.com/products")
WebDriverWait(driver, 15).until(
EC.presence_of_element_located((By.CSS_SELECTOR, "div.product-card"))
)
for card in driver.find_elements(By.CSS_SELECTOR, "div.product-card"):
name = card.find_element(By.CSS_SELECTOR, ".title").text
price = card.find_element(By.CSS_SELECTOR, ".price").text
print(name, price)
driver.quit()
Uma ressalva: o simples flag –proxy-server não aceita usuário e senha, então um proxy autenticado precisa de um auxiliar. A biblioteca Selenium Wire injeta as credenciais de forma limpa, e nosso passo a passo sobre a configuração do proxy com Selenium Wire mostra a configuração exata. O Selenium é mais pesado de configurar que o Playwright e suas esperas são mais verbosas, então projetos novos costumam começar com Playwright, a menos que haja um motivo para ficar no Selenium.
Como reduzir a largura de banda do proxy ao renderizar páginas?
Bloqueie os recursos de que você não precisa para que o navegador pare de baixar imagens, fontes e mídia que você nunca faz parse. Um navegador headless busca todos os recursos por padrão, e cada um desses bytes custa dinheiro quando roteado por um proxy cobrado por gigabyte.
O Playwright permite interceptar requisições e abortar as que são irrelevantes para a sua extração. Bloquear imagens, mídia e fontes pode reduzir drasticamente o tráfego do proxy sem mexer no JSON e no HTML que você realmente lê:
BLOCK = {"image", "media", "font"}
def block_heavy(route):
if route.request.resource_type in BLOCK:
route.abort()
else:
route.continue_()
context = browser.new_context()
context.route("**/*", block_heavy)
page = context.new_page()
page.goto("https://example.com/products", wait_until="domcontentloaded")
Você pode estender o mesmo manipulador para pular os domínios de analytics e de anúncios que adicionam carga sem adicionar dados. Com o tráfego da DataImpulse cobrado conforme o uso a partir de 1 dólar por GB e sem expirar, cortar bytes desperdiçados reduz diretamente quanto custa cada scrape. Reutilizar um mesmo contexto de navegador entre páginas relacionadas também evita o custo repetido de inicialização, e a requisição mais barata continua sendo aquela que você evita ao recorrer a uma API oculta.
Como adicionar tratamento de erros e retentativas?
Envolva cada navegação em um laço de retentativas com backoff exponencial para que um único carregamento lento ou bloqueio transitório não arruíne toda a execução. Páginas dinâmicas falham de forma intermitente, e um scraper que desiste no primeiro timeout perderá uma grande parcela dos registros em escala.
Capture o timeout que o Playwright lança, espere um intervalo crescente e tente novamente um número limitado de vezes antes de registrar a falha e seguir em frente:
import time
from playwright.sync_api import TimeoutError as PlaywrightTimeout
def fetch_with_retries(page, url, selector, retries=3, backoff=2):
for attempt in range(1, retries + 1):
try:
page.goto(url, wait_until="domcontentloaded", timeout=30000)
page.wait_for_selector(selector, timeout=15000)
return page.query_selector_all(selector)
except PlaywrightTimeout:
wait = backoff ** attempt
print(f"Attempt {attempt} timed out, retrying in {wait}s")
time.sleep(wait)
raise RuntimeError(f"Failed to load {url} after {retries} attempts")
Combine as retentativas com a rotação de proxies para que cada tentativa saia por uma IP nova. Se uma saída está com limite de taxa, a próxima requisição chega a um endereço diferente e muitas vezes tem sucesso. Um pool rotativo de proxies residenciais ou proxies móveis atribui uma IP nova por requisição automaticamente, o que transforma muitos bloqueios difíceis em uma simples retentativa. Para lidar especificamente com falhas a nível de proxy, nossa nota sobre o erro HTTP 407 aborda a resposta de autenticação mais comum.
Como fazer scraping de muitas páginas dinâmicas de forma concorrente?
Use a API assíncrona do Playwright com um semáforo do asyncio para que várias páginas rendam ao mesmo tempo sem sobrecarregar sua máquina ou o alvo. A concorrência é onde o scraping dinâmico recupera sua vazão, já que cada página do navegador passa a maior parte do tempo esperando pela rede.
O semáforo limita quantas páginas rodam em paralelo. Compartilhe um mesmo navegador e contexto entre as tarefas, abra uma página por URL e reúna os resultados:
import asyncio
from playwright.async_api import async_playwright
PROXY = {
"server": "http://gw.dataimpulse.com:823",
"username": "USERNAME",
"password": "PASSWORD",
}
async def scrape_one(context, url, sem):
async with sem:
page = await context.new_page()
try:
await page.goto(url, wait_until="domcontentloaded")
await page.wait_for_selector("div.product-card", timeout=15000)
cards = await page.query_selector_all("div.product-card")
return [await c.inner_text() for c in cards]
finally:
await page.close()
async def main(urls):
sem = asyncio.Semaphore(5) # at most 5 pages at once
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True, proxy=PROXY)
context = await browser.new_context()
tasks = [scrape_one(context, u, sem) for u in urls]
results = await asyncio.gather(*tasks)
await browser.close()
return results
urls = ["https://example.com/products?page=%d" % i for i in range(1, 21)]
data = asyncio.run(main(urls))
Mantenha o limite de concorrência modesto. Páginas paralelas em excesso esgotam a memória e disparam os limites de taxa, o que custa mais retentativas do que o paralelismo economiza. Comece em torno de cinco e aumente apenas enquanto as taxas de sucesso se mantiverem altas.
Como exportar os dados extraídos para JSON e CSV?
Reúna os resultados de cada página em uma lista de dicionários e depois grave essa lista em JSON para dados aninhados ou em CSV para tabelas planas e amigáveis a planilhas. Manter a extração e a exportação separadas torna o scraper mais fácil de testar e reexecutar.
import json
import csv
records = [
{"name": "Laptop A", "price": "999"},
{"name": "Laptop B", "price": "1299"},
]
# Export to JSON
with open("products.json", "w", encoding="utf-8") as f:
json.dump(records, f, ensure_ascii=False, indent=2)
# Export to CSV
with open("products.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=["name", "price"])
writer.writeheader()
writer.writerows(records)
Escolha JSON quando os registros contêm listas ou objetos aninhados, e CSV quando cada registro compartilha os mesmos campos planos e os analistas vão abri-lo em uma planilha. Normalize seus campos antes de gravar para que cada registro tenha as mesmas chaves, e o passo de exportação permanece trivial não importa como a página dinâmica foi renderizada. Daqui, o mesmo JSON alimenta uma carga em banco de dados ou um trabalho de analytics sem parsing adicional.

Perguntas frequentes
Como saber se uma página é dinâmica antes de escrever um scraper?
Compare Ver código-fonte com o painel Elements nas ferramentas de desenvolvedor, ou conte um elemento-alvo no HTML bruto versus o DOM renderizado. Se os dados aparecem apenas depois que o JavaScript roda, a página é dinâmica e precisa de uma chamada de API ou de um navegador headless.
É melhor chamar a API oculta ou usar um navegador headless?
Chame a API oculta quando conseguir encontrá-la, porque ela retorna JSON limpo por uma fração do custo e com mais velocidade que a renderização. Use um navegador headless apenas quando não existir uma API reproduzível ou os dados dependerem de scripts complexos do lado do cliente.
Devo usar Playwright ou Selenium para scraping dinâmico?
O Playwright é o padrão moderno graças à espera automática confiável e ao suporte assíncrono integrado, então projetos novos costumam começar por ele. O Selenium se encaixa bem quando seu stack já o usa ou você precisa do seu suporte a mais linguagens.
Como reduzo a largura de banda do proxy ao usar um navegador headless?
Intercepte as requisições e aborte imagens, fontes, mídia e analytics que você não faz parse. Isso pode reduzir o tráfego significativamente, já que o custo do proxy é medido por gigabyte, e também acelera cada carregamento de página.
Como faço scraping de conteúdo que carrega em scroll infinito?
Primeiro verifique na aba Network a chamada XHR paginada por trás do scroll e itere sobre esse endpoint diretamente. Se você precisar rolar, faça-o em um laço controlado e espere cada novo lote renderizar antes de lê-lo.
Quando a DataImpulse não é a escolha certa?
Se você precisa de proxies ISP estáticos, de uma API de scraping totalmente gerenciada ou de acesso a sites bancários e governamentais, a DataImpulse não é a ferramenta certa. Ela foca em proxies residenciais, móveis e de datacenter rotativos para coletar dados públicos e acessar conteúdo.
Faça scraping de páginas dinâmicas com proxies confiáveis
Se o seu scraper precisa de IPs residenciais, móveis ou de datacenter que se comportam como visitantes reais, você pode começar com tráfego de pagamento conforme o uso a partir de 1 dólar por GB. Crie uma conta na DataImpulse e roteie seu scraping dinâmico por sessões rotativas ou sticky.
