Saltar para o conteúdo
Voltar ao blogue

Como extrair dados de uma tabela HTML em JavaScript e exportar um ficheiro CSV limpo

Mihai MaximÚltima atualização em 14 min read
Como extrair dados de uma tabela HTML em JavaScript e exportar um ficheiro CSV limpo
Resumo: Primeiro, determine se as linhas da tabela existem na resposta HTTP, se são obtidas através de um pedido de dados ou se requerem que um navegador as apresente. Em seguida, utilize o script estático do Axios e do Cheerio ou a abordagem específica do Puppeteer apresentada abaixo para extrair dados de uma tabela HTML em JavaScript, validar registos com base nos cabeçalhos, eliminar páginas duplicadas e gravar ficheiros CSV de forma segura. Uma tabela HTML organiza a informação em linhas e colunas, normalmente com <table>, <tr>, <th>, e <td> . Extrair uma tabela HTML em JavaScript significa recuperar ou renderizar essa marcação, converter cada linha de dados num objeto JavaScript, validar o resultado e serializá-lo para um formato como o CSV.

O ciclo de iteração pelas linhas raramente é a parte difícil. A maioria das falhas ocorre porque o scraper lê a resposta do servidor enquanto o programador inspeciona um DOM renderizado pelo navegador, seleciona a tabela errada, assume posições fixas das células ou exporta valores sem escape. Um fluxo de trabalho fiável escolhe o método de extração mais leve que consiga efetivamente aceder aos dados.

Este tutorial de web scraping com Node.js começa com o Axios e o Cheerio para marcação estática e, em seguida, adiciona um percurso específico de extração de tabelas com o Puppeteer para as linhas criadas após o carregamento da página. O analisador deriva as chaves a partir dos títulos das tabelas, em vez de assumir um único conjunto de dados. Também sinaliza linhas malformadas, preserva os valores como cadeias de caracteres até que a conversão seja intencional e verifica se o resultado é utilizável. O resultado é uma forma prática de extrair tabelas HTML em JavaScript sem fingir que rowspan, colspan, tabelas aninhadas ou todas as grelhas personalizadas possam ser achatadas automaticamente.

Escolha o método de extração adequado antes de escrever o código

Antes de extrair dados de tabelas HTML em JavaScript, identifique onde os dados das linhas se encontram. Na marcação padrão, <tr> representa uma linha, <th> normalmente identifica uma coluna e <td> contém um valor normal. A referência ao elemento de tabela HTML da MDN é uma verificação semântica útil quando a estrutura é desconhecida.

Siga esta ordem de decisão:

  1. Linhas no HTML da resposta: solicite a página com o Axios e analise-a com o Cheerio.
  2. Linhas devolvidas pelo Fetch ou XHR: utilize o ponto de extremidade de dados permitido, desde que este seja estável e adequado.
  3. Linhas criadas apenas após a execução de scripts ou interação: utilize a automação do navegador.

Esta ordem torna um scraper de tabelas em JavaScript mais simples e económico de operar do que ter de iniciar um navegador para cada página.

Verifique se as linhas estão presentes no HTML da resposta

Abra «Ver Código-fonte», e não apenas o painel «Elementos», e procure um valor de célula distinto. Também pode guardar response.data e pesquisá-lo diretamente. Se o valor e um seletor de linha específico estiverem presentes, a automação do navegador é desnecessária, mesmo quando a página apresenta controlos de paginação.

Teste os seletores no DevTools antes de enviar pedidos repetidos e, em seguida, volte a testá-los com base na marcação obtida. Uma ficha de referência de seletores CSS ajuda quando table tr também capturar navegação, tabelas aninhadas ou linhas de rodapé.

Verifique se existem linhas obtidas da rede ou renderizadas pelo navegador

Se o valor estiver ausente do código-fonte, filtre o painel «Rede» por «Fetch/XHR», atualize a página, altere a página ou o filtro e inspecione as respostas. Um ponto final de dados autorizado e estável é normalmente mais fácil de validar do que células renderizadas, mas confirme se a sua utilização é permitida.

Opte por um navegador sem interface gráfica quando o JavaScript, o estado de início de sessão, os cliques ou a rolagem forem essenciais. Uma comparação entre o Cheerio e o Puppeteer pode ajudar a documentar essa escolha. Evite expressões regulares como analisador HTML geral; utilize-as apenas para padrões específicos dentro de uma célula já selecionada.

Crie um scraper reutilizável com Axios e Cheerio para extrair tabelas HTML em JavaScript

Para uma página estática, o fluxo de trabalho é simples: o Axios recupera a resposta, o Cheerio carrega a marcação e um analisador com âmbito específico transforma as linhas em objetos. A escolha de design importante é a configuração. Aceite o URL, o seletor da tabela e o caminho de saída como entradas e, em seguida, derive as chaves dos registos a partir dos títulos da tabela selecionada.

Esta abordagem de extração web com Axios e Cheerio evita um mapeamento frágil do tipo «a célula zero é o nome, a célula um é o preço». Funciona em tabelas convencionais cujos títulos e larguras de linha são consistentes, ao mesmo tempo que continua a identificar exceções estruturais.

Configure o projeto Node.js e solicite a página de forma segura

Crie um projeto e instale os três pacotes utilizados pelo exemplo estático:

mkdir table-scraper
cd table-scraper
npm init -y
npm install axios cheerio objects-to-csv

Criar scrape-table.js. O script completo utiliza o CommonJS de forma consistente, aplica um tempo limite à solicitação, aceita apenas estados HTTP bem-sucedidos e agrupa o trabalho de solicitação, análise, validação e exportação num único bloco de erros. Não repete cegamente as tentativas em caso de erros do cliente, pois solicitações repetidas não irão corrigir um seletor em falta, um acesso negado ou um URL inválido.

As APIs dos pacotes podem sofrer alterações. Fixe as versões após testar o script no seu ambiente e consulte a documentação oficial do pacote antes de considerar o exemplo como uma dependência de produção.

Selecione a tabela e defina nomes de colunas estáveis

Limite o seletor a uma tabela sempre que possível, por exemplo #results em vez de table. O analisador seleciona deliberadamente a primeira correspondência e, em seguida, procura a última linha em <thead>. Se não houver <thead>, trata a primeira linha que contenha <th> células como o cabeçalho. Se os rótulos continuarem indisponíveis, gera column_1, column_2, e assim por diante.

O texto do cabeçalho é normalizado para minúsculas no formato «snake case». Um cabeçalho em branco recebe um valor de recurso de reserva posicional, enquanto as duplicatas se tornam chaves como price e price_2. Isso impede que as células posteriores substituam as propriedades anteriores. Para cabeçalhos multilingues ou um contrato de dados público, substitua a normalização automática por um mapa de cabeçalhos explícito.

Cabeçalhos com várias linhas, rowspane colspan exigem lógica específica da tabela, uma vez que a grelha visual pode não corresponder à contagem de células do DOM. Não faça suposições sem avisar. Inspecione a marcação, defina as colunas pretendidas e adicione uma etapa de expansão da grelha se essa semântica for relevante.

Mapeie cada linha para um objeto sem nomes de campos codificados de forma rígida

Leia as células filhas diretas de cada linha, em vez de todas as células descendentes. Essa distinção impede que uma tabela aninhada adicione valores inesperados à sua linha pai. O script constrói primeiro uma matriz de cadeias de células limpas, ignora as linhas que não contêm texto significativo e calcula a linha mais larga observada.

Em seguida, cria uma chave para cada coluna e emparelha as chaves com os valores por índice. Os valores em falta tornam-se cadeias vazias. As células extra recebem cabeçalhos gerados quando a análise inicial as revela. O analisador também regista um aviso sempre que a contagem de células de uma linha difere da largura calculada, para que as colunas deslocadas não passem despercebidas.

Este é o núcleo reutilizável quando se extrai dados de uma tabela HTML em JavaScript: o array de destino existe fora da iteração das linhas e cada linha aceite contribui com um objeto completo. Continua a ser importante verificar os avisos. Uma discrepância na largura pode indicar uma célula que se estende legitimamente, uma coluna de controlo oculta, marcação malformada ou um seletor que se estendeu para outra secção da tabela.

Normalize e valide os valores extraídos

Normalize o ruído de apresentação, não o significado. O exemplo converte espaços não separáveis em espaços normais, apara as extremidades e comprime os espaços em branco repetidos. Deixa datas, moedas, percentagens, identificadores e zeros à esquerda como cadeias de caracteres, porque valores como 01/02/03 ou 1,234 são ambíguos sem regras de localização e esquema.

A validação deve falhar de forma evidente quando o seletor da tabela estiver em falta ou não restarem registos não vazios. Verifique também as colunas obrigatórias, inspecione um objeto de amostra, analise os avisos de incompatibilidade e confirme que o ficheiro final existe antes de uma tarefa a jusante o utilizar. Se precisar de valores tipados, aplique conversores explícitos por coluna após a extração e mantenha o valor bruto para depuração. Um guia de análise de dados é um recurso útil quando essas conversões se tornam uma etapa separada do pipeline.

Serialize os registos para CSV de forma segura

Não crie ficheiros CSV com row.join(","). Vírgulas, aspas duplas, retornos de carro e quebras de linha dentro de uma célula requerem escape e aspas. Utilize um serializador ativamente mantido cujo comportamento tenha testado. O exemplo utiliza objects-to-csv, atribui a cada objeto as propriedades na mesma ordem do cabeçalho e grava o array JavaScript resultante num ficheiro CSV.

Teste a versão escolhida com fixtures que contenham vírgulas, aspas, quebras de linha incorporadas, valores em branco e texto não ASCII. Confirme o comportamento dos cabeçalhos e do UTF-8 nas aplicações que irão ler o ficheiro. Uma serialização CSV correta não consegue corrigir uma tabela de origem irregular; resolva células que se estendem por várias linhas, semânticas duplicadas e linhas incompatíveis antes da exportação.

Execute o scraper de tabela estática completo

Guarde o seguinte como scrape-table.js. Abrange o pedido, a seleção, a normalização dos cabeçalhos, o mapeamento de linhas, as verificações e a exportação para CSV. Edite requiredColumns quando os dados a jusante necessitarem de campos garantidos.

const axios = require("axios");
const cheerio = require("cheerio");
const ObjectsToCsv = require("objects-to-csv");

const [url, tableSelector = "table", outputPath = "table.csv"] =
  process.argv.slice(2);

const requiredColumns = [];

function clean(value) {
  return value
    .replace(/\u00a0/g, " ")
    .replace(/\s+/g, " ")
    .trim();
}

function uniqueHeaders(labels, width) {
  const seen = new Map();

  return Array.from({ length: width }, (_, index) => {
    const normalized = clean(labels[index] || "")
      .toLowerCase()
      .replace(/[^a-z0-9]+/g, "_")
      .replace(/^_+|_+$/g, "");

    const base = normalized || `column_${index + 1}`;
    const count = (seen.get(base) || 0) + 1;
    seen.set(base, count);

    return count === 1 ? base : `${base}_${count}`;
  });
}

function parseTable(html) {
  const $ = cheerio.load(html);
  const table = $(tableSelector).first();

  if (!table.length) {
    throw new Error(`No table matched selector: ${tableSelector}`);
  }

  let headerRow = table.find("thead tr").last();

  if (!headerRow.length) {
    const firstRow = table.find("tr").first();
    if (firstRow.children("th").length) {
      headerRow = firstRow;
    }
  }

  const headerLabels = headerRow
    .children("th, td")
    .map((_, cell) => clean($(cell).text()))
    .get();

  let dataRows = table.children("tbody").children("tr");
  if (!dataRows.length) {
    dataRows = table.children("tr");
  }

  const headerNode = headerRow.get(0);
  const matrix = dataRows
    .toArray()
    .filter((row) => row !== headerNode)
    .map((row) =>
      $(row)
        .children("th, td")
        .map((_, cell) => clean($(cell).text()))
        .get()
    )
    .filter((cells) => cells.some(Boolean));

  const width = Math.max(
    0,
    headerLabels.length,
    ...matrix.map((cells) => cells.length)
  );

  if (!width) {
    throw new Error("The selected table has no readable cells.");
  }

  const headers = uniqueHeaders(headerLabels, width);
  const warnings = [];

  if (headerLabels.length && headerLabels.length !== width) {
    warnings.push(
      `Header has ${headerLabels.length} cells; widest row has ${width}.`
    );
  }

  const records = matrix.map((cells, rowIndex) => {
    if (cells.length !== width) {
      warnings.push(
        `Row ${rowIndex + 1} has ${cells.length} cells; expected ${width}.`
      );
    }

    return Object.fromEntries(
      headers.map((header, cellIndex) => [
        header,
        cells[cellIndex] ?? "",
      ])
    );
  });

  return { headers, records, warnings };
}

async function main() {
  if (!url) {
    throw new Error(
      'Usage: node scrape-table.js "<url>" "<table-selector>" "<output.csv>"'
    );
  }

  const response = await axios.get(url, {
    timeout: 15000,
    responseType: "text",
    maxRedirects: 5,
    validateStatus: (status) => status >= 200 && status < 300,
    headers: {
      "User-Agent": "TableScraper/1.0 (+contact@example.com)",
    },
  });

  const { headers, records, warnings } = parseTable(response.data);

  if (!records.length) {
    throw new Error("The table matched, but no nonempty rows were parsed.");
  }

  for (const column of requiredColumns) {
    if (!headers.includes(column)) {
      throw new Error(`Required column is missing: ${column}`);
    }
  }

  if (warnings.length) {
    console.warn(warnings.slice(0, 10).join("\n"));
  }

  await new ObjectsToCsv(records).toDisk(outputPath);

  console.log(`Wrote ${records.length} rows to ${outputPath}`);
  console.log(`Columns: ${headers.join(", ")}`);
  console.log("Sample record:", records[0]);
}

main().catch((error) => {
  console.error(`Scrape failed: ${error.message}`);
  process.exitCode = 1;
});

Execute-o com o URL de destino, um seletor de tabela entre aspas e um caminho de saída:

node scrape-table.js "https://example.com/page" "#results" "results.csv"

Inspecione as colunas apresentadas e o registo de amostra antes de confiar no ficheiro CSV. Trate cada aviso de largura como um problema de qualidade dos dados a investigar.

Lidar com tabelas renderizadas em JavaScript e paginadas

Para extrair tabelas HTML em JavaScript quando faltam linhas na resposta, utilize a automação do navegador após excluir um ponto de extremidade de dados adequado. Um navegador sem interface gráfica executa scripts da página e pode clicar em controlos, mas consome mais memória e introduz mais estados de falha do que uma solicitação HTTP.

O caminho em destaque abaixo utiliza o Puppeteer e um controlo Next configurável. Instale-o no mesmo projeto:

npm install puppeteer

Este caminho é adequado para uma tabela renderizada convencional. Não tenta resolver grelhas virtualizadas que reciclam nós DOM, desafios de autenticação ou frameworks de componentes arbitrários sem alterações no seletor.

Aguarde pelas linhas e recolha cada página ou lote de deslocamento

Aguarde por um seletor de linha em vez de ficar inativo por um período de tempo arbitrário. Em cada página, extraia uma matriz de células, adicione os seus registos, clique em «Next» e aguarde até que a matriz mude. A documentação oficial do Puppeteer waitForSelector descreve a espera pelo seletor utilizada para o primeiro lote.

Guarde isto como scrape-rendered.js. O código reutiliza a estratégia de cabeçalho, falha em colunas deslocadas e recusa-se a gravar um ficheiro quando a paginação parece estar bloqueada.

const puppeteer = require("puppeteer");
const ObjectsToCsv = require("objects-to-csv");

const [
  url,
  tableSelector = "#results",
  nextSelector = 'button[aria-label="Next"]',
  outputPath = "rendered.csv",
] = process.argv.slice(2);

function clean(value) {
  return value.replace(/\u00a0/g, " ").replace(/\s+/g, " ").trim();
}

function uniqueHeaders(labels, width) {
  const seen = new Map();

  return Array.from({ length: width }, (_, index) => {
    const normalized = clean(labels[index] || "")
      .toLowerCase()
      .replace(/[^a-z0-9]+/g, "_")
      .replace(/^_+|_+$/g, "");

    const base = normalized || `column_${index + 1}`;
    const count = (seen.get(base) || 0) + 1;
    seen.set(base, count);
    return count === 1 ? base : `${base}_${count}`;
  });
}

async function main() {
  if (!url) {
    throw new Error(
      'Usage: node scrape-rendered.js "<url>" "<table>" "<next>" "<output.csv>"'
    );
  }

  const rowSelector = `${tableSelector} tbody tr`;
  const headerSelector =
    `${tableSelector} thead tr:last-child th, ` +
    `${tableSelector} thead tr:last-child td`;

  const browser = await puppeteer.launch({ headless: true });

  try {
    const page = await browser.newPage();
    await page.goto(url, {
      waitUntil: "domcontentloaded",
      timeout: 30000,
    });
    await page.waitForSelector(rowSelector, { timeout: 15000 });

    const labels = await page.$$eval(headerSelector, (cells) =>
      cells.map((cell) => cell.textContent.replace(/\s+/g, " ").trim())
    );

    const recordsByValue = new Map();
    let headers;
    let pageNumber = 0;

    while (true) {
      pageNumber += 1;

      if (pageNumber > 1000) {
        throw new Error("Safety limit reached before pagination completed.");
      }

      const matrix = await page.$$eval(rowSelector, (rows) =>
        rows.map((row) =>
          Array.from(row.children)
            .filter((cell) => cell.matches("th, td"))
            .map((cell) => cell.textContent.replace(/\s+/g, " ").trim())
        )
      );

      if (!matrix.length) {
        throw new Error(`No rows found on rendered page ${pageNumber}.`);
      }

      if (!headers) {
        const width = Math.max(
          labels.length,
          ...matrix.map((cells) => cells.length)
        );
        headers = uniqueHeaders(labels, width);
      }

      for (const cells of matrix) {
        if (cells.length !== headers.length) {
          throw new Error(
            `Column mismatch on page ${pageNumber}: ` +
              `${cells.length} cells, expected ${headers.length}.`
          );
        }

        const record = Object.fromEntries(
          headers.map((header, index) => [header, cells[index]])
        );

        recordsByValue.set(JSON.stringify(record), record);
      }

      const next = await page.$(nextSelector);
      if (!next) break;

      const disabled = await next.evaluate(
        (element) =>
          element.disabled ||
          element.getAttribute("aria-disabled") === "true" ||
          element.classList.contains("disabled")
      );

      if (disabled) break;

      const previous = JSON.stringify(matrix);
      await next.click();

      try {
        await page.waitForFunction(
          ({ rowSelector, previous }) => {
            const current = Array.from(
              document.querySelectorAll(rowSelector)
            ).map((row) =>
              Array.from(row.children)
                .filter((cell) => cell.matches("th, td"))
                .map((cell) =>
                  cell.textContent.replace(/\s+/g, " ").trim()
                )
            );

            return current.length > 0 && JSON.stringify(current) !== previous;
          },
          { timeout: 10000 },
          { rowSelector, previous }
        );
      } catch {
        throw new Error(
          `Pagination did not advance after page ${pageNumber}; ` +
            "refusing to save a possibly partial file."
        );
      }
    }

    const records = [...recordsByValue.values()];

    if (!records.length) {
      throw new Error("No rendered records were collected.");
    }

    await new ObjectsToCsv(records).toDisk(outputPath);
    console.log(`Wrote ${records.length} unique rows to ${outputPath}`);
  } finally {
    await browser.close();
  }
}

main().catch((error) => {
  console.error(`Rendered scrape failed: ${error.message}`);
  process.exitCode = 1;
});

Para a rolagem infinita, substitua o clique em «Seguinte» por uma ação de rolagem e compare a contagem de linhas ou a assinatura de linhas após cada lote. Pare apenas depois de a contagem e a altura de rolagem permanecerem inalteradas durante várias verificações e mantenha um limite de segurança de passagens máximas.

Pare com segurança e elimine registos duplicados

Pare quando o botão «Seguinte» estiver ausente ou desativado e mantenha um limite de páginas. Se o controlo permanecer ativado, mas a assinatura da linha não se alterar, indique uma paginação parcial em vez de guardar silenciosamente.

O exemplo remove duplicados exatos com um Map. Dê preferência a um ID estável ou a um URL canónico, quando disponível, uma vez que as chaves de registo completo podem fundir linhas repetidas legítimas. Um guia sobre web scraping com JavaScript e Node.js pode ajudar a separar a navegação, a extração e o armazenamento.

Resolver falhas comuns na extração de tabelas

Ao extrair tabelas HTML em JavaScript, classifique as falhas por recuperação, seleção, paginação ou exportação.

Sintoma

Solução prática

O Axios analisa zero linhas

Procure na resposta guardada um valor conhecido; se não estiver presente, verifique os pedidos de rede ou utilize um navegador

Nenhuma tabela corresponde

Defina um ID ou contentor estável e inclua o seletor que falhou no erro

Deslocamento de colunas

Inspecione as células diretamente e adicione um tratamento personalizado para estruturas que se estendem ou estão aninhadas

Apenas a primeira página é guardada

Aguarde por uma assinatura de linha alterada, registe o progresso da página e sinalize um erro num controlo bloqueado

O texto da célula está em branco

Leia o link, o campo de entrada ou o atributo de dados relevante, em vez de apenas o texto

Os pedidos devolvem o código de erro 403 ou 429

Interrompa, reveja o acesso, reduza a simultaneidade e utilize o backoff limitado, quando apropriado

As colunas do CSV ficam corrompidas

Utilize um serializador testado e fixtures que contenham aspas, vírgulas, novas linhas e texto UTF-8

Adicione medidas de segurança responsáveis para a produção

Se extrair tabelas HTML em JavaScript para além de um teste pontual, utilize uma lista de verificação concisa para produção:

  • Analise os termos, as regras de acesso e as orientações para robôs antes da recolha. Obtenha a aprovação adequada para casos de utilização sensíveis ou regulamentados.
  • Mantenha a concorrência a um nível conservador, armazene em cache as páginas inalteradas, respeite Retry-After quando aplicável e limite as tentativas de reenvio com um período de espera. Nenhum atraso é universalmente seguro.
  • Recolha apenas os campos necessários, restrinja o acesso, proteja os dados retidos e elimine-os quando o período de retenção terminar.
  • Registe o estado, o seletor, o número da página, o número de linhas e os avisos, excluindo credenciais e valores sensíveis.

As consequências legais podem variar consoante o site, os dados, o método e a jurisdição. Considere isto como orientação geral de engenharia, não como aconselhamento jurídico. Um manual de estratégias para a extração de dados da Web sem ser bloqueado pode incluir verificações operacionais após a definição da autorização e do âmbito.

Pontos-chave

  • A forma mais rápida de extrair uma tabela HTML em JavaScript é inspecionar primeiro a resposta e, só depois, utilizar um navegador quando os scripts ou a interação forem realmente necessários.
  • Derive chaves únicas a partir dos títulos, gere alternativas explícitas e assinale discrepâncias na contagem de células, em vez de codificar rigidamente as colunas de um site.
  • Conserve as cadeias de caracteres em bruto até que as regras de conversão sejam conhecidas; depois, valide as colunas obrigatórias, a saída não vazia, os avisos e os casos extremos em CSV.
  • Para paginação renderizada, aguarde uma alteração mensurável nas linhas, pare ao receber sinais explícitos, limite o ciclo e elimine duplicados com uma chave significativa e estável.

Perguntas frequentes

Por que razão o DevTools consegue mostrar linhas de tabela que o Axios e o Cheerio não conseguem encontrar?

O DevTools mostra o DOM em tempo real depois de o navegador ter executado os scripts e normalizado a marcação, enquanto o Axios recebe os bytes da resposta do servidor. O código do lado do cliente pode ir buscar dados, inserir linhas ou substituir um espaço reservado após o carregamento. Os navegadores também podem inserir elementos estruturais, tais como <tbody>, por isso compare um valor representativo e tenha em conta a normalização do DOM ao testar seletores.

Preciso do Puppeteer quando uma tabela apresenta controlos de paginação?

Não. Os controlos de paginação podem ocultar linhas já presentes no DOM, alternar entre partes de uma matriz na memória ou solicitar outra página a um ponto de extremidade de dados. Utilize apenas um navegador quando a mudança de páginas exigir interação com JavaScript e não estiver disponível nenhum ponto de extremidade adequado. Inspecione o código-fonte, a contagem de linhas do DOM e as respostas de rede antes de adicionar automação do navegador.

Selecione o elemento dentro da célula e leia a propriedade relevante. Com o Cheerio, utilize .attr("href") para links, .attr("data-id") para atributos de dados e .val() para controlos de formulário. Resolva links relativos com new URL(relativeHref, pageUrl).href. Armazene o texto visível e o atributo em campos separados quando ambos contiverem informações úteis.

Posso guardar os registos analisados como JSON ou enviá-los para uma base de dados em vez de CSV?

Sim. Escreva JSON com JSON.stringify(records, null, 2), envie registos para uma API ou insira-os numa base de dados. Valide primeiro o mesmo esquema. Para bases de dados, utilize operações parametrizadas, lotes ou transações, conforme apropriado, e uma chave única estável para que as reexecuções possam atualizar ou inserir registos em vez de criar duplicados acidentais.

Conclusão

Uma forma fiável de extrair dados de uma tabela HTML em JavaScript começa com o diagnóstico, não com a escolha de uma biblioteca. Confirme se as linhas existem na resposta, chegam através de um pedido de rede ou requerem um navegador a renderizá-las. Para marcação estática, o Axios e o Cheerio oferecem uma solução leve, enquanto um analisador orientado por cabeçalhos torna o resultado reutilizável em tabelas convencionais, em vez de um layout de colunas fixo.

Antes da exportação, normalize apenas os espaços em branco de apresentação, preserve valores ambíguos como cadeias de caracteres, verifique os campos obrigatórios, analise avisos de incompatibilidade e teste as aspas no CSV com dados de teste adversos. Para tabelas dinâmicas, aguarde pelos seletores e alterações nas linhas, utilize condições de paragem explícitas, registe o progresso da paginação e elimine duplicados com uma chave que reflita os dados. São estas verificações que distinguem uma demonstração de um scraper que se pode depurar.

Comece com o script estático e adicione complexidade apenas quando a página provar que é necessário. Se os bloqueios na camada de pedidos, os desafios CAPTCHA ou a rotação de proxies se tornarem o gargalo, a API de Scraper da WebScrapingAPI pode devolver HTML bruto enquanto lida com essas questões, para que possa manter o mesmo código de análise e validação do Cheerio.

Sobre o autor

Mihai Maxim, Desenvolvedor Full Stack @ WebScrapingAPI

Mihai Maxim

Desenvolvedor Full Stack

Mihai Maxim é um programador Full Stack na WebScrapingAPI, contribuindo em todas as áreas do produto e ajudando a criar ferramentas e funcionalidades fiáveis para a plataforma.

Web Scraping com AWS Lambda: Guia para Python e Java 2026
Guias

Web Scraping com AWS Lambda: Guia para Python e Java 2026

Resumo: A extração de dados da Web com o AWS Lambda funciona melhor quando cada invocação é curta, delimitada e pode ser repetida de forma independente. Comece com HTTP direto, AWS SAM e S3 e, só depois, adicione SQS, contentores, renderização no navegador, proxies ou uma camada de recuperação gerida, apenas quando a carga de trabalho demonstrar que precisa deles.

Suciu Dan32 min read
Ler artigo
Como utilizar o GoSpider: rastrear, limpar URLs e extrair dados
Guias

Como utilizar o GoSpider: rastrear, limpar URLs e extrair dados

Resumo: O GoSpider é um rastreador de linha de comandos destinado a descobrir URLs, não um extrator completo de dados estruturados. Este guia sobre como utilizar o GoSpider mostra como realizar um rastreio limitado, gerir resultados de forma organizada, fazer a transferência de dados do Colly para CSV e seguir um percurso de diagnóstico para respostas 403 ou páginas que exijam renderização em JavaScript.

Suciu Dan23 min read
Ler artigo
Como fazer o Scrape Redfin: Guia Python para Dados de Propriedade
Guias

Como fazer o Scrape Redfin: Guia Python para Dados de Propriedade

TL;DR: A Redfin expõe pontos de extremidade de API ocultos que retornam JSON estruturado para listagens de propriedades, tornando possível ignorar totalmente a análise HTML frágil. Este guia orienta-o na construção de um scraper Python que extrai dados de aluguer e venda, pesquisa por localização, monitoriza novas listagens através de sitemaps XML e exporta resultados limpos para CSV ou JSON.

Suciu Dan14 min read
Ler artigo

Comece a construir

Pronto para expandir a sua recolha de dados?

Junte-se a mais de 2.000 empresas que utilizam a WebScrapingAPI para extrair dados da Web à escala empresarial, sem quaisquer custos de infraestrutura.