Voltar ao blogue
Guias
Raluca PenciucLast updated on Mar 31, 20268 min read

Scraping com o Cheerio: Como recolher dados facilmente de páginas web

Scraping com o Cheerio: Como recolher dados facilmente de páginas web

Já lá vão os dias em que era necessário recolher e processar manualmente os dados necessários para dar início aos seus projetos. Quer se trate de um site de comércio eletrónico ou de um algoritmo de geração de leads, uma coisa é certa: o processo de recolha de dados era tedioso e demorado.

Neste artigo, irá aprender como o Cheerio pode ajudá-lo com as suas funcionalidades abrangentes de análise de linguagens de marcação, começando com alguns exemplos simples e, em seguida, com um caso de utilização na vida real.

Introdução ao Cheerio

«Mas o que é o Cheerio?», poderá perguntar-se. Bem, para tentar esclarecer um equívoco comum, começarei por explicar o que o Cheerio não é: um navegador.

A confusão pode ter origem no facto de o Cheerio analisar documentos escritos numa linguagem de marcação e, em seguida, oferecer uma API para o ajudar a manipular a estrutura de dados resultante. Mas, ao contrário de um navegador, o Cheerio não renderiza visualmente o documento, não carrega ficheiros CSS nem executa Javascript.

Basicamente, o que o Cheerio faz é receber uma entrada HTML ou XML, analisar a string e devolver a API. Isto torna-o incrivelmente rápido e fácil de usar, daí a sua popularidade entre os programadores Node.js.

Configurar o ambiente

Agora, vamos ver alguns exemplos práticos do que o Cheerio pode fazer. Antes de mais nada, precisa de se certificar de que o seu ambiente está totalmente configurado.

Escusado será dizer que tem de ter o Node.js instalado no seu computador. Se não tiver, basta seguir as instruções do site oficial, de acordo com o seu sistema operativo.

Certifique-se de que descarrega a versão de Suporte a Longo Prazo (LTS) e não se esqueça do Gestor de Pacotes Node.js (NPM). Pode executar estes comandos para se certificar de que a instalação correu bem:

node -v
npm -v

O resultado deve ser semelhante a este:

Agora, quanto ao debate sobre o IDE: para este tutorial, irei utilizar o Visual Studio Code, uma vez que é bastante flexível e fácil de utilizar, mas pode utilizar qualquer IDE que preferir.

Basta criar uma pasta para o seu pequeno projeto e abrir um terminal. Execute o seguinte comando para configurar um projeto Node.js:

npm init -y

Isto irá criar uma versão padrão do ficheiro package.json, que pode ser modificada a qualquer momento.

Próximo passo: vou instalar o TypeScript juntamente com as definições de tipos para o Node.js:

npm install typescript @types/node -save-dev

Escolhi o TypeScript neste tutorial devido à sua tipagem estática opcional para objetos JavaScript, o que torna o código mais à prova de erros no que diz respeito a erros de tipagem. 

Esta é a mesma vantagem que aumentou constantemente a sua popularidade entre a comunidade JavaScript, de acordo com um inquérito recente da CircleCI sobre as linguagens de programação mais populares.

Para verificar se o comando anterior foi instalado corretamente, pode executar:

npx tsc --version

Agora vou criar o ficheiro de configuração tsconfig.json na raiz do diretório do projeto, que deve definir as opções do compilador. Se quiser compreender melhor este ficheiro e as suas propriedades, a documentação oficial do TypeScript está à sua disposição. 

Caso contrário, basta copiar e colar o seguinte:

{
    "compilerOptions": {
        "module": "commonjs",
        "esModuleInterop": true,
        "target": "es6",
        "moduleResolution": "node",
        "sourceMap": true,
        "outDir": "dist"
    },
    "lib": ["es2015"]
}

Está quase pronto! Agora tem de instalar o Cheerio (obviamente):

npm install cheerio

Por último, mas não menos importante, crie o diretório src, que irá conter os ficheiros de código. E por falar em ficheiro de código, crie e coloque o ficheiro index.ts no diretório src.

Como funciona o Cheerio

Perfeito! Agora já podes começar.

Por enquanto, vou ilustrar algumas funcionalidades básicas do Cheerio utilizando um documento HTML estático. Basta copiar e colar o conteúdo abaixo num novo ficheiro static.html dentro do seu projeto:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Page Name - Static HTML Example</title>
</head>
<body>
    <div class="page-heading">
        <h1>Page Heading</h1>
    </div>
    <div class="page-container">
        <div class="page-content">
            <ul>
                <li>
                    <a href="#">Item 1</a>
                      <p class="price">$100</p>
                      <p class="stock">12</p>
                </li>
                <li>
                    <a href="#">Item 2</a>
                    <p class="price">$200</p>
                    <p class="stock">422</p>
                </li>
                <li>
                    <a href="#">Item 3</a>
                    <p class="price">$150</p>
                    <p class="stock">5</p>
                </li>
            </ul>
        </div>
    </div>
    <footer class="page-footer">
        <p>Last Updated: Friday, September 23, 2022</p>
    </footer>
</body>
</html>

Em seguida, tem de enviar o ficheiro HTML como entrada para o Cheerio, que irá então devolver a API resultante:

import fs from 'fs'
import * as cheerio from 'cheerio'

const staticHTML = fs.readFileSync('static.html')
const $ = cheerio.load(staticHTML)

Se receber um erro nesta etapa, certifique-se de que o ficheiro de entrada contém um documento HTML válido, pois a partir da versão 1.0.0 do Cheerio este critério também é verificado.

Agora pode começar a experimentar o que o Cheerio tem para oferecer. O pacote NPM é conhecido pela sua sintaxe semelhante à do jQuery e pela utilização de seletores CSS para extrair os nós que procura. Pode consultar a documentação oficial para ter uma ideia melhor.

Digamos que queira extrair o título da página:

const title = $('title').text()
console.log("Static HTML page title:", title)

Devemos testar isto, certo? Está a usar Typescript, por isso tem de compilar o código, o que irá criar o diretório dist, e depois executar o ficheiro index.js associado. Para simplificar, vou definir o seguinte script no ficheiro package.json:

"scripts": {
    "test": "npx tsc && node dist/index.js",
}

Desta forma, basta executar:

npm run test

e o script tratará das duas etapas que acabei de descrever.

Ok, mas e se o seletor corresponder a mais do que um elemento HTML? Vamos tentar extrair o nome e o valor de mercado dos itens apresentados na lista não ordenada:

const itemStocks = {}
$('li').each((index, element) => {
    const name = $(element).find('a').text()
    const stock = $(element).find('p.stock').text()
    itemStocks[name] = stock
})
console.log("All items stock:", itemStocks)

Agora execute o script de atalho novamente, e a saída do seu terminal deverá ficar assim:

Static HTML page title: Page Name - Static HTML Example
All items stock: { 'Item 1': '12', 'Item 2': '422', 'Item 3': '5' }

Casos de utilização do Cheerio

Então, isso foi basicamente a ponta do iceberg. O Cheerio também é capaz de analisar documentos XML, extrair o estilo dos elementos HTML e até mesmo alterar os atributos dos nós.

Mas como é que o Cheerio pode ajudar num caso de utilização real?

Digamos que queremos recolher alguns dados para treinar um modelo de Machine Learning para um projeto futuro de maior dimensão. Normalmente, pesquisaria no Google por alguns ficheiros de treino e descarregá-los-ia, ou utilizaria a API do site.

Mas o que faz quando não consegue encontrar ficheiros relevantes ou o site que está a consultar não disponibiliza uma API, tem uma limitação de taxa sobre os dados ou não oferece todos os dados que está a ver numa página?

Bem, é aqui que o web scraping se torna útil. Se estiver curioso sobre casos de uso mais práticos do web scraping, pode consultar este artigo bem escrito do nosso blog.

Voltando ao nosso assunto, para efeitos do exemplo, vamos considerar que estamos precisamente nesta situação: queremos dados, mas não os encontramos em lado nenhum. Lembre-se de que o Cheerio não lida com a extração de HTML, nem com o carregamento de CSS ou a execução de JS.

Assim, no nosso tutorial, estou a usar o Puppeteer para navegar até ao site, recolher o HTML e guardá-lo num ficheiro. Depois, vou repetir o processo da secção anterior.

Para ser mais específico, quero recolher algumas opiniões públicas do Reddit sobre um módulo de bateria popular e centralizar os dados num único ficheiro que será posteriormente alimentado a um potencial modelo de treino de ML. O que acontece a seguir também pode variar: análise de sentimentos, pesquisa de mercado e a lista pode continuar.

Solicitação do HTML

Vamos ver como este caso de uso ficará traduzido em código. Primeiro, é necessário instalar o pacote NPM do Puppeteer:

npm install puppeteer

Também vou criar um novo ficheiro reddit.ts, para manter o projeto mais organizado, e definir um novo script no ficheiro package.json:

"scripts": {
    "test": "npx tsc && node dist/index.js",
    "parse": "npx tsc && node dist/reddit.js"
},

Para obter o documento HTML, vou definir uma função com o seguinte aspeto:

import fs from 'fs'
import puppeteer from 'puppeteer'
import * as cheerio from 'cheerio'

async function getContent(url: string): Promise<void> {

    // Open the browser and a new tab
    const browser = await puppeteer.launch()
    const page = await browser.newPage()

    // Navigate to the URL and write the content to file
    await page.goto(url)
    const pageContent = await page.content()
    fs.writeFileSync("reddit.html", pageContent)

    // Close the browser
    await browser.close()
    console.log("Got the HTML. Check the reddit.html file.")
}

Para testar isto rapidamente, adicione um ponto de entrada no seu código e chame a função:

async function main() {


    const targetURL = 'https://old.reddit.com/r/Drumming/comments/r3tidc/yamaha_ead10/'
    await getContent(targetURL)
}

main()
    .then(() => {console.log("All done!")})
    .catch(e => {console.log("Unexpected error occurred:", e.message)})

O ficheiro reddit.html deverá aparecer na árvore do seu projeto, contendo o documento HTML que pretendemos.

Onde estão os meus nós?

Agora, uma parte um pouco mais desafiante: tem de identificar os nós que são de interesse para o nosso caso de uso. Volte ao seu navegador (o verdadeiro) e navegue até ao URL de destino. Passe o cursor do rato sobre a secção de comentários, clique com o botão direito e, em seguida, escolha a opção “Inspecionar”.

A guia Ferramentas do Desenvolvedor será aberta, mostrando exatamente o mesmo documento HTML que você salvou anteriormente no seu computador.

Para extrair apenas os comentários, tem de identificar os seletores exclusivos desta secção da página. Pode verificar que toda a lista de comentários se encontra dentro de um contêiner div com a classe sitetable nestedlisting.

Aprofundando um pouco mais, cada comentário individual tem um elemento form como pai, com a classe usertext warn-on-unload. Depois, na parte inferior, pode ver que o texto de cada comentário está dividido entre vários elementos p.

Análise e gravação dos dados

Vamos ver como isto funciona no código:

function parseComments(): void {

    // Load the HTML document
    const staticHTML = fs.readFileSync('reddit.html')
    const $ = cheerio.load(staticHTML)

    // Get the comments section
    const commentsSection = $('div.sitetable.nestedlisting')

    // Iterate each comment
    const comments = []


    $(commentsSection).find('form.usertext.warn-on-unload').each((index, comment) => {
        let commentText = ""

          // Iterate each comment section and concatenate them
          $(comment).find('p').each((index, piece) => {
            commentText += $(piece).text() + '\n'
          })

          comments.push(commentText)
    })

    // Write the results to external file
    fs.writeFileSync("comments.json", JSON.stringify({comments}))
}

Muito bem, e agora vamos atualizar o ponto de entrada com a função recém-definida e ver como este código funciona em conjunto:

async function main() {

    const targetURL = 'https://old.reddit.com/r/Drumming/comments/r3tidc/yamaha_ead10/'
    await getContent(targetURL)
    parseComments()
}

main()
    .then(() => {console.log("All done. Check the comments.csv file.")})
    .catch(e => {console.log("Unexpected error occurred:", e.message)})

Execute o código com o script definido anteriormente:

npm run parse

Demorará cerca de 5 a 10 segundos para o navegador headless abrir e navegar até ao nosso URL de destino. Se estiver mais curioso sobre isto, pode adicionar carimbos de data/hora no início e no fim de cada uma das nossas funções para ver realmente quão rápido é o Cheerio.

O ficheiro comments.json deve ser o nosso resultado final:

Este caso de uso pode ser facilmente estendido para analisar o número de votos positivos e negativos para cada comentário, ou para obter as respostas aninhadas dos comentários. As possibilidades são infinitas.

Conclusão

Obrigado por ter chegado ao fim deste tutorial. Espero que tenha percebido como o Cheerio é indispensável para o processo de extração de dados e como integrá-lo rapidamente no seu próximo projeto de scraping.

Também estamos a utilizar o Cheerio no nosso produto, o WebScrapingAPI. Se alguma vez se vir em apuros devido aos muitos desafios encontrados no web scraping (bloqueios de IP, deteção de bots, etc.), considere experimentá-lo.

Sobre o autor
Raluca Penciuc, Desenvolvedor Full-Stack @ WebScrapingAPI
Raluca PenciucDesenvolvedor Full-Stack

Raluca Penciuc é programadora Full Stack na WebScrapingAPI, onde desenvolve scrapers, aperfeiçoa estratégias de evasão e procura formas fiáveis de reduzir a deteção nos sites-alvo.

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.