Raspagem Web com Cheerio: Extraia Dados do Hacker News!

A técnica de web scraping possibilita a extração de dados de páginas web. Sites utilizam HTML para estruturar seu conteúdo, e quando este HTML é bem construído e semântico, a localização de informações relevantes torna-se facilitada.

Geralmente, o web scraping é empregado para coletar, acompanhar informações e monitorar alterações em dados específicos.

Conceitos de jQuery Essenciais Antes de Usar Cheerio

jQuery é uma biblioteca JavaScript amplamente utilizada, que simplifica a interação com o Document Object Model (DOM), o tratamento de eventos e a criação de animações. Cheerio, por sua vez, é uma biblioteca para web scraping que se inspira no jQuery, compartilhando sua sintaxe e API, e facilita a análise de documentos HTML ou XML.

Antes de usar o Cheerio, é fundamental compreender como selecionar elementos HTML com jQuery. jQuery oferece suporte à maioria dos seletores CSS3, o que simplifica a identificação de elementos no DOM. Veja o exemplo:

 $("#container");

Neste trecho de código, jQuery seleciona elementos que possuem o ID “container”. Uma implementação equivalente com JavaScript puro seria:

 document.querySelectorAll("#container");

A comparação dos dois exemplos evidencia que o código jQuery é mais legível e conciso, ilustrando a vantagem de seu uso.

jQuery também inclui métodos úteis como `text()`, `html()`, que permitem manipular elementos HTML. Há também diversos métodos para navegar pelo DOM, como `parent()`, `siblings()`, `prev()` e `next()`.

O método `each()` do jQuery é muito usado em projetos que utilizam Cheerio, permitindo iterar sobre objetos e arrays. A sintaxe do `each()` é:

 $(<element>).each(<array or object>, callback)

Nesse código, a função de callback é executada em cada iteração do array ou objeto fornecido.

Carregando HTML com Cheerio

Para começar a análise de dados HTML ou XML com Cheerio, use o método `cheerio.load()`. Observe este exemplo:

 const $ = cheerio.load('<html><body><h1>Olá, mundo!</h1></body></html>');
console.log($('h1').text())

Este código utiliza o método `text()` do jQuery para obter o texto do elemento h1. A sintaxe completa do método `load()` é:

 load(content, options, mode)

O parâmetro `content` representa os dados HTML ou XML. `options` é um objeto opcional que pode modificar o comportamento do método. Por padrão, o `load()` insere os elementos `html`, `head` e `body` caso estejam ausentes. Para evitar este comportamento, o modo deve ser definido como `false`.

Extraindo Notícias do Hacker News com Cheerio

O código deste projeto está disponível em um repositório GitHub, sob licença MIT.

Vamos combinar os conhecimentos adquiridos para criar um web scraper simples. O Hacker News é um site popular entre empreendedores e inovadores, e ideal para a prática de web scraping, pois carrega rapidamente, possui uma interface simples e não exibe publicidade.

Certifique-se de ter o Node.js e o Node Package Manager instalados. Crie uma pasta vazia, um arquivo `package.json` e insira o seguinte JSON:

 {
  "name": "web-scraper",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "nodemon index.js"
  },
  "author": "",
  "license": "MIT",
  "dependencies": {
    "cheerio": "^1.0.0-rc.12",
    "express": "^4.18.2"
  },
  "devDependencies": {
    "nodemon": "^3.0.1"
  }
}

Em seguida, abra o terminal e execute:

 npm i

Isso instalará as dependências necessárias para construir o scraper, incluindo Cheerio para análise de HTML, ExpressJS para criar o servidor e Nodemon como dependência de desenvolvimento para detectar alterações e reiniciar o servidor.

Configurando e Criando as Funções Necessárias

Crie um arquivo `index.js` e declare uma constante `PORT`, definindo-a para 5500 (ou outro número) e importe os pacotes Cheerio e Express:

 const PORT = 5500;
const cheerio = require("cheerio");
const express = require("express");
const app = express();

Em seguida, declare três variáveis: `url`, `html` e `finishedPage`. Defina `url` como o endereço do Hacker News:

 const url="https://news.ycombinator.com";
let html;
let finishedPage;

Agora, crie uma função `getHeader()` que retorna um HTML a ser renderizado no navegador:

 function getHeader(){
    return `
        <div style="display:flex; flex-direction:column; align-items:center;">
        <h1 style="text-transform:capitalize">Scraper News</h1>
        <div style="display:flex; gap:10px; align-items:center;">
        <a href="https://www.makeuseof.com/" id="news" onClick='showLoading()'>Home</a>
        <a href="https://wilku.top/best" id="best" onClick='showLoading()'>Best</a>
        <a href="https://wilku.top/newest" id="newest" onClick='showLoading()'>Newest</a>
        <a href="https://wilku.top/ask" id="ask" onClick='showLoading()'>Ask</a>
        <a href="https://wilku.top/jobs" id="jobs" onClick='showLoading()'>Jobs</a>
        </div>
        <p class="loading" style="display:none;">Loading...</p>
        </div>
`}

Crie outra função `getScript()` que retorna JavaScript para o navegador executar, passando o tipo da variável como argumento ao chamá-la:

 function getScript(type){
    return `
    <script>
    document.title = "${type.substring(1)}"

    window.addEventListener("DOMContentLoaded", (e) => {
      let navLinks = [...document.querySelectorAll("a")];
      let current = document.querySelector("#${type.substring(1)}");
      document.body.style = "margin:0 auto; max-width:600px;";
      navLinks.forEach(x => x.style = "color:black; text-decoration:none;");
      current.style.textDecoration = "underline";
      current.style.color = "black";
      current.style.padding = "3px";
      current.style.pointerEvents = "none";
    })

    function showLoading(e){
      document.querySelector(".loading").style.display = "block";
      document.querySelector(".loading").style.textAlign = "center";
    }
    </script>`
}

Por fim, crie uma função assíncrona `fetchAndRenderPage()`. Esta função busca uma página do Hacker News, a analisa com Cheerio e envia o HTML para o cliente:

 async function fetchAndRenderPage(type, res) {
    const response = await fetch(`${url}${type}`)
    html = await response.text();
}

O Hacker News oferece diferentes tipos de posts, como “notícias” da página inicial, posts para perguntas (“ask”), posts de tendências (“best”), posts mais recentes (“newest”) e posts de vagas de emprego (“jobs”).

`fetchAndRenderPage()` busca a lista de posts de acordo com o tipo fornecido. Se a busca for bem-sucedida, a função associa a variável `html` ao texto de resposta.

Em seguida, adicione as seguintes linhas à função:

 res.set('Content-Type', 'text/html');
res.write(getHeader());

const $ = cheerio.load(html);
const articles = [];
let i = 1;

Aqui, o método `set()` define o cabeçalho HTTP e o método `write()` envia uma parte do corpo da resposta. A função `load()` recebe o `html` como argumento.

Em seguida, adicione estas linhas para selecionar os filhos de todos os elementos com a classe “titleline”:

 $('.titleline').children('a').each(function(){
    let title = $(this).text();
    articles.push(`<h4>${i}. ${title}</h4>`);
    i++;
})

Neste trecho, a cada iteração, o conteúdo de texto do elemento HTML é recuperado e armazenado na variável `title`.

Em seguida, inclua a resposta da função `getScript()` na matriz `articles`. Crie a variável `finishPage` que conterá o HTML final a ser enviado ao navegador e utilize o método `write()` para enviar `finishPage` e finalizar o processo de resposta com `end()`:

 articles.push(getScript(type))
finishedPage = articles.reduce((c, n) => c + n);
res.write(finishedPage);
res.end();

Definindo as Rotas para Solicitações GET

Abaixo da função `fetchAndRenderPage`, utilize o método `express get()` para definir as rotas para os diferentes tipos de posts. Em seguida, utilize o método `listen` para monitorar conexões na porta especificada em sua rede local:

 app.get("https://www.makeuseof.com/", (req, res) => {
    fetchAndRenderPage('/news', res);
})

app.get("https://wilku.top/best", (req, res) => {
    fetchAndRenderPage("https://wilku.top/best", res);
})

app.get("https://wilku.top/newest", (req, res) => {
    fetchAndRenderPage("https://wilku.top/newest", res);
})

app.get("https://wilku.top/ask", (req, res) => {
    fetchAndRenderPage("https://wilku.top/ask", res);
})

app.get("https://wilku.top/jobs", (req, res) => {
    fetchAndRenderPage("https://wilku.top/jobs", res);
})

app.listen(PORT)

Cada método `get` tem uma função de retorno que chama `fetchAndRenderPage`, passando o tipo de postagem e o objeto `res`.

Ao executar `npm run start` no terminal, o servidor iniciará e você poderá acessar `localhost:5500` no navegador para ver os resultados.

Parabéns, você acabou de extrair os títulos do Hacker News sem a necessidade de uma API externa.

Avançando com Web Scraping

Com os dados extraídos do Hacker News, é possível criar diversas visualizações, como tabelas, gráficos e nuvens de palavras, para apresentar insights e tendências de forma mais acessível.

Também é possível coletar informações de perfis de usuários para analisar sua reputação na plataforma, com base em fatores como votos recebidos, comentários e outros dados relevantes.