Web Scraping em JavaScript - Como usar o Puppeteer para extrair dados de páginas web

Web scrapting em javascript com puppeteer
AdsTerra, Junte-se ao AdsTerra

Usando scripts, podemos extrair os dados que precisamos de um site para vários fins, como criar bancos de dados, fazer análises e até mais.

Aviso: Tenha cuidado ao fazer web scraping. Sempre verifique se está raspando sites que permitem isso e realize essa atividade dentro de limites éticos e legais.

JavaScript e Node.js oferecem várias bibliotecas que tornam o web scraping mais fácil. Para extração simples de dados, você pode usar o Axios para obter respostas de API ou o HTML de um site.

Mas se você está procurando fazer tarefas mais avançadas, incluindo automações, você precisará de bibliotecas como Puppeteer, Cheerio ou Nightmare.

Neste artigo, apresentarei o básico do web scraping em JavaScript e Node.js usando o Puppeteer. Eu estruturei a escrita para mostrar a você alguns conceitos básicos de como obter informações de um site e clicar em um botão (por exemplo, para ir para a próxima página).

Pré-requisitos

Antes de começarmos a raspar nossa primeira página juntos usando JavaScript, Node.js e o DOM HTML, eu recomendaria ter um entendimento básico dessas tecnologias. Isso irá melhorar sua aprendizagem e compreensão do tópico.

Como iniciar seu primeiro raspador com o Puppeteer

Novo projeto...nova pasta! Primeiro, crie a pasta first-puppeteer-scraper-example no seu computador. Ela conterá o código do nosso futuro raspador.

mkdir first-puppeteer-scraper-example

Agora é hora de inicializar seu repositório Node.js com um arquivo package.json. É útil adicionar informações ao repositório e aos pacotes NPM, como a biblioteca Puppeteer.

npm init -y

Depois de digitar esse comando, você deve encontrar o arquivo package.json em sua árvore de repositório.

{
  "name": "first-puppeteer-scraper-example",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "puppeteer": "^19.6.2"
  },
  "type": "module",
  "devDependencies": {},
  "description": ""
}

Antes de prosseguir, é preciso garantir que o projeto esteja configurado para lidar com recursos do ES6. Para isso, você pode adicionar a instrução "types": "module" no final da configuração.

{
  "name": "first-puppeteer-scraper-example",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "puppeteer": "^19.6.2"
  },
  "type": "module",
  "description": "",
  "types": "module"
}

O último passo da nossa inicialização de raspador é instalar a biblioteca Puppeteer. Veja como fazer:

npm install puppeteer

Como raspar os primeiros dados

Neste artigo, usaremos o site ToScrape como nossa plataforma de aprendizado. Este sandbox online fornece dois projetos especificamente projetados para web scraping, tornando-se um ótimo ponto de partida para aprender conceitos básicos, como extração de dados e navegação de páginas.

Para esta introdução para iniciantes, nos concentraremos especificamente no site Quotes to Scrape.

Como inicializar o script

Na raiz do repositório do projeto, você pode criar um arquivo index.js. Este será o ponto de entrada do nosso aplicativo.

Para manter simples, nosso script consistirá de uma função responsável por obter as citações do site (getQuotes).

No corpo da função, precisaremos seguir diferentes etapas:

  • Inicie uma sessão Puppeteer com puppeteer.launch (ele instanciará uma variável do navegador que usaremos para manipular o navegador)
  • Abra uma nova página/guia com browser.newPage (ele instanciará uma variável de página que usaremos para manipular a página)
  • Mude a URL de nossa nova página para http://quotes.toscrape.com/ com page.goto

Aqui está a versão comentada do script inicial:

import puppeteer from "puppeteer";

const getQuotes = async () => {
  // Start a Puppeteer session with:
  // - a visible browser (`headless: false` - easier to debug because you'll see the browser in action)
  // - no default viewport (`defaultViewport: null` - website page will in full width and height)
  const browser = await puppeteer.launch({
    headless: false,
    defaultViewport: null,
  });

  // Open a new page
  const page = await browser.newPage();
  // On this new page:
  // - open the "http://quotes.toscrape.com/" website
  // - wait until the dom content is loaded (HTML is ready)
  await page.goto("http://quotes.toscrape.com/", {
    waitUntil: "domcontentloaded",
  });
};

// Start the scraping
getQuotes();

O que você acha de executar nosso raspador e ver a saída? Vamos fazê-lo com o comando abaixo:

node index.js

Depois de fazer isso, você deve ter um novo aplicativo de navegador iniciado com uma nova página e o site Quotes to Scrape carregado nele.

Quotes to scrape

Nota: Para esta primeira iteração, não estamos fechando o navegador. Isso significa que você precisará fechar o navegador para interromper a execução do aplicativo em execução.

Como buscar a primeira citação

Sempre que você quiser raspar um site, precisará trabalhar com o DOM HTML. O que eu recomendo é inspecionar a página e começar a navegar pelos diferentes elementos para encontrar o que você precisa.

Depois de navegar pelo HTML da página, podemos perceber que uma citação está encapsulada em um elemento <div> com um nome de classe quote (class="quote"). Essa é uma informação importante porque a raspagem funciona com seletores CSS (por exemplo, .quote).

Inspecionando o DOM

Agora que temos esse conhecimento, podemos voltar à nossa função getQuotes e melhorar nosso código para selecionar a primeira citação e extrair seus dados.

Precisaremos adicionar o seguinte após a instrução page.goto:

Extrair dados do HTML da nossa página com page.evaluate (ele executará a função passada como parâmetro no contexto da página e retornará o resultado) Obter o nó HTML da citação com document.querySelector (ele buscará o primeiro <div> com a classname quote e o retornará) Obter o texto da citação e o autor do nó HTML da citação previamente extraído com quote.querySelector (ele extrairá os elementos com a classname text e author sob <div class="quote"> e os retornará) Aqui está a versão atualizada com comentários detalhados:

import puppeteer from "puppeteer";

const getQuotes = async () => {
  // Start a Puppeteer session with:
  // - a visible browser (`headless: false` - easier to debug because you'll see the browser in action)
  // - no default viewport (`defaultViewport: null` - website page will in full width and height)
  const browser = await puppeteer.launch({
    headless: false,
    defaultViewport: null,
  });

  // Open a new page
  const page = await browser.newPage();
  // On this new page:
  // - open the "http://quotes.toscrape.com/" website
  // - wait until the dom content is loaded (HTML is ready)
  await page.goto("http://quotes.toscrape.com/", {
    waitUntil: "domcontentloaded",
  });

  // Get page data
  const quotes = await page.evaluate(() => {
    // Fetch the first element with class "quote"
    const quote = document.querySelector(".quote");

    // Fetch the sub-elements from the previously fetched quote element
    // Get the displayed text and return it (`.innerText`)
    const text = quote.querySelector(".text").innerText;
    const author = quote.querySelector(".author").innerText;
    return { text, author };
  });

  // Display the quotes
  console.log(quotes);

  // Close the browser
  await browser.close();
};

// Start the scraping
getQuotes();

Algo interessante a destacar é que o nome da função para selecionar um elemento é o mesmo usado na inspeção do navegador. Aqui está um exemplo:

DOM selector no console

Vamos rodar nosso script mais uma vez e ver o que teremos de saída:

{
  text: '“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”',
  author: 'Albert Einstein'
}

Conseguimos! Nossa primeira raspagem foi exibida no terminal.

Como Obter Todas as Citações da Página Atual

Agora que sabemos como obter uma citação, vamos mudar um pouco nosso código para obter todas as citações e extrair seus dados uma a uma.

Anteriormente, usamos document.querySelector para selecionar o primeiro elemento correspondente (a primeira citação). Para obter todas as citações, precisaremos usar a função document.querySelectorAll.

Precisaremos seguir estes passos para fazê-lo funcionar:

  • Substitua document.getQuerySelector por document.querySelectorAll (ele buscará todos os elementos <div> com a classe de nome quote e os retornará)
  • Converta os elementos obtidos em uma lista com Array.from(quoteList) (isso garantirá que a lista de citações seja iterável)
  • Mova nosso código anterior para obter o texto e o autor da citação dentro do loop e retorne o resultado (isso extrairá os elementos com a classe de nome text e author sob <div class="quote"> para cada citação)

Aqui está a atualização do código:

import puppeteer from "puppeteer";

const getQuotes = async () => {
  // Start a Puppeteer session with:
  // - a visible browser (`headless: false` - easier to debug because you'll see the browser in action)
  // - no default viewport (`defaultViewport: null` - website page will be in full width and height)
  const browser = await puppeteer.launch({
    headless: false,
    defaultViewport: null,
  });

  // Open a new page
  const page = await browser.newPage();

  // On this new page:
  // - open the "http://quotes.toscrape.com/" website
  // - wait until the dom content is loaded (HTML is ready)
  await page.goto("http://quotes.toscrape.com/", {
    waitUntil: "domcontentloaded",
  });

  // Get page data
  const quotes = await page.evaluate(() => {
    // Fetch the first element with class "quote"
    // Get the displayed text and returns it
    const quoteList = document.querySelectorAll(".quote");

    // Convert the quoteList to an iterable array
    // For each quote fetch the text and author
    return Array.from(quoteList).map((quote) => {
      // Fetch the sub-elements from the previously fetched quote element
      // Get the displayed text and return it (`.innerText`)
      const text = quote.querySelector(".text").innerText;
      const author = quote.querySelector(".author").innerText;

      return { text, author };
    });
  });

  // Display the quotes
  console.log(quotes);

  // Close the browser
  await browser.close();
};

// Start the scraping
getQuotes();

Como resultado final, se executarmos nosso script mais uma vez, deveremos ter uma lista de citações como saída. Cada elemento desta lista deve ter uma propriedade de texto e uma de autor. Abaixo resultado da saída:

[
  {
    text: '“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”',
    author: 'Albert Einstein'
  },
  {
    text: '“It is our choices, Harry, that show what we truly are, far more than our abilities.”',
    author: 'J.K. Rowling'
  },
  {
    text: '“There are only two ways to live your life. One is as though nothing is a miracle. The other is as though everything is a miracle.”',
    author: 'Albert Einstein'
  },
  {
    text: '“The person, be it gentleman or lady, who has not pleasure in a good novel, must be intolerably stupid.”',
    author: 'Jane Austen'
  },
  {
    text: "“Imperfection is beauty, madness is genius and it's better to be absolutely ridiculous than absolutely boring.”",
    author: 'Marilyn Monroe'
  },
  {
    text: '“Try not to become a man of success. Rather become a man of value.”',
    author: 'Albert Einstein'
  },
  {
    text: '“It is better to be hated for what you are than to be loved for what you are not.”',
    author: 'André Gide'
  },
  {
    text: "“I have not failed. I've just found 10,000 ways that won't work.”",
    author: 'Thomas A. Edison'
  },
  {
    text: "“A woman is like a tea bag; you never know how strong it is until it's in hot water.”",
    author: 'Eleanor Roosevelt'
  },
  {
    text: '“A day without sunshine is like, you know, night.”',
    author: 'Steve Martin'
  }
]

Ótimo, agora todas citações foram raspadas por nosso script!

Como Passar para a Próxima Página

Nosso script agora é capaz de buscar todas as citações de uma página. Seria interessante clicar em "Próxima página" na parte inferior da página e fazer o mesmo na segunda página.

Botão próxima página

De volta à inspeção do navegador, vamos encontrar como podemos direcionar esse elemento usando seletores CSS.

Como podemos perceber, o botão próximo está localizado sob uma lista não ordenada <ul> com uma classe de nome pager (<ul class="pager">). Esta lista tem um elemento <li> com uma classe de nome next (<li class="next">). Finalmente, há um link âncora <a> que aponta para a segunda página (<a href="/page/2/">).

No CSS, se quisermos direcionar esse link específico, existem diferentes maneiras de fazer isso. Podemos fazer:

  • .next > a: porém, é arriscado porque se houver outro elemento com .next como elemento pai contendo um link, ele será clicado.
  • .pager > .next > a: mais seguro, porque garantimos que o link deve estar dentro do elemento pai .pager, sob o elemento .next. Existe um baixo risco de ter essa hierarquia mais de uma vez.

Inspeção DOM elemento pager

Para clicar nesse botão, no final do nosso script após console.log(quotes), você pode adicionar o seguinte: await page.click(".pager > .next > a");.

Uma vez que agora estamos fechando a página do navegador com await browser.close(); após todas as instruções serem concluídas, você precisa comentar esta instrução para ver a segunda página aberta no navegador do raspador.

Isso é temporário e apenas para fins de teste, mas o final de nossa função getQuotes deve se parecer com isso:

  // Display the quotes
  console.log(quotes);

  // Click on the "Next page" button
  await page.click(".pager > .next > a");

  // Close the browser
  // await browser.close();

Depois disso, se você executar nosso script novamente, após processar todas as instruções, seu navegador deve parar na segunda página:

Print da segunda página

O que você pode fazer a seguir:

Agora é sua vez de melhorar o raspador e fazer com que ele obtenha mais dados do site Quotes to Scrape. Aqui está uma lista de melhorias potenciais que você pode fazer:

  • Navegar entre todas as páginas usando o botão "Next" e coletar as citações de todas as páginas.
  • Coletar as tags das citações (cada citação possui uma lista de tags).
  • Raspar a página "sobre" do autor (clicando no nome do autor em cada citação).
  • Categorizar as citações por tags ou autores (não está relacionado diretamente com o scraping em si, mas pode ser uma boa melhoria).
AdsTerra, Junte-se ao AdsTerra