In this Article
Este guia explica como fazer web scraping com JavaScript usando Node.js, o runtime que a maioria dos engenheiros escolhe quando quer coletar dados públicos em escala. Aprender a fazer web scraping com JavaScript significa escolher a ferramenta certa para a página: um cliente HTTP leve para HTML estático, ou um navegador headless para conteúdo carregado via scripts.
Abaixo você encontrará código funcional e executável em Node.js para ambos os casos, além da configuração do projeto, exportações estruturadas para JSON e CSV, paginação, tentativas com backoff, limitação de taxa, autenticação de proxy, interceptação de XHR, e as questões de ética e bloqueio que decidem se um scraper continua funcionando ao longo do tempo.
A DataImpulse é uma provedora de proxies éticos que oferece mais de 90 milhões de endereços IP residenciais, móveis e de datacenter em 195 países. Ela usa um modelo pay-as-you-go a partir de 1 dólar por GB com tráfego sem prazo de expiração, e é utilizada para web scraping, verificação de anúncios, monitoramento de preços, pesquisa de mercado e gerenciamento de múltiplas contas.
Principais Fatos
- Dois conjuntos de ferramentas: páginas estáticas precisam apenas de um cliente HTTP mais um parser como fetch e cheerio, enquanto páginas dinâmicas que renderizam conteúdo no navegador precisam de um motor headless como Puppeteer ou Playwright.
- Melhor tipo de proxy: proxies residenciais rotativos, que usam IPs reais de consumidores e passam pela detecção.
- Preço: a partir de 1 dólar por GB, pay-as-you-go, com tráfego sem prazo de expiração e sem assinatura.
- Cobertura: mais de 90M de IPs obtidos eticamente em 195 países.
- Confiabilidade: taxa de sucesso de 99.51%, com nota 4.8 de 5 no G2.
- Protocolos e segmentação: HTTP, HTTPS e SOCKS5, com segmentação por país inclusa.

O que é web scraping com JavaScript?
Web scraping com JavaScript é o processo de solicitar páginas web programaticamente e extrair dados estruturados delas usando JavaScript, geralmente executado no runtime Node.js em vez de dentro de uma aba do navegador.
JavaScript é uma escolha natural porque a linguagem já modela o Document Object Model, então selecionar elementos parece familiar para desenvolvedores front-end. Na prática, um scraper faz três coisas: busca uma página via HTTP, faz o parsing do markup retornado em uma árvore consultável, e extrai os campos que interessam, como títulos, preços ou links. A complexidade vem de como a página-alvo entrega seus dados, por isso escolher a ferramenta certa importa mais do que o próprio parsing. Páginas estáticas enviam seus dados na primeira resposta HTML, enquanto páginas dinâmicas as montam no navegador após o carregamento, e os dois casos exigem conjuntos de ferramentas diferentes.
Como configurar um projeto de scraping em Node.js?
Crie uma pasta de projeto, inicialize-a com npm e instale as bibliotecas necessárias antes de escrever qualquer código de scraping.
Node.js 18 e versões posteriores vêm com um fetch global embutido, então você não precisa de uma biblioteca HTTP para requisições GET simples, embora muitas equipes ainda prefiram axios por seus interceptores e tratamento de proxy. Os comandos abaixo montam a estrutura de um projeto e instalam cheerio para parsing, axios como cliente alternativo, p-limit para controle de concorrência, e Puppeteer e Playwright para páginas dinâmicas:
mkdir js-scraper && cd js-scraper
npm init -y
npm install cheerio axios p-limit
npm install puppeteer playwright
# enable modern import syntax
npm pkg set type=module
Definir type=module permite usar instruções import. Se preferir CommonJS, mantenha o padrão e use require em vez disso. Os exemplos neste guia usam sintaxe import, mas cada um corresponde diretamente a uma chamada require. Depois que a instalação terminar, você terá tudo o que precisa para scraping estático e dinâmico em um único projeto.
Como fazer scraping de uma página estática com fetch e cheerio?
Para páginas estáticas, baixe o HTML com a função fetch embutida e faça o parsing com cheerio, que oferece uma API estilo jQuery no servidor.
Páginas estáticas retornam seu conteúdo diretamente na resposta HTML inicial, então nenhuma renderização de navegador é necessária. Essa abordagem é rápida e barata porque você baixa apenas markup, não imagens, fontes ou scripts. O exemplo abaixo busca uma página, carrega-a no cheerio e lê cada card de produto:
import * as cheerio from 'cheerio';
async function scrape(url) {
const res = await fetch(url, {
headers: { 'User-Agent': 'Mozilla/5.0 (compatible; MyScraper/1.0)' }
});
if (!res.ok) throw new Error(`Request failed: ${res.status}`);
const html = await res.text();
const $ = cheerio.load(html);
const items = [];
$('.product').each((i, el) => {
items.push({
title: $(el).find('h2').text().trim(),
price: $(el).find('.price').text().trim(),
link: $(el).find('a').attr('href')
});
});
return items;
}
console.log(await scrape('https://example.com/catalog'));
Se preferir axios, a parte de parsing é idêntica e apenas a requisição muda. O axios lança erro automaticamente em códigos de status que não sejam 2xx e facilita a configuração de proxy, por isso muitos scrapers em produção o utilizam:
import axios from 'axios';
import * as cheerio from 'cheerio';
const { data: html } = await axios.get('https://example.com/catalog', {
headers: { 'User-Agent': 'Mozilla/5.0 (compatible; MyScraper/1.0)' },
timeout: 15000
});
const $ = cheerio.load(html);
const titles = $('.product h2').map((i, el) => $(el).text().trim()).get();
console.log(titles);
Antes de apontar um scraper para qualquer site, vale a pena confirmar que o alvo permite isso. Nosso guia sobre como verificar se um site permite scraping percorre o robots.txt e os termos de serviço.
Como extrair dados estruturados para JSON e CSV?
Colete cada registro em um array de objetos simples, depois grave esse array em um arquivo JSON, ou achate-o em linhas separadas por vírgulas para um arquivo CSV.
Texto extraído bruto só é útil quando armazenado em um formato previsível. JSON é o alvo mais simples porque um array JavaScript é serializado diretamente. O trecho abaixo grava os itens extraídos no disco com o módulo de sistema de arquivos embutido:
import { writeFile } from 'node:fs/promises';
const items = await scrape('https://example.com/catalog');
await writeFile('products.json', JSON.stringify(items, null, 2), 'utf-8');
console.log(`Saved ${items.length} records to products.json`);
CSV é melhor quando os dados alimentam uma planilha ou um data warehouse. Você pode construí-lo sem biblioteca alguma escapando aspas e unindo campos, embora o escape importe porque preços e títulos podem conter vírgulas:
import { writeFile } from 'node:fs/promises';
function toCsv(rows) {
const headers = Object.keys(rows[0]);
const escape = (v) => `"${String(v ?? '').replace(/"/g, '""')}"`;
const lines = [headers.join(',')];
for (const row of rows) {
lines.push(headers.map((h) => escape(row[h])).join(','));
}
return lines.join('\n');
}
const items = await scrape('https://example.com/catalog');
await writeFile('products.csv', toCsv(items), 'utf-8');
Mantenha os nomes dos campos estáveis entre execuções para que ferramentas downstream não quebrem quando o layout de uma página mudar ligeiramente. Se também precisar extrair arquivos binários, veja nosso guia sobre como extrair imagens de um site.
Como fazer scraping de várias páginas com paginação?
Percorra as URLs das páginas do site ou siga o link de próxima página em cada página, extraindo cada uma e parando quando não houver mais resultados.
A maioria dos catálogos divide os resultados em páginas numeradas usando um parâmetro de consulta como ?page=2, ou expõe um link de próxima página. O padrão mais seguro lê cada página, coleta seus registros, depois procura a próxima página e para quando ela estiver ausente. O exemplo abaixo percorre páginas numeradas até que uma página retorne zero itens:
import * as cheerio from 'cheerio';
async function scrapeAll(base) {
const all = [];
for (let page = 1; page <= 50; page++) {
const res = await fetch(`${base}?page=${page}`, {
headers: { 'User-Agent': 'Mozilla/5.0 (compatible; MyScraper/1.0)' }
});
if (!res.ok) break;
const $ = cheerio.load(await res.text());
const items = $('.product').map((i, el) => ({
title: $(el).find('h2').text().trim(),
price: $(el).find('.price').text().trim()
})).get();
if (items.length === 0) break;
all.push(...items);
await new Promise((r) => setTimeout(r, 1000));
}
return all;
}
console.log((await scrapeAll('https://example.com/catalog')).length);
Observe a pausa de um segundo entre páginas e o limite rígido de 50 iterações, que evita um loop descontrolado caso o site nunca retorne uma página vazia. Quando o conteúdo é carregado por rolagem em vez de páginas numeradas, você precisa de um navegador headless, o que a seção do Puppeteer abaixo aborda.
Como lidar com tentativas, backoff e limitação de taxa?
Envolva cada requisição em um helper de retry que espera mais tempo após cada falha, e limite quantas requisições rodam ao mesmo tempo para permanecer dentro da tolerância de um site.
Redes não são confiáveis e servidores retornam erros transitórios, então um scraper que desiste na primeira falha perde dados desnecessariamente. Um helper de retry com backoff exponencial espera progressivamente mais entre tentativas, o que também dá tempo para um servidor limitado se recuperar:
async function fetchWithRetry(fn, retries = 4, delay = 1000) {
for (let attempt = 1; attempt <= retries; attempt++) {
try {
return await fn();
} catch (err) {
if (attempt === retries) throw err;
const wait = delay * 2 ** (attempt - 1);
console.warn(`Attempt ${attempt} failed, retrying in ${wait}ms`);
await new Promise((r) => setTimeout(r, wait));
}
}
}
const html = await fetchWithRetry(async () => {
const res = await fetch('https://example.com/catalog');
if (res.status === 429) throw new Error('Rate limited');
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.text();
});
O backoff lida com requisições individuais, mas você também precisa controlar quantas rodam em paralelo. Disparar centenas de requisições concorrentes degrada o servidor-alvo e faz seu tráfego ser sinalizado. A biblioteca p-limit limita a concorrência para que um número fixo de workers processe uma fila de URLs:
import pLimit from 'p-limit';
const limit = pLimit(5); // at most 5 requests in flight
const urls = Array.from({ length: 100 }, (_, i) =>
`https://example.com/item/${i + 1}`
);
const results = await Promise.all(
urls.map((url) =>
limit(() => fetchWithRetry(async () => {
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.text();
}))
)
);
console.log(`Fetched ${results.length} pages with a cap of 5 in parallel`);
Juntos, o backoff e um limite de concorrência mantêm sua pegada razoável e sua taxa de sucesso alta. Para uma lista de verificação mais ampla, veja nosso guia de boas práticas de web scraping.
Como fazer scraping de páginas dinâmicas com Puppeteer?
Para páginas dinâmicas que constroem conteúdo com JavaScript após o carregamento, controle o Chrome headless com Puppeteer, espere o seletor-alvo, e leia o DOM totalmente renderizado.
Muitos sites modernos retornam um shell HTML quase vazio e depois buscam dados por meio de requisições em segundo plano. Um cliente HTTP não vê nada útil, então você precisa de um motor que execute o próprio JavaScript da página. O Puppeteer controla o Chrome headless e pode esperar elementos, clicar e rolar. O exemplo abaixo espera um seletor, depois rola repetidamente para acionar o carregamento por scroll infinito antes de extrair os dados dentro do contexto do navegador:
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({ headless: 'new' });
const page = await browser.newPage();
await page.setUserAgent('Mozilla/5.0 (compatible; MyScraper/1.0)');
await page.goto('https://example.com/feed', { waitUntil: 'networkidle2' });
// wait until the first batch of cards is present
await page.waitForSelector('.product');
// infinite scroll: scroll to the bottom until height stops growing
let previousHeight = 0;
for (let i = 0; i < 20; i++) {
const height = await page.evaluate('document.body.scrollHeight');
if (height === previousHeight) break;
previousHeight = height;
await page.evaluate('window.scrollTo(0, document.body.scrollHeight)');
await new Promise((r) => setTimeout(r, 1500));
}
const items = await page.evaluate(() =>
Array.from(document.querySelectorAll('.product')).map((el) => ({
title: el.querySelector('h2')?.innerText.trim(),
price: el.querySelector('.price')?.innerText.trim()
}))
);
console.log(`Extracted ${items.length} items`);
await browser.close();
O loop para assim que a altura da página deixa de aumentar, o que sinaliza que nenhum conteúdo novo está carregando, e o limite rígido de 20 rolagens evita um loop infinito. Para mais sobre essa classe de alvo, veja nosso guia sobre como extrair páginas web dinâmicas.
Como usar o Playwright como alternativa ao Puppeteer?
O Playwright funciona quase de forma idêntica ao Puppeteer, mas adiciona suporte de primeira classe para Chromium, Firefox e WebKit, além de locators com espera automática que reduzem seletores instáveis.
O Playwright foi criado por alguns dos engenheiros originais do Puppeteer e compartilha a maior parte de sua API, então migrar geralmente é uma questão de renomear algumas chamadas. Seus locators esperam automaticamente que os elementos fiquem acionáveis, o que elimina muitas chamadas manuais a waitForSelector. O scraper equivalente fica assim:
import { chromium } from 'playwright';
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
userAgent: 'Mozilla/5.0 (compatible; MyScraper/1.0)'
});
const page = await context.newPage();
await page.goto('https://example.com/feed', { waitUntil: 'networkidle' });
await page.locator('.product').first().waitFor();
const items = await page.locator('.product').evaluateAll((els) =>
els.map((el) => ({
title: el.querySelector('h2')?.innerText.trim(),
price: el.querySelector('.price')?.innerText.trim()
}))
);
console.log(`Extracted ${items.length} items`);
await browser.close();
Escolha o Playwright quando precisar testar o mesmo scraper em vários motores de navegador ou quiser sua simulação de rede embutida. Escolha o Puppeteer quando visar apenas o Chrome e quiser a dependência menor. Ambos consomem muito mais memória do que fetch e cheerio, então reserve-os para páginas que realmente exigem renderização.
Qual ferramenta de scraping em JavaScript você deve escolher?
Use fetch ou axios com cheerio para HTML estático, e recorra ao Puppeteer ou Playwright apenas quando uma página renderiza seus dados no navegador.
A ferramenta certa depende de como a página entrega os dados e de quanto você valoriza velocidade em detrimento da fidelidade de renderização. Clientes HTTP são rápidos e leves mas não conseguem executar JavaScript; navegadores headless executam tudo o que um usuário real vê, mas custam mais memória e tempo. A tabela abaixo resume os trade-offs:
| Ferramenta | Executa JS | Velocidade | Melhor para |
|---|---|---|---|
| fetch + cheerio | Não | Mais rápida | HTML estático, alto volume |
| axios + cheerio | Não | Rápida | HTML estático, configuração de proxy fácil |
| Puppeteer | Sim | Lenta | Páginas dinâmicas somente Chrome |
| Playwright | Sim | Lenta | Dinâmico, testes multi-navegador |
Um padrão comum de produção mistura os dois: use um navegador headless apenas para alcançar as páginas ou endpoints que precisam de renderização, depois recorra a um cliente HTTP simples para o grosso das requisições. JavaScript é uma entre várias stacks viáveis para esse trabalho; se você está comparando ecossistemas, nosso guia de web scraping com Rust cobre uma alternativa compilada.
Como rotear scrapers JavaScript por um proxy?
Passe as configurações de proxy para seu cliente HTTP ou para as opções de inicialização do navegador para que as requisições saiam de um endereço IP diferente, o que distribui a carga e reduz a chance de um único endereço ser limitado por taxa.
Fazer scraping de muitas páginas a partir de um único IP é a forma mais rápida de ser bloqueado. Rotacionar requisições entre um pool de endereços faz o tráfego parecer vir de muitos visitantes separados. Com axios, você fornece o host, a porta e as credenciais do proxy na configuração da requisição. O exemplo abaixo roteia por um gateway da DataImpulse:
import axios from 'axios';
const { data } = await axios.get('https://example.com', {
proxy: {
protocol: 'http',
host: 'gw.dataimpulse.com',
port: 823,
auth: { username: 'YOUR_USERNAME', password: 'YOUR_PASSWORD' }
},
headers: { 'User-Agent': 'Mozilla/5.0 (compatible; MyScraper/1.0)' }
});
console.log(data.length, 'bytes received through the proxy');
O Puppeteer recebe o proxy como um argumento de inicialização e depois se autentica na página, porque o Chrome não aceita credenciais dentro da URL do proxy. O padrão é o mesmo no Playwright, que aceita um objeto de proxy com nome de usuário e senha diretamente:
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({
headless: 'new',
args: ['--proxy-server=gw.dataimpulse.com:823']
});
const page = await browser.newPage();
await page.authenticate({
username: 'YOUR_USERNAME',
password: 'YOUR_PASSWORD'
});
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
console.log(await page.title());
await browser.close();
A DataImpulse oferece proxies residenciais, proxies de datacenter e proxies móveis via HTTP, HTTPS e SOCKS5, com sessões rotativas e fixas. A segmentação por país está inclusa, enquanto segmentação por estado, cidade, CEP e ASN são complementos pagos. Se uma requisição de proxy retornar um erro de autenticação, nossa nota sobre o erro HTTP 407 explica a causa mais comum, e nosso guia de autenticação de proxy cobre formatos de credenciais em mais detalhes.
Como interceptar XHR e endpoints JSON?
Em vez de extrair o HTML renderizado, observe o tráfego de rede em um navegador headless e leia as respostas JSON que a página busca em segundo plano, o que costuma ser mais limpo e rápido.
Páginas dinâmicas geralmente obtêm seus dados de uma API oculta que retorna JSON. Ler essa resposta diretamente ignora completamente o parsing do DOM e entrega registros bem estruturados. Tanto o Puppeteer quanto o Playwright permitem escutar respostas e capturar as que você quiser. O exemplo do Puppeteer abaixo captura qualquer resposta JSON de um caminho de API:
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({ headless: 'new' });
const page = await browser.newPage();
const captured = [];
page.on('response', async (response) => {
const url = response.url();
if (url.includes('/api/') && response.headers()['content-type']?.includes('application/json')) {
try {
captured.push(await response.json());
} catch (err) {
// non-JSON body, ignore
}
}
});
await page.goto('https://example.com/feed', { waitUntil: 'networkidle2' });
console.log(`Captured ${captured.length} JSON payloads`);
await browser.close();
Depois de conhecer a URL do endpoint e seus parâmetros, você muitas vezes pode dispensar o navegador por completo e chamar a API JSON com fetch simples, passando os mesmos headers que a página enviou. Isso lhe dá a velocidade de um scraper estático com a completude de um renderizado, e é o maior ganho de performance disponível para sites dinâmicos.
Quais são os limites éticos e legais do scraping?
Extraia apenas dados públicos, respeite o robots.txt e os termos de serviço do site, evite dados pessoais, e nunca sobrecarregue um servidor. Essas práticas mantêm seu projeto defensável e sustentável.
Verifique o arquivo robots.txt na raiz do domínio para ver quais caminhos um site pede aos crawlers para evitar, e leve esses pedidos a sério. Identifique seu scraper com um User-Agent honesto, limite suas requisições, e faça cache das respostas para não buscar novamente a mesma página desnecessariamente. No lado da infraestrutura, a origem dos seus endereços IP importa. A DataImpulse opera proxies éticos obtidos de usuários que optam por participar e são compensados, alinhados ao GDPR, com um acordo de processamento de dados disponível. Ética não é apenas uma questão de conformidade; scrapers educados e transparentes também são os que continuam funcionando. Para os princípios por trás de uma coleta sustentável, veja nosso guia sobre web scraping ético.

Perguntas frequentes
Preciso do Node.js para fazer scraping com JavaScript?
Para qualquer coisa além de um teste rápido de uma única página, sim. O Node.js permite executar scrapers em um servidor, percorrer muitas URLs, lidar com tentativas, exportar dados e usar proxies, nada disso o console do navegador faz bem.
Quando devo usar o Puppeteer em vez de fetch e cheerio?
Use fetch ou axios com cheerio quando os dados estão presentes na resposta HTML inicial. Mude para Puppeteer ou Playwright somente quando o conteúdo é renderizado por JavaScript depois que a página carrega, porque um navegador headless é bem mais pesado.
Como exporto dados extraídos para CSV no Node.js?
Colete registros como um array de objetos, depois construa linhas separadas por vírgulas unindo os valores de cada objeto e escapando aspas e vírgulas. Grave o resultado em um arquivo com o módulo fs embutido, ou use uma biblioteca como csv-stringify.
Por que sou bloqueado ao fazer scraping com JavaScript?
As causas comuns são requisições demais de um único IP, um User-Agent ausente ou suspeito, e nenhuma pausa entre requisições. Proxies rotativos, headers realistas, tentativas com backoff e limitação de taxa reduzem os bloqueios.
Qual tipo de proxy é melhor para scraping com JavaScript?
Proxies de datacenter são rápidos e baratos para alvos tolerantes, enquanto proxies residenciais e móveis parecem mais usuários reais em sites rígidos. Sessões rotativas ajudam a distribuir requisições entre muitos endereços.
Quando a DataImpulse não é a opção certa?
Se você precisa de proxies ISP estáticos, uma API de scraping totalmente gerenciada, ou 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.
Guias relacionados
Comece a fazer scraping com proxies confiáveis
Pronto para manter seus scrapers JavaScript funcionando sem bloqueios constantes? A DataImpulse oferece IPs residenciais, móveis e de datacenter obtidos eticamente a partir de 1 dólar por GB com tráfego sem prazo de expiração e sem assinatura. Crie uma conta e roteie seu primeiro scraper por um proxy hoje mesmo.
