Voltar ao blogue
Guias
Mihai Maxim31 de janeiro de 202311 min de leitura

Como extrair uma tabela HTML em JavaScript

Como extrair uma tabela HTML em JavaScript

Compreender a estrutura de uma tabela HTML

HTML tables are a powerful tool for marking up structured tabular data and displaying it in a way that is easy for users to read and understand. Tables are made up of data organized into rows and columns, and HTML provides several different elements for defining and structuring this data. A table must include at least the following elements: <table>, <tr> (table row), and <td> (table data). For added structure and semantic value, tables may also include the <th> (table header) element, as well as the <thead>, <tbody>, and <tfoot> elements.

Vamos explorar as etiquetas com a ajuda de um pequeno exemplo.

Fragmentos de código lado a lado que comparam uma tabela HTML básica com uma versão que utiliza as secções `thead`, `tbody` e `tfoot`

Repare como a segunda tabela utiliza uma sintaxe mais específica

Duas pequenas tabelas HTML lado a lado, com linhas para as frutas e as cores, para a maçã, a banana e o total

The <thead> tag will apply a bold font to the "Fruit" and "Color" cells in the second table. Other than that, you can see how both syntaxes achieve the same organization of the data.

Ao extrair tabelas da Web, é importante ter em conta que pode encontrar tabelas escritas com diferentes graus de especificidade semântica. Por outras palavras, algumas tabelas podem incluir etiquetas HTML mais detalhadas e descritivas, enquanto outras podem utilizar uma sintaxe mais simples e menos descritiva.

Raspagem de tabelas HTML com Node.js e cheerio

Bem-vindo à parte divertida! Aprendemos sobre a estrutura e o objetivo das tabelas HTML e agora está na altura de pôr esse conhecimento em ação, fazendo algo prático. Nosso alvo para este tutorial é a tabela dos artistas mais vendidos de todos os tempos, encontrada em https://chartmasters.org/best-selling-artists-of-all-time/. Começaremos por configurar o nosso ambiente de trabalho e instalar as bibliotecas necessárias. Em seguida, exploraremos o site de destino e criaremos alguns seletores para extrair os dados encontrados na tabela. Depois disso, escreveremos o código para extrair os dados e, por fim, exportá-los-emos para diferentes formatos, como CSV e JSON.

Criação do ambiente de trabalho

Muito bem, vamos começar com o nosso novo projeto! Antes de começar, certifique-se de que tem o node.js instalado. Você pode baixá-lo em https://nodejs.org/en/.

Agora abra o seu editor de código favorito, abra o diretório do seu projeto e execute (no terminal):

npm init -y

Isso inicializará um novo projeto e criará um arquivo package.json padrão.

{

  "name": "html_table_scraper", // the name of your project folder

  "version": "1.0.0",

  "description": "",

  "main": "index.js",

  "scripts": {

    "test": "echo \"Error: no test specified\" && exit 1"

  },

  "keywords": [],

  "author": "",

  "license": "ISC"

}

Para manter as coisas simples, vamos importar nossos módulos com "require". Mas se você quiser importar módulos usando a instrução "import", adicione isso ao seu arquivo package.json:

 "type": "module",

 // isto permite-lhe utilizar a instrução import 

 // ex.: import * as cheerio from 'cheerio';

A opção "main": "index.js" especifica o nome do ficheiro que é o ponto de entrada do nosso programa. Dito isto, pode agora criar um ficheiro index.js vazio.

Vamos usar a biblioteca cheerio para analisar o HTML do nosso site de destino. Você pode instalá-la com:

npm install cheerio

Agora abra o ficheiro index.js e inclua-o como um módulo:

const cheerio = require('cheerio');

O ambiente de trabalho básico está definido. No próximo capítulo, iremos explorar a estrutura da tabela dos artistas mais vendidos de sempre.

Testar o site de destino usando DevTools

Tabela dos artistas mais vendidos da Chartmasters, com linhas por artista e números de vendas, apresentada ao lado das ferramentas de desenvolvimento do navegador que inspecionam o código HTML da tabela

Ao inspecionar o separador "Elementos" das Ferramentas de desenvolvimento, podemos extrair informações valiosas sobre a estrutura da tabela:

The table is stored in a <thead>, <tbody> format.

A todos os elementos do quadro são atribuídos id's, classes e funções descritivas.

No contexto de um browser, o utilizador tem acesso direto ao HTML de uma página Web. Isto significa que pode utilizar funções JavaScript como getElementsByTagName ou querySelector para extrair dados do HTML.

Com isto em mente, podemos utilizar a Consola de Ferramentas do Programador para testar alguns selectores.

Vamos extrair os nomes dos cabeçalhos utilizando o atributo role="columnheader"

Trecho de código da consola do navegador que extrai os cabeçalhos das colunas da tabela com document.querySelectorAll('[role=columnheader]') e exibe os nomes dos cabeçalhos

Agora vamos extrair os dados da primeira linha utilizando os atributos role="cell" e role="row":

Código da consola do navegador que seleciona a primeira linha da tabela e mapeia as suas células para URLs de texto ou de imagens, gerando uma matriz com os valores da linha

Como se pode ver:

Podemos utilizar "[role=columheader]" para selecionar todos os elementos de cabeçalho.

Podemos utilizar "tbody [role=row]" para selecionar todos os elementos de linha.

Para cada linha, podemos utilizar "[role=cell]" para selecionar as suas células.

Um aspeto a ter em conta é que a célula PIC contém uma imagem e devemos escrever uma regra especial para extrair o seu URL.

Implementação de um raspador de tabelas HTML

Agora é altura de tornar as coisas um pouco mais avançadas, utilizando Node.js e cheerio.

Para obter o HTML de um site em um projeto Node.js, você terá que fazer uma solicitação de busca para o site. Isso retorna o HTML como uma string, o que significa que não é possível usar funções DOM do JavaScript para extrair dados. É aí que entra o cheerio. Cheerio é uma biblioteca que permite analisar e manipular a string HTML como se estivesse no contexto de um navegador. Isto significa que pode utilizar selectores CSS familiares para extrair dados do HTML, tal como faria num browser.

Também é importante notar que o HTML devolvido de um pedido de pesquisa pode ser diferente do HTML que vê no browser. Isso ocorre porque o navegador executa JavaScript, que pode modificar o HTML que é exibido. Num pedido de pesquisa, está apenas a obter o HTML em bruto, sem quaisquer modificações de JavaScript.

Apenas para fins de teste, vamos fazer um pedido de busca ao nosso site de destino e escrever o HTML resultante num ficheiro local:

//index.js

const fs = require('fs');

(async () => {

    const response =  await fetch('https://chartmasters.org/best-selling-artists-of-all-time/');

    const raw_html = await response.text();

    fs.writeFileSync('raw-best-selling-artists-of-all-time.html', raw_html);

})();

// it will write the raw html to raw-best-selling-artists-of-all-time.html

// try it with other websites: https://randomwordgenerator.com/

Pode executá-lo com:

node index.js

E vais receber:

Tabela dos artistas mais vendidos da Chartmasters, com linhas por artista e números de vendas, apresentada ao lado das ferramentas de desenvolvimento do navegador que inspecionam a tabela

A estrutura da tabela permanece a mesma. Isto significa que os selectores que encontrámos no capítulo anterior continuam a ser relevantes.

Ok, agora vamos continuar com o código real:

Depois de fazer o pedido de fetch a https://chartmasters.org/best-selling-artists-of-all-time/, terá de carregar o html em bruto no cheerio:

const cheerio = require('cheerio');

(async () => {

    const response =  await fetch('https://chartmasters.org/best-selling-artists-of-all-time/');

    const raw_html = await response.text();

    const $ = cheerio.load(raw_html);

})();

Com o cheerio carregado, vamos ver como podemos extrair os cabeçalhos:

    const headers = $("[role=columnheader]")

    const header_names = []

    headers.each((index, el) => {

        header_names.push($(el).text())

    })
 //header_names

    [

        '#',

        'PIC',

        'Artist',

        'Total CSPC',

        'Studio Albums Sales',

        'Other LPs Sales',

        'Physical Singles Sales',

        'Digital Singles Sales',

        'Sales Update',

        'Streams EAS (Update)'

    ]

E a primeira fila:

    const first_row = $("tbody [role=row]")[0]

    const first_row_cells = $(first_row).find('[role=cell]')

    const first_row_data = []

    first_row_cells.each((index, f_r_c) => {

        const image = $(f_r_c).find('img').attr('src')

        if(image) {

            first_row_data.push(image)

        }

        else {

            first_row_data.push($(f_r_c).text())

        }

    })
   //first_row_data

     [

        '1',

        'https://i.scdn.co/image/ab6761610000f178e9348cc01ff5d55971b22433',

        'The Beatles',

        '421,300,000',

        '160,650,000',

        '203,392,000',

        '116,080,000',

        '35,230,000',

        '03/01/17',

        '17,150,000 (01/03/23)'

      ]

Lembra-se de quando raspamos a tabela HTML com JavaScript no console do Browser Developer Tools? Neste ponto, replicamos a mesma funcionalidade que implementamos lá, mas no contexto do projeto Node.js. Você pode rever o último capítulo e observar as muitas semelhanças entre as duas implementações.

Vamos reescrever o código para extrair todas as linhas:

    const rows = $("tbody [role=row]")

    const rows_data = []

    rows.each((index, row) => {

        const row_cell_data = []

        const cells = $(row).find('[role=cell]')

        cells.each((index, cell) => {

            const image = $(cell).find('img').attr('src')

            if(image) {

                row_cell_data.push(image)

            }

            else {

                row_cell_data.push($(cell).text())

            }

        })
        rows_data.push(row_cell_data)

    })

    //rows_data

    [

        [

          '1',

          'https://i.scdn.co/image/ab6761610000f178e9348cc01ff5d55971b22433',

          'The Beatles',

          '421 300 000',

          '160 650 000',

          '203 392 000',

          '116 080 000',

          '35 230 000',

          '01/03/17',

          '17 150 000 (01/03/23)'

        ],

        [

          '2',

          'https://i.scdn.co/image/ab6761610000f178a2a0b9e3448c1e702de9dc90',

          'Michael Jackson',

          '336 084 000',

          '182 600 000',

          '101 997 000',

          '79 350 000',

          '79 930 000',

          '27/09/17',

          '15 692 000 (06/01/23)'

        ],

        ...

    ]

Agora que obtivemos os dados, vamos ver como podemos exportá-los.

Exportar os dados

Depois de ter obtido com êxito os dados que pretende extrair, é importante considerar a forma como pretende armazenar as informações. As opções mais populares são .json e .csv. Escolha o formato que melhor se adapta às suas necessidades e requisitos específicos.

Exportar os dados para json

Se pretender exportar os dados para .json, deve primeiro agrupar os dados num objeto JavaScript que se assemelhe ao formato .json.

Temos uma matriz de nomes de cabeçalho (header_names) e outra matriz (rows_data, uma matriz de matrizes) que contém os dados das linhas. O formato .json armazena informações em pares chave-valor. Precisamos de agrupar os nossos dados de forma a que sigam essa regra:

[ // this is what we need to obtain

  {

    '#': '1',

    PIC: 'https://i.scdn.co/image/ab6761610000f178e9348cc01ff5d55971b22433',

    Artist: 'The Beatles',

    'Total CSPC': '421,300,000',

    'Studio Albums Sales': '160,650,000',

    'Other LPs Sales': '203,392,000',

    'Physical Singles Sales': '116,080,000',

    'Digital Singles Sales': '35,230,000',

    'Sales Update': '03/01/17',

    'Streams EAS (Update)': '17,150,000 (01/03/23)'

  },

  {

    '#': '2',

    PIC: 'https://i.scdn.co/image/ab6761610000f178a2a0b9e3448c1e702de9dc90',

    Artist: 'Michael Jackson',

    'Total CSPC': '336,084,000',

    'Studio Albums Sales': '182,600,000',

    'Other LPs Sales': '101,997,000',

    'Physical Singles Sales': '79,350,000',

    'Digital Singles Sales': '79,930,000',

    'Sales Update': '09/27/17',

    'Streams EAS (Update)': '15,692,000 (01/06/23)'

  } 

  ...

]

Eis como o pode conseguir:

     // go through each row

 const table_data = rows_data.map(row => {

        // create a new object

        const obj = {};

        // forEach element in header_names

        header_names.forEach((header_name, i) => {

          // add a key-value pair to the object where the key is the current header name and the value is the value at the same index in the row

          obj[header_name] = row[i];

        });

        // return the object

        return obj;

 });

Agora pode utilizar a função JSON.stringify() para converter o objeto JavaScript numa cadeia de caracteres .json e depois escrevê-la num ficheiro.

 const fs = require('fs');

 const table_data_json_string = JSON.stringify(table_data, null, 2)

 fs.writeFile('table_data.json', table_data_json_string, (err) => {

if (err) throw err;

console.log('The file has been saved as table_data.json!');

 })

Exportar os dados para csv

O formato .csv significa "valores separados por vírgulas". Se quiser guardar a sua tabela em .csv, terá de a escrever num formato semelhante a este:

id,nome,idade // os cabeçalhos da tabela seguidos das linhas

1,Alice,20

2,Bob,25

3,Charlie,30

Os dados da nossa tabela são constituídos por uma matriz de nomes de cabeçalho (header_names) e outra matriz (rows_data, uma matriz de matrizes) que contém os dados das linhas. Eis como pode escrever estes dados num ficheiro .csv:

    let csv_string = header_names.join(',') + '\n'; // add the headers

    // forEach row in rows_data

    rows_data.forEach(row => {

      // add the row to the CSV string

      csv_string += row.join(',') + '\n';

    });

   

    // write the string to a file

    fs.writeFile('table_data.csv', csv_string, (err) => {

      if (err) throw err;

      console.log('The file has been saved as table_data.csv!');

    });

Evitar ser bloqueado

Já alguma vez se deparou com o problema de tentar fazer scraping de um Web site e perceber que a página da qual está a tentar extrair informações não está a carregar totalmente? Isto pode ser frustrante, especialmente se souber que o site está a utilizar JavaScript para gerar o seu conteúdo. Não temos a capacidade de executar JavaScript como um navegador normal faz, o que pode levar a dados incompletos ou até mesmo ser banido do site por fazer muitos pedidos num curto período de tempo.

Uma solução para este problema é o WebScrapingApi. Com o nosso serviço, pode simplesmente fazer pedidos à nossa API e esta tratará de todas as tarefas complexas por si. Executa JavaScript, roda proxies e até trata de CAPTCHAs.

Here is how you can make a simple fetch request to a <target_url> and write the response to a file:

const fs = require('fs');

(async () => {

    const result = await fetch('https://api.webscrapingapi.com/v1?' + new URLSearchParams({

        api_key: '<api_key>',

        url: '<target_url>',

        render_js: 1,

        proxy_type: 'residential',

    }))

   

    const html = await result.text();

    fs.writeFileSync('wsa_test.html', html);

})();

Pode obter uma API_KEY gratuita criando uma nova conta em https://www.webscrapingapi.com/

Ao especificar o parâmetro render_js=1, activará a capacidade do WebScrapingAPI de aceder à página Web visada utilizando um browser sem cabeça que permite que os elementos da página JavaScript sejam processados antes de lhe entregar o resultado final da recolha de dados.

Consulte https://docs.webscrapingapi.com/webscrapingapi/advanced-api-features/proxies para descobrir as capacidades dos nossos proxies rotativos.

Conclusão

Neste artigo, aprendemos sobre o poder da raspagem de tabelas HTML com JavaScript e como ela pode nos ajudar a extrair dados valiosos de sites. Explorámos a estrutura das tabelas HTML e aprendemos a utilizar a biblioteca cheerio em combinação com o Node.js para extrair facilmente dados das mesmas. Também analisámos diferentes formas de exportar os dados, incluindo os formatos CSV e JSON. Ao seguir as etapas descritas neste artigo, você deve ter uma base sólida para extrair tabelas HTML em qualquer site.

Quer seja um profissional experiente ou esteja apenas a começar o seu primeiro projeto de scraping, a WebScrapingAPI's está aqui para o ajudar em cada passo do caminho. A nossa equipa tem todo o gosto em responder a quaisquer questões que possa ter e oferecer orientação nos seus projectos. Por isso, se alguma vez se sentir bloqueado ou apenas precisar de uma ajuda, não hesite em contactar-nos.

Sobre o autor
Mihai Maxim, Desenvolvedor Full Stack na WebScrapingAPI
Mihai MaximDesenvolvedor 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.

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.