1

Web Scraping eficiente: Do Headless Browser às APIs ocultas

Quando você decide criar um agregador de dados em tempo real, a primeira ideia que vem à cabeça para coletar informações de grandes plataformas é: "Vou subir um Puppeteer, simular cliques humanos e pronto".

Foi exatamente isso que fiz quando comecei a construir a base de dados do NaveIdeal, uma plataforma criada para minerar, higienizar e auditar ofertas de veículos de múltiplos portais (como OLX, WebMotors e iCarros) em tempo real, calculando a defasagem contra a Tabela FIPE para ranquear as melhores oportunidades do mercado.

No papel, parecia perfeito. Na prática, me fez desistir do projeto no primeiro mês.

Neste post, quero compartilhar as 3 fases de maturidade de Web Scraping pelas quais passei, os erros de consumo de memória que quase derrubaram a infraestrutura e como saí de instâncias pesadas de Chromium para requisições de alta performance consumindo frações míseras de CPU e RAM.

1. O Ponto de Partida e o Pesadelo da RAM (Puppeteer)
No início, para garantir que as páginas carregassem scripts dinâmicos e contornassem desafios visuais, configurei uma fila usando puppeteer-cluster e puppeteer-extra-plugin-stealth.

A lógica de extração era parecida com isto:


async function scrapeComBrowser(page, url) {
  await page.goto(url, { waitUntil: "domcontentloaded", timeout: 60000 });
  await page.waitForSelector("#price-box-container", { timeout: 10000 });

  const dados = await page.evaluate(() => {
    const titulo = document.querySelector("h1")?.innerText || "";
    const preco = document.querySelector("#price-box-container")?.innerText || "";
    return { titulo, preco };
  });

  return dados;
}

Onde a conta não fechava:
Consumo de Memória: Cada aba ou instância do Chromium consome facilmente entre 150 MB e 350 MB de RAM. Multiplique isso por uma concorrência mínima de 4 workers em um servidor modesto e você terá a máquina travando ou muito lenta.

Overhead de CPU: O Chromium renderiza CSS (mas temos como configurar para o puppeteer não renderizar isso), calcula layout e processa scripts que você não precisa para nada se o seu objetivo for apenas extrair texto e JSON.

Fragilidade e Timeouts: Quedas de conexão no protocolo do DevTools (protocolTimeout) eram comuns durante varreduras contínuas.

2. A Virada de Chave: HTTP Puro + Cheerio
Percebi que em 80% das páginas que eu precisava varrer, o HTML inicial já vinha com o conteúdo renderizado no servidor (SSR) ou embutido em tags iniciais. Eu não precisava executar JavaScript no cliente, precisava apenas fazer o download do HTML cru e fazer o parse da árvore estática.

Trocamos o Puppeteer por um cliente HTTP otimizado (com suporte a proxies residenciais e headers humanizados) aliado ao Cheerio (uma implementação rápida e leve do núcleo do jQuery voltada para o servidor).

const cheerio = require("cheerio");
const proxyManager = require("./services/proxyManager");//aqui esta configurado os proxys residencias

async function scrapeComCheerio(url) {
  const response = await proxyManager.fetch({ url });
 
  const $ = cheerio.load(response.body);

  const title = $("h1").first().text().trim();
  const precoTxt = $("#price-box-container").text().trim();
  const preco = Number(precoTxt.replace(/\D/g, ""));

  return { title, preco };
}

O impacto imediato:
Memória RAM: Caiu de centenas de megabytes por worker para menos de 30 MB no processo total.

Velocidade: Uma requisição que as vezes levava até 10 ou 15 segundos no Chromium passou a ser resolvida em 500ms a 1.2s.

3. O Santo Graal: Mineração Direta de APIs e Payloads Ocultos
Se usar Cheerio já reduziu o consumo em 80%, a descoberta seguinte mudou completamente o jogo: muitos portais modernos trafegam dados estruturados por trás das cortinas.

Em vez de fazer o scraping visual de dezenas de elementos (.div > span > a), começamos a inspecionar a aba Network das plataformas. Descobrimos que portais robustos como a WebMotors alimentam suas listagens e telas através de APIs REST internas ou dados serializados.

No extrator do NaveIdeal, em vez de baixar todo o HTML de uma listagem para caçar tags CSS, consumimos diretamente os endpoints de busca com filtros ordenados:

async function extrairViaApiInterna(estado, tipo, pagina = 1) {
  const apiUrl = `https://www.site.com.br/api/search/${tipo}?state=${estado}&actualPage=${pagina}&displayPerPage=24`;

  const response = await proxyManager.fetch({
    url: apiUrl,
    headers: {
      "Accept": "application/json",
      "Origin": "https://www.site.com.br"
    }
  });

  const json = JSON.parse(response.body);
  const anuncios = json.SearchResults || [];

  return anuncios.map(item => ({
    id: item.UniqueId,
    marca: item.Specification?.Make?.Value,
    modelo: item.Specification?.Model?.Value,
    versao: item.Specification?.Version?.Value,
    ano: item.Specification?.YearModel,
    preco: item.Prices?.Price,
    fotos: item.Media?.Photos?.map(p => p.PhotoPath)
  }));
}

Por que isso é superior?
Zero quebra de layout: Se a plataforma mudar a cor do botão ou a classe CSS do preço de .ad-price para .sc-price-new, o seu scraper tradicional quebra. A API estruturada tende a ser muito mais estável.

Custo Computacional Quase Zero: Fazer o parse de uma string JSON nativa com JSON.parse() é ordens de grandeza mais rápido do que construir e percorrer nós de DOM.

Onde tudo isso se conecta?
Todo esse processo de extração leve alimenta o pipeline do NaveIdeal.

Depois que esses dados brutos chegam sem peso na infraestrutura, eles entram em uma camada de normalização semântica, cruzam com a base histórica da Tabela FIPE e geram um Score de Oportunidade para filtrar veículos com real margem de desconto versus distorções de mercado.

Carregando publicação patrocinada...
1

Bacana AlefSantos. Parabéns.

Já tive problema muito parecido em um projeto. Era alto consumo de recursos e quebras frequentes em produção. Porém, no meu caso não tinha como fazer requisições diretamente via APIs, então, foi necessário usar parte do projeto em Rust para dar conta de parsear tudo rápido o suficiente dado o volume.

Legal o seu site. Pretende monetizar ?

1