One place for hosting & domains

      Como usar a Fetch API do JavaScript para buscar dados


      Introdução

      Houve um tempo em que o XMLHttpRequest era usado para fazer solicitações de API. Ele não incluía promessas e não gerava um código JavaScript organizado. Ao usar o jQuery, usava-se a sintaxe mais organizada com o jQuery.ajax().

      Agora, o JavaScript tem sua própria maneira integrada de fazer solicitações de API. Isso é feito pela Fetch API, um novo padrão para fazer solicitações de servidor com promessas, que inclui também muitas outras funcionalidades.

      Neste tutorial, você criará solicitações GET e POST usando a Fetch API.

      Pré-requisitos

      Para concluir este tutorial, você precisará do seguinte:

      Para usar a Fetch API, chame o método fetch, que aceita a URL da API como um parâmetro:

      fetch(url)
      

      Após o método fetch(), inclua o método de promessa then():

      .then(function() {
      
      })
      

      O método fetch() retorna uma promessa. Se a promessa retornada for resolve, a função dentro do método then() é executada. Essa função contém o código para lidar com os dados recebidos da API.

      Abaixo do método then(), inclua o método catch():

      .catch(function() {
      
      });
      

      A API chamada usando fetch() pode estar inoperante ou outros erros podem ocorrer. Se isso acontecer, a promessa reject será retornada. O método catch é usado para lidar com reject. O código dentro de catch() será executado se um erro ocorrer ao chamar a API escolhida.

      Resumindo, usar a Fetch API será parecido com isto:

      fetch(url)
      .then(function() {
      
      })
      .catch(function() {
      
      });
      

      Com uma compreensão da sintaxe para usar a Fetch API, agora siga em frente para usar fetch() em uma API real.

      Passo 2 — Usando Fetch para buscar dados de uma API

      As amostras de código a seguir baseiam-se na Random User API. Usando a API, você irá buscar dez usuários e os exibirá na página usando o JavaScript puro.

      A ideia é obter todos os dados da Random User API e exibí-los em itens de lista dentro da lista de autores. Comece criando um arquivo HTML e adicionando um cabeçalho e uma lista não ordenada com o id de authors:

      <h1>Authors</h1>
      <ul id="authors"></ul>
      

      Agora, adicione identificadores script no final do seu arquivo HTML e use um seletor DOM para pegar o ul. Utilize getElementById com authors como o argumento. Lembre-se, authors é o id para o ul criado anteriormente:

      <script>
      
          const ul = document.getElementById('authors');
      
      </script>
      

      Crie uma variável constante chamada url que armazenará o URL da API que irá retornar dez usuários aleatórios:

      const url="https://randomuser.me/api/?results=10";
      

      Com ul e url instalados, é hora de criar as funções que serão usadas para criar os itens de lista. Crie uma função chamada createNode que recebe um parâmetro chamado element:

      function createNode(element) {
      
      }
      

      Mais tarde, quando o createNode for chamado, será necessário passar o nome de um elemento HTML real a ser criado.

      Dentro da função, adicione uma instrução return que retorna o element usando document.createElement():

      function createNode(element) {
          return document.createElement(element);
      }
      

      Também será necessário criar uma função chamada append que recebe dois parâmetros: parent e el:

      function append(parent, el) {
      
      }
      

      Essa função irá acrescentar el ao parent usando o document.createElement:

      function append(parent, el) {
          return parent.appendChild(el);
      }
      

      Tanto o createNode quanto o append estão prontos para o uso. Agora, com a Fetch API, chame a Random User API usando fetch() com url como o argumento:

      fetch(url)
      
      fetch(url)
        .then(function(data) {
      
          })
        })
        .catch(function(error) {
      
        });
      

      No código acima, você está chamando a Fetch API e passando o URL para a Random User API. Então, uma resposta é recebida. No entanto, a resposta recebida não é JSON, mas um objeto com uma série de métodos que podem ser usados dependendo do que você quer fazer com as informações. Para converter o objeto retornado em JSON, use o método json().

      Adicione o método then(), que irá conter uma função com um parâmetro chamado resp:

      fetch(url)
      .then((resp) => )
      

      O parâmetro resp recebe o valor do objeto retornado de fetch(url). Use o método json() para converter resp em dados JSON:

      fetch(url)
      .then((resp) => resp.json())
      

      Os dados JSON ainda precisam ser processados. Adicione outra instrução then() com uma função que tem um argumento chamado data:

      .then(function(data) {
      
          })
      })
      

      Dentro dessa função, crie uma variável chamada authors que seja definida igual à data.results:

      .then(function(data) {
          let authors = data.results;
      

      Para cada autor em authors, será criado um item de lista que exibe uma figura o nome deles. O método map() é ótimo para isso:

      let authors = data.results;
      return authors.map(function(author) {
      
      })
      

      Dentro de sua função map, crie uma variável chamada li que será definida igual a createNode com li (o elemento HTML) como o argumento:

      return authors.map(function(author) {
          let li = createNode('li');
      })
      

      Repita isso para criar um elemento span e um elemento img:

      let li = createNode('li');
      let img = createNode('img');
      let span = createNode('span');
      

      A API oferece um nome para o author e uma imagem que acompanha o nome. Defina img.src para a imagem do autor:

      let img = createNode('img');
      let span = createNode('span');
      
      img.src = author.picture.medium;
      

      O elemento span deve conter o primeiro e último nome de author. A propriedade innerHTML e a interpolação de strings permitirão fazer isso:

      img.src = author.picture.medium;
      span.innerHTML = `${author.name.first} ${author.name.last}`;
      

      Com a imagem e o elemento de lista criados juntamente com o elemento span, use a função append criada anteriormente para exibir esses elementos na página:

      append(li, img);
      append(li, span);
      append(ul, li);
      

      Com ambas as funções then() concluídas, adicione agora a função catch(). Essa função irá registrar o erro em potencial no console:

      .catch(function(error) {
        console.log(error);
      });
      

      Este é o código completo da solicitação que você criou:

      function createNode(element) {
          return document.createElement(element);
      }
      
      function append(parent, el) {
        return parent.appendChild(el);
      }
      
      const ul = document.getElementById('authors');
      const url="https://randomuser.me/api/?results=10";
      
      fetch(url)
      .then((resp) => resp.json())
      .then(function(data) {
        let authors = data.results;
        return authors.map(function(author) {
          let li = createNode('li');
          let img = createNode('img');
          let span = createNode('span');
          img.src = author.picture.medium;
          span.innerHTML = `${author.name.first} ${author.name.last}`;
          append(li, img);
          append(li, span);
          append(ul, li);
        })
      })
      .catch(function(error) {
        console.log(error);
      });
      

      Você acabou de realizar uma solicitação GET com sucesso usando a Random User API e a Fetch API. No próximo passo, irá aprender como realizar solicitações POST.

      Passo 3 — Lidando com solicitações POST

      A Fetch usa por padrão solicitações GET, mas é possível usar qualquer outro tipo de solicitação, alterar os cabeçalhos e enviar os dados. Para fazer isso, é necessário definir seu objeto e passá-lo como o segundo argumento da função fetch.

      Antes de criar uma solicitação POST, crie os dados que gostaria de enviar à API. Este será um objeto chamado data com a chave name e o valor Sammy (ou seu nome):

      const url="https://randomuser.me/api";
      
      let data = {
        name: 'Sammy'
      }
      

      Certifique-se de incluir uma variável constante que contém o link da Random User API.

      Como esta é uma solicitação POST, será necessário declarar isso explicitamente. Crie um objeto chamado fetchData:

      let fetchData = {
      
      }
      

      Esse objeto precisa incluir três chaves: method, body e headers. A chave method deve conter o valor 'POST'. body deve ser definido igual ao objeto data que acabou de ser criado. headers deve conter o valor de new Headers():

      let fetchData = {
        method: 'POST',
        body: data,
        headers: new Headers()
      }
      

      A interface Headers, que é uma propriedade da Fetch API, permite realizar várias ações em cabeçalhos de solicitação HTTP e de resposta. Se quiser aprender mais sobre isso, este artigo chamado Como definir rotas e métodos de solicitação HTTP no Express pode oferecer-lhe mais informações.

      Com esse código no lugar, a solicitação POST pode ser feita usando a Fetch API. Você incluirá url e fetchData como argumentos para sua solicitação POST fetch:

      fetch(url, fetchData)
      

      A função then() irá incluir o código que lida com a resposta recebida do servidor da Random User API:

      fetch(url, fetchData)
      .then(function() {
          // Handle response you get from the server
      });
      

      Para criar um objeto e usar a função fetch(), há também outra opção. Ao invés de criar um objeto como o fetchData, é possível usar o construtor de solicitações para criar seu objeto de solicitação. Para fazer isso, crie uma variável chamada request:

      const url="https://randomuser.me/api";
      
      let data = {
        name: 'Sara'
      }
      
      var request =
      

      A variável request deve ser definida igual a new Request. O constructo new Request recebe dois argumentos: a url da API (url) e um objeto. O objeto também deve incluir as chaves method, body e headers assim como o fetchData:

      var request = new Request(url, {
          method: 'POST',
          body: data,
          headers: new Headers()
      });
      

      Agora, request pode ser usado como o argumento único para o fetch(), uma vez que ele também inclui a url da API:

      fetch(request)
      .then(function() {
          // Handle response we get from the API
      })
      

      No conjunto, seu código ficará semelhante a este:

      const url="https://randomuser.me/api";
      
      let data = {
        name: 'Sara'
      }
      
      var request = new Request(url, {
          method: 'POST',
          body: data,
          headers: new Headers()
      });
      
      fetch(request)
      .then(function() {
          // Handle response we get from the API
      })
      

      Agora, você conhece dois métodos para criar e executar solicitações POST com a Fetch API.

      Conclusão

      Embora a Fetch API ainda não seja suportada por todos os navegadores, é uma ótima alternativa ao XMLHttpRequest. Se quiser aprender como chamar APIs da Web usando o React, confira este artigo sobre este tópico.



      Source link

      Como converter tipos de dados em Python 3


      Introdução

      Na linguagem Python, os tipos de dados são usados para classificar um tipo específico de dados, determinando os valores que você pode atribuir ao tipo e as operações que você pode realizar nele. Ao programar, há momentos em que será necessário converter valores entre os no tipos, no intuito de manipular tais valores de maneira diferente. Por exemplo, talvez seja necessário concatenar valores numéricos com strings, ou representar casas decimais em números que foram inicializados como valores de número inteiro.

      Este tutorial irá ensinar a converter tipos de dados, incluindo números, strings, tuplas e listas, bem como fornecer exemplos para ajudar você a familiarizar-se com diferentes casos de uso.

      Convertendo os tipos de número

      Em Python, existem dois tipos de dados numéricos: inteiros e números de ponto flutuante (float). Às vezes, ao trabalhar no código de outra pessoa, será necessário converter um inteiro para um float ou vice-versa. Em outros casos, você pode estar usando um inteiro quando o que realmente precisa é de um float. O Python possui métodos integrados para permitir a conversão de inteiros em floats e floats em inteiros.

      Convertendo os números inteiros em floats

      O método float() do Python irá converter inteiros em floats. Para usar essa função, adicione um inteiro dentro dos parênteses:

      float(57)
      

      Neste caso, 57 será convertido em 57.0.

      Também é possível usar essa técnica com uma variável. Vamos declarar f como igual a 57, e então imprimir o novo float:

      f = 57
      print(float(f))
      

      Output

      57.0

      Usando a função float(), podemos converter inteiros em floats.

      Convertendo floats em inteiros

      O Python também possui uma função integrada para converter floats em inteiros: int().

      A função int() funciona de maneira semelhante à função float(): é possível adicionar um número de ponto flutuante dentro dos parênteses para convertê-lo em um inteiro:

      int(390.8)
      

      Neste caso, 390.8 será convertido em 390.

      Você também pode usar essa técnica com as variáveis. Vamos declarar b como igual a 125.0, e c como igual a 390.8, e então imprimir os novos floats:

      b = 125.0
      c = 390.8
      
      print(int(b))
      print(int(c))
      

      Output

      125 390

      Ao converter os floats em inteiros com a função int(), o Python corta a casa decimal e os números do float que restam são usados para criar um inteiro. Embora talvez você queira arredondar 390,8 para 391, o Python não fará isso através da função int().

      Números convertidos através de divisão

      Em Python 3, quocientes relevantes são convertidos de inteiros para floats ao fazer uma divisão, embora não sejam em Python 2. Ou seja, quando você dividir 5 por 2, em Python 3, você receberá um float como resposta (2.5):

      a = 5 / 2
      print(a)
      

      Output

      2.5

      Em Python 2, como você estava lidando com dois inteiros, você receberia um inteiro de volta como sua resposta: 5 / 2 = 2. Leia “Python 2 vs Python 3: considerações práticas“ para mais informações sobre as diferenças entre o Python 2 e Python 3.

      Uma string é uma sequência de um ou mais caracteres (letras, números ou símbolos). As strings são uma forma comum de dados em programas de computador. Muitas vezes, pode ser necessário converter strings em números e números em strings, especialmente quando estivermos incluindo dados gerados pelo usuário.

      Convertendo números em strings

      Podemos converter números em strings usando o método str(). Vamos passar um número ou uma variável dentro dos parênteses do método e então esse valor numérico será convertido em um valor de string.

      Primeiro, vamos ver como converter inteiros. Para converter o inteiro 12 em um valor de string, passe o número 12 para o método str():

      str(12)
      

      Ao executar str(12) no shell interativo do Python com o comando python em uma janela de terminal, você receberá o seguinte resultado:

      Output

      '12'

      As aspas ao redor do número 12 significam que o número já não é mais um inteiro, mas sim um valor de string.

      Com variáveis, podemos começar a ver como é prático converter inteiros em strings. Vamos supor que queiramos monitorar o progresso diário de um usuário na programação e estamos inserindo quantas linhas de código eles estão escrevendo por vez. Queremos mostrar esse feedback para o usuário. Para tanto, os valores de string e inteiro serão impressos ao mesmo tempo:

      user = "Sammy"
      lines = 50
      
      print("Congratulations, " + user + "! You just wrote " + lines + " lines of code.")
      

      Ao executar este código, iremos receber o seguinte erro:

      Output

      TypeError: Can't convert 'int' object to str implicitly

      Não é possível concatenar strings e inteiros em Python, de modo que teremos que converter a variável lines para se tornar um valor de string:

      user = "Sammy"
      lines = 50
      
      print("Congratulations, " + user + "! You just wrote " + str(lines) + " lines of code.")
      

      Agora, ao executar o código, iremos receber o resultado a seguir, parabenizando o usuário pelo progresso alcançado:

      Output

      Congratulations, Sammy! You just wrote 50 lines of code.

      Se quisermos converter um float em uma string, ao invés de um inteiro em uma string, seguimos os mesmos passos e formatos. Ao passar um float no método str(), um valor de string do float será retornado. Podemos usar o valor do float em si ou uma variável:

      print(str(421.034))
      
      f = 5524.53
      print(str(f))
      

      Output

      421.034 5524.53

      Vamos fazer um teste para garantir que tudo está correto, fazendo a concatenação com uma string:

      f = 5524.53
      print("Sammy has " + str(f) + " points.")
      

      Output

      Sammy has 5524.53 points.

      Agora, temos certeza de que o float foi convertido corretamente em uma string, pois a concatenação foi realizada sem nenhum erro.

      Convertendo strings em números

      As strings podem ser convertidas em números usando os métodos int() e float().

      Se sua string não tiver casas decimais, você provavelmente vai querer convertê-la em um número inteiro, usando o método int().

      Vamos usar o exemplo do usuário Sammy, monitorando as linhas de código escritas a cada dia. Pode ser que queiramos manipular esses valores com operações matemáticas, no intuito de dar um feedback mais interessante para o usuário. Porém, no momento, tais valores estão armazenados em strings:

      lines_yesterday = "50"
      lines_today = "108"
      
      lines_more = lines_today - lines_yesterday
      
      print(lines_more)
      

      Output

      TypeError: unsupported operand type(s) for -: 'str' and 'str'

      Como os dois valores numéricos foram armazenados em strings, um erro foi emitido. O operando - para a subtração não é um operando válido para dois valores de string.

      Modifique o código para incluir o método int() que converterá as strings em inteiros, permitindo, assim, fazer operações matemáticas com os valores que eram originalmente strings.

      lines_yesterday = "50"
      lines_today = "108"
      
      lines_more = int(lines_today) - int(lines_yesterday)
      
      print(lines_more)
      

      Output

      58

      A variável lines_more é automaticamente um inteiro, sendo igual ao valor numérico de 58 neste exemplo.

      Também é possível converter os números no exemplo acima em valores float usando o método float() no lugar do método int(). Em vez de receber o resultado de 58, iremos receber o resultado de 58.0, um float.

      O usuário Sammy está ganhando pontos em valores decimais.

      total_points = "5524.53"
      new_points = "45.30"
      
      new_total_points = total_points + new_points
      
      print(new_total_points)
      

      Output

      5524.5345.30

      Neste caso, usar o operando + com duas strings é uma operação válida, mas irá concatenar duas strings ao invés de fazer a adição de dois valores numéricos. Então, nosso resultado parece não convencional, pois apenas coloca os dois valores um ao lado do outro.

      Precisamos converter essas strings em floats antes de executar qualquer operação matemática com o método float():

      total_points = "5524.53"
      new_points = "45.30"
      
      new_total_points = float(total_points) + float(new_points)
      
      print(new_total_points)
      

      Output

      5569.83

      Agora que convertemos as duas strings em floats, recebemos o resultado esperado que adiciona 45.30 a 5524.53.

      Se tentarmos converter um valor de string com casas decimais em um inteiro, iremos receber um erro:

      f = "54.23"
      print(int(f))
      

      Output

      ValueError: invalid literal for int() with base 10: '54.23'

      Se passarmos um valor decimal em uma string para o método int(), iremos receber um erro, pois ele não será convertido em um inteiro.

      Converter strings em números nos permite modificar rapidamente o tipo de dados com o qual estamos trabalhando para que possamos realizar operações com valores numéricos que eram originalmente strings.

      Convertendo em tuplas e listas

      Os métodos list() e tuple() podem ser usados para converter os valores passados a eles nos tipos de dados lista e tupla respectivamente. Em Python:

      • uma lista é uma sequência ordenada mutável de elementos que fica contida dentro de colchetes [ ].
      • uma tupla é uma sequência ordenada imutável de elementos contida dentro de parênteses ( ).

      Convertendo em tuplas

      Vamos começar com a conversão de uma lista em uma tupla. Converter uma lista em uma tupla, que é um tipo de dados imutável, pode permitir uma otimização substancial aos programas que criamos. Quando usamos o método tuple(), ela irá retornar a versão em tupla do valor passado a ele.

      print(tuple(['pull request', 'open source', 'repository', 'branch']))
      

      Output

      ('pull request', 'open source', 'repository', 'branch')

      Vemos que uma tupla foi impressa no resultado, pois os itens estão agora contidos dentro de parênteses ao invés de colchetes.

      Vamos usar tuple() com uma variável que representa uma lista:

      sea_creatures = ['shark', 'cuttlefish', 'squid', 'mantis shrimp']
      print(tuple(sea_creatures))
      

      Output

      ('shark', 'cuttlefish', 'squid', 'mantis shrimp')

      Novamente, vemos que o valor de lista foi alterado para um valor de tupla, indicado pelos parênteses. Podemos converter qualquer tipo iterável em uma tupla, incluindo strings:

      print(tuple('Sammy'))
      

      Output

      ('S', 'a', 'm', 'm', 'y')

      Considerando que é possível iterar em strings, podemos convertê-las em tuplas com o método tuple(). Entretanto, com tipos de dados que não são iteráveis, como inteiros e floats, iremos receber um erro do tipo:

      print(tuple(5000))
      

      Output

      TypeError: 'int' object is not iterable

      Embora seja possível converter um inteiro em uma string, para então converter em uma tupla, como em tuple(str(5000)), é melhor escolher um código legível ao invés de conversões complicadas.

      Convertendo em listas

      Converter valores, especialmente tuplas, em listas, pode ser útil quando for necessário ter uma versão mutável desse valor.

      Vamos usar o método list() para converter a tupla a seguir em uma lista. Como a sintaxe para criar uma lista usa parênteses, certifique-se de incluir os parênteses do método list() e, neste caso, também o método print():

      print(list(('blue coral', 'staghorn coral', 'pillar coral')))
      

      Output

      ['blue coral', 'staghorn coral', 'pillar coral']

      Os colchetes indicam que uma lista foi retornada a partir do valor de tupla original passado ao método list().

      Para tornar o código mais legível, podemos remover um dos pares de parênteses usando uma variável:

      coral = ('blue coral', 'staghorn coral', 'pillar coral')
      list(coral)
      

      Caso imprimíssemos list(coral), iríamos receber o mesmo resultado que acima.

      Assim como as tuplas, as strings podem ser convertidas em listas:

      print(list('shark'))
      

      Output

      ['s', 'h', 'a', 'r', 'k']

      Aqui, a string 'shark' foi convertida em uma lista, fornecendo uma versão mutável do valor original.

      Conclusão

      Este tutorial de Python demonstrou como converter vários dos tipos de dados nativos importantes em outros tipos de dados, principalmente através de métodos integrados. Ser capaz de converter tipos de dados em Python fornece uma flexibilidade extra ao escrever seus programas.



      Source link

      Como coletar dados automaticamente de um site usando o Node.js e o Puppeteer


      O autor selecionou o Free and Open Source Fund para receber uma doação como parte do programa Write for DOnations.

      Introdução

      O scraping (coleta de dados) é o processo de automatizar a coleta de dados da Web. O processo envolve geralmente a implementação de um “rastreador” que navega automaticamente pela Web e coleta dados de páginas selecionadas. Existem muitas razões pelas quais pode ser interessante coletar dados automaticamente. A principal delas é que esse processo torna a coleta de dados muito mais rápida, eliminando a necessidade de um processo manual. O scraping também funciona como uma solução nos casos em que a coleta de dados é desejada ou necessária, mas o site não fornece uma API.

      Neste tutorial, você irá construir um aplicativo Web de scraping usando o Node.js e o Puppeteer. Seu app irá ganhar complexidade à medida que você progredir. Primeiro, você irá programar seu app para abrir o Chromium e carregar um site especial projetado como uma área restrita de scraping na internet: books.toscrape.com. Nos dois passos seguintes, você irá coletar todos os livros em uma única página do books.toscrape e então todos os livros em várias páginas. Nos passos restantes, irá filtrar seu scraping por categoria de livro e então salvar seus dados como um arquivo JSON.

      Atenção: a ética e a legalidade do scraping na internet são muito complexas e estão em constante evolução. Elas também diferem com base em sua localização, na localização dos dados e no site em questão. Esse tutorial faz a coleta de um site especial, o books.toscrape.com, que foi projetado especificamente para testar aplicativos de coleta de dados. Aplicar o scraping em qualquer outro domínio não está no âmbito deste tutorial.

      Pré-requisitos

      Passo 1 — Configurando o coletor de dados Web

      Com o Node.js instalado, já é possível começar a configurar seu coletor de dados Web. Primeiro, você irá criar um diretório raiz do projeto e então instalar as dependências necessárias. Esse tutorial exige apenas uma dependência, e você irá instalá-la usando o gerenciador de pacotes padrão do Node.js, o npm. O npm já vem instalado previamente junto com o Node.js, de forma que não é necessário instalá-lo.

      Crie uma pasta para este projeto e então entre nela:

      • mkdir book-scraper
      • cd book-scraper

      Você irá executar todos os comandos subsequentes a partir deste diretório.

      Precisamos instalar um pacote usando o npm (node package manager). Primeiro, inicialize o npm para criar um arquivo packages.json, que irá gerenciar as dependências e os metadados do seu projeto.

      Inicialize o npm para o seu projeto:

      O npm irá apresentar uma sequência de prompts. Você pode pressionar ENTER para todos os prompts, ou adicionar descrições personalizadas. Certifique-se de pressionar ENTER e deixar os valores padrão intactos quando questinado sobre o entry point: e test command:. De maneira alternativa, você pode passar a flag y para o npmnpm init -y— ela fará com que todos os valores padrão sejam submetidos para você.

      Seu resultado se parecerá com este:

      Output

      { "name": "sammy_scraper", "version": "1.0.0", "description": "a web scraper", "main": "index.js", "scripts": { "test": "echo "Error: no test specified" && exit 1" }, "keywords": [], "author": "sammy the shark", "license": "ISC" } Is this OK? (yes) yes

      Digite yes e pressione ENTER. O npm irá salvar esse resultado como seu arquivo package.json.

      Agora, use o npm para instalar o Puppeteer:

      • npm install --save puppeteer

      Esse comando instala tanto o Puppeteer quanto uma versão do Chromium que a equipe do Puppeteer sabe que irá funcionar com sua API.

      Em máquinas Linux, o Puppeteer pode exigir algumas dependências adicionais.

      Caso esteja usando o Ubuntu 18.04, verifique o menu suspenso ‘Debian Dependencies’ dentro da seção ‘Chrome headless doesn’t launch on UNIX’ dos documentos de solução de problemas do Puppeteer. Você pode usar o comando a seguir como auxílio para encontrar quaisquer dependências que estejam faltando:

      Com o npm, o Puppeteer e as dependências adicionais instaladas, seu arquivo package.json exige uma última configuração antes que você possa começar a codificar. Neste tutorial, você irá iniciar seu app a partir da linha de comando com npm run start. É necessário adicionar algumas informações sobre esse script start no package.json. Mais especificamente, é preciso adicionar uma linha abaixo da diretiva scripts sobre seu comando start.

      Abra o arquivo no seu editor de texto de preferência:

      Encontre a seção scripts: e adicione as seguintes configurações. Lembre-se de colocar uma vírgula no final da linha test do script, ou seu arquivo não funcionará corretamente.

      Output

      { . . . "scripts": { "test": "echo "Error: no test specified" && exit 1", "start": "node index.js" }, . . . "dependencies": { "puppeteer": "^5.2.1" } }

      Você também verá que o puppeteer agora aparece sob dependências, próximos do final do arquivo. Seu arquivo package.json não exigirá mais revisões. Salve suas alterações e saia do seu editor.

      Agora, você está pronto para começar a codificar seu coletor de dados. No próximo passo, você irá configurar uma instância de navegador e testar funcionalidades básicas do seu coletor de dados.

      Passo 2 — Configurando a instância do navegador

      Ao abrir um navegador tradicional, você pode fazer coisas como clicar em botões, navegar com seu mouse, digitar, abrir as ferramentas de desenvolvedor e muito mais. Um navegador sem periféricos como o Chromium lhe permite fazer essas mesmas coisas, mas programaticamente e sem uma interface de usuário. Neste passo, você irá configurar a instância de navegador do seu coletor de dados. Ao iniciar seu aplicativo, ele irá automaticamente abrir o Chromium e navegar para books.toscrape.com. Essas ações iniciais irão formar a base do seu programa.

      Seu coletor de dados exigirá quatro arquivos .js: browser.js, index.js, pageController.js e pageScraper.js. Neste passo, você irá criar todos os quatro arquivos e então atualizá-los continuamente enquanto seu programa cresce em complexidade. Comece com o browser.js. Esse arquivo irá conter o script que inicia seu navegador.

      A partir do diretório raiz do seu projeto, crie e abra o browser.js em um editor de texto:

      Primeiro, você irá require (solicitar) o Puppeteer e então criar uma função async chamada startBrowser(). Essa função irá iniciar o navegador e retornar uma instância dele. Adicione o seguinte código:

      ./book-scraper/browser.js

      const puppeteer = require('puppeteer');
      
      async function startBrowser(){
          let browser;
          try {
              console.log("Opening the browser......");
              browser = await puppeteer.launch({
                  headless: false,
                  args: ["--disable-setuid-sandbox"],
                  'ignoreHTTPSErrors': true
              });
          } catch (err) {
              console.log("Could not create a browser instance => : ", err);
          }
          return browser;
      }
      
      module.exports = {
          startBrowser
      };
      

      O Puppeteer tem um método .launch() que inicia uma instância de um navegador. Esse método retorna uma Promessa, então é necessário garantir que a Promesa resolva usando um bloco .then ou await.

      Você está usando o await para garantir que a Promessa resolva, envolvendo essa instância em torno de um bloco de código try-catch e então retornando uma instância do navegador.

      Observe que o método .launch() recebe um parâmetro JSON com diversos valores:

      • headlessfalse significa que o navegador será executado com uma Interface para que você possa assistir ao seu script sendo executado, enquanto true significa que o navegador será executado em modo sem periféricos. No entanto, observe que se você quiser implantar seu coletor de dados na nuvem, deve redefinir headless para true. A maioria das máquinas virtuais são sem periféricos e não incluem uma interface de usuário. Dessa forma, o navegador só pode ser executado no modo sem periféricos. O Puppeteer também inclui um modo headful (com periféricos), mas que deve ser usado exclusivamente para fins de teste.
      • ignoreHTTPSErrorstrue permite que você visite sites que não estão hospedados em um protocolo HTTPS seguro e ignore quaisquer erros relacionados ao HTTPS.

      Salve e feche o arquivo.

      Agora, crie seu segundo arquivo .js, o index.js:

      Aqui você irá usar o require para o browser.js e o pageController.js. Em seguida, irá chamar a função startBrowser() e passar a instância do navegador criada para nosso controlador de páginas, que irá direcionar suas ações. Adicione as linhas a seguir:

      ./book-scraper/index.js

      const browserObject = require('./browser');
      const scraperController = require('./pageController');
      
      //Start the browser and create a browser instance
      let browserInstance = browserObject.startBrowser();
      
      // Pass the browser instance to the scraper controller
      scraperController(browserInstance)
      

      Salve e feche o arquivo.

      Crie seu terceiro arquivo .js, o pageController.js:

      O pageController.js controla seu processo de coleta de dados. Ele usa a instância do navegador para controlar o arquivo pageScraper.js, onde todos os scripts de coleta de dados são executados. Por fim, você irá usá-lo para especificar qual categoria de livro deseja coletar. Por enquanto, você só deseja, no entanto, garantir que seja capaz de abrir o Chromium e navegar até uma página da Web:

      ./book-scraper/pageController.js

      const pageScraper = require('./pageScraper');
      async function scrapeAll(browserInstance){
          let browser;
          try{
              browser = await browserInstance;
              await pageScraper.scraper(browser); 
      
          }
          catch(err){
              console.log("Could not resolve the browser instance => ", err);
          }
      }
      
      module.exports = (browserInstance) => scrapeAll(browserInstance)
      

      Esse código exporta uma função que toma a instância do navegador e a passa para uma função chamada scrapeAll(). Essa função, por sua vez, passa essa instância para o pageScraper.scraper() como um argumento que é usado para fazer a coleta de páginas.

      Salve e feche o arquivo.

      Por fim, crie seu último arquivo .js, o pageScraper.js:

      Aqui você irá criar um objeto literal com uma propriedade url e um método scraper(). O url é o URL da página Web na qual deseja fazer a coleta, enquanto que o método scraper() contém o código que irá realizar a coleta de dados em si, embora neste estágio ele meramente navegue até uma URL. Adicione as linhas a seguir:

      ./book-scraper/pageScraper.js

      const scraperObject = {
          url: 'http://books.toscrape.com',
          async scraper(browser){
              let page = await browser.newPage();
              console.log(`Navigating to ${this.url}...`);
              await page.goto(this.url);
      
          }
      }
      
      module.exports = scraperObject;
      

      O Puppeteer possui um método newPage() que cria uma nova instância de página no navegador, e essas instâncias de página podem fazer algumas coisas. Em nosso método scraper(), você criou uma instância de página e então usou o método page.goto() para navegar até a página inicial do books.toscrape.com.

      Salve e feche o arquivo.

      A estrutura de arquivos do seu programa agora está completa. O primeiro nível da árvore de diretórios do seu projeto se parecerá com isto:

      Output

      . ├── browser.js ├── index.js ├── node_modules ├── package-lock.json ├── package.json ├── pageController.js └── pageScraper.js

      Agora, execute o comando npm run start e acompanhe seu aplicativo coletor de dados enquanto ele é executado:

      Ele irá abrir automaticamente uma instância do navegador Chromium, abrir uma nova página no navegador e navegar até books.toscrape.com.

      Neste passo, você criou um aplicativo Puppeteer que abriu o Chromium e carregou a página inicial de uma livraria online fictícia, books.toscrape.com. No próximo passo, você irá coletar os dados de todos os livros nessa página inicial.

      Passo 3 — Coletando os dados de uma única página

      Antes de adicionar mais funcionalidades ao seu aplicativo coletor de dados, abra seu navegador Web de preferência e navegue manualmente até a página inicial de books to scrape. Navegue pelo site e observe como os dados são estruturados.

      Imagem do site books to scrape

      Você verá uma seção de categoria à esquerda e os livros exibidos à direita. Ao clicar em um livro, o navegador irá até uma nova URL que exibirá informações relevantes sobre esse livro em particular.

      Neste passo, esse comportamento será replicado, mas em código. Você fará a automação do processo de navegar pelo site e consumir seus dados.

      Primeiro, se você inspecionar o código fonte para a página inicial usando as ferramentas de desenvolvedor dentro do seu navegador, verá que a página lista os dados de cada livro sob uma etiqueta section. Dentro da etiqueta section, todos os livros estão sob uma etiqueta list (li), e é aqui que você encontra o link para a página dedicada do livro, seu preço e a disponibilidade em estoque.

      O código fonte de books.toscrape visto com ferramentas de desenvolvedor

      Você irá coletar essas URLs de livros, filtrando por livros que estão em estoque. Isso será feito navegando até a página de cada livro e coletando os dados deste livro.

      Reabra seu arquivo pageScraper.js:

      Adicione o conteúdo destacado a seguir: Você irá aninhar outro bloco await dentro de await page.goto(this.url):

      ./book-scraper/pageScraper.js

      
      const scraperObject = {
          url: 'http://books.toscrape.com',
          async scraper(browser){
              let page = await browser.newPage();
              console.log(`Navigating to ${this.url}...`);
              // Navigate to the selected page
              await page.goto(this.url);
              // Wait for the required DOM to be rendered
              await page.waitForSelector('.page_inner');
              // Get the link to all the required books
              let urls = await page.$$eval('section ol > li', links => {
                  // Make sure the book to be scraped is in stock
                  links = links.filter(link => link.querySelector('.instock.availability > i').textContent !== "In stock")
                  // Extract the links from the data
                  links = links.map(el => el.querySelector('h3 > a').href)
                  return links;
              });
              console.log(urls);
          }
      }
      
      module.exports = scraperObject;
      
      

      Nesse bloco de código, você chamou o método page.waitForSelector(). Isso fez com que houvesse a espera pelo div que contém todas as informações relacionadas ao livro ser renderizado no DOM, e então você chamou o método page.$$eval(). Esse método recebe o elemento URL com o seletor section ol li (certifique-se de que seja retornado sempre somente uma string ou um número dos métodos page.$eval() e page.$$eval()).

      Cada livro possui dois status; ou um livro está In Stock (em estoque) ou Out of stock (fora de estoque). Você só deseja coletar os livros que estão In Stock. Como o page.$$eval() retorna uma matriz de elementos correspondentes, você filtrou essa matriz para garantir que estivesse trabalhando apenas com livros em estoque. Você fez isso procurando e avaliando a classe .instock.availability. Em seguida, mapeou a propriedade href dos links dos livros e a retornou do método.

      Salve e feche o arquivo.

      Execute seu aplicativo novamente:

      O navegador será aberto. Navegue até a página Web e então feche-a assim que a tarefa for concluída. Agora, verifique seu console; ele irá conter todas as URLs coletadas:

      Output

      > book-scraper@1.0.0 start /Users/sammy/book-scraper > node index.js Opening the browser...... Navigating to http://books.toscrape.com... [ 'http://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html', 'http://books.toscrape.com/catalogue/tipping-the-velvet_999/index.html', 'http://books.toscrape.com/catalogue/soumission_998/index.html', 'http://books.toscrape.com/catalogue/sharp-objects_997/index.html', 'http://books.toscrape.com/catalogue/sapiens-a-brief-history-of-humankind_996/index.html', 'http://books.toscrape.com/catalogue/the-requiem-red_995/index.html', 'http://books.toscrape.com/catalogue/the-dirty-little-secrets-of-getting-your-dream-job_994/index.html', 'http://books.toscrape.com/catalogue/the-coming-woman-a-novel-based-on-the-life-of-the-infamous-feminist-victoria-woodhull_993/index.html', 'http://books.toscrape.com/catalogue/the-boys-in-the-boat-nine-americans-and-their-epic-quest-for-gold-at-the-1936-berlin-olympics_992/index.html', 'http://books.toscrape.com/catalogue/the-black-maria_991/index.html', 'http://books.toscrape.com/catalogue/starving-hearts-triangular-trade-trilogy-1_990/index.html', 'http://books.toscrape.com/catalogue/shakespeares-sonnets_989/index.html', 'http://books.toscrape.com/catalogue/set-me-free_988/index.html', 'http://books.toscrape.com/catalogue/scott-pilgrims-precious-little-life-scott-pilgrim-1_987/index.html', 'http://books.toscrape.com/catalogue/rip-it-up-and-start-again_986/index.html', 'http://books.toscrape.com/catalogue/our-band-could-be-your-life-scenes-from-the-american-indie-underground-1981-1991_985/index.html', 'http://books.toscrape.com/catalogue/olio_984/index.html', 'http://books.toscrape.com/catalogue/mesaerion-the-best-science-fiction-stories-1800-1849_983/index.html', 'http://books.toscrape.com/catalogue/libertarianism-for-beginners_982/index.html', 'http://books.toscrape.com/catalogue/its-only-the-himalayas_981/index.html' ]

      Esse é um ótimo começo, mas é desejável coletar todos os dados relevantes para um livro em particular, e não apenas sua URL. Agora, você irá usar essas URLs para abrir cada página e coletar o título do livro, autor, preço, disponibilidade, código de barras, descrição e a URL da imagem.

      Abra o pageScraper.js novamente:

      Adicione o código a seguir, que irá percorrer em loop todos os links coletados, abrir uma nova instância de página e então recuperar os dados relevantes:

      ./book-scraper/pageScraper.js

      const scraperObject = {
          url: 'http://books.toscrape.com',
          async scraper(browser){
              let page = await browser.newPage();
              console.log(`Navigating to ${this.url}...`);
              // Navigate to the selected page
              await page.goto(this.url);
              // Wait for the required DOM to be rendered
              await page.waitForSelector('.page_inner');
              // Get the link to all the required books
              let urls = await page.$$eval('section ol > li', links => {
                  // Make sure the book to be scraped is in stock
                  links = links.filter(link => link.querySelector('.instock.availability > i').textContent !== "In stock")
                  // Extract the links from the data
                  links = links.map(el => el.querySelector('h3 > a').href)
                  return links;
              });
      
      
              // Loop through each of those links, open a new page instance and get the relevant data from them
              let pagePromise = (link) => new Promise(async(resolve, reject) => {
                  let dataObj = {};
                  let newPage = await browser.newPage();
                  await newPage.goto(link);
                  dataObj['bookTitle'] = await newPage.$eval('.product_main > h1', text => text.textContent);
                  dataObj['bookPrice'] = await newPage.$eval('.price_color', text => text.textContent);
                  dataObj['noAvailable'] = await newPage.$eval('.instock.availability', text => {
                      // Strip new line and tab spaces
                      text = text.textContent.replace(/(rnt|n|r|t)/gm, "");
                      // Get the number of stock available
                      let regexp = /^.*((.*)).*$/i;
                      let stockAvailable = regexp.exec(text)[1].split(' ')[0];
                      return stockAvailable;
                  });
                  dataObj['imageUrl'] = await newPage.$eval('#product_gallery img', img => img.src);
                  dataObj['bookDescription'] = await newPage.$eval('#product_description', div => div.nextSibling.nextSibling.textContent);
                  dataObj['upc'] = await newPage.$eval('.table.table-striped > tbody > tr > td', table => table.textContent);
                  resolve(dataObj);
                  await newPage.close();
              });
      
              for(link in urls){
                  let currentPageData = await pagePromise(urls);
                  // scrapedData.push(currentPageData);
                  console.log(currentPageData);
              }
      
          }
      }
      
      module.exports = scraperObject;
      

      Você tem uma matriz de todas as URLs. Você deseja que o loop percorra essa matriz, abra a URL em uma nova página, colete os dados dela, feche-a e abra uma nova página para a próxima URL na matriz. Observe que você envolveu esse código em uma Promessa. Isso foi feito porque deseja-se ser capaz de esperar que cada ação no loop seja concluída. Portanto, cada Promessa abre uma nova URL e não irá ser finalizada até que o programa tenha coletado todos os dados na URL, e que essa instância de página tenha sido fechada.

      Aviso: note que você esperou pela Promessa usando um loop for-in. Qualquer outro loop será suficiente, mas evite iterar sobre suas matrizes de URL usando um método de iteração de matrizes, como o forEach, ou qualquer outro método que use uma função de callback. Isso ocorre porque a função de callback terá que percorrer a fila de callbacks e o loop de eventos primeiro e, portanto, várias instâncias de página serão abertas de uma só vez. Isso irá colocar uma tensão muito maior em sua memória.

      Dê uma olhada mais de perto na sua função pagePromise. Seu coletor de dados criou uma nova página para cada URL, e então você usou a função page.$eval() para direcionar os coletores de dados para detalhes relevantes que você queria coletar na nova página. Alguns dos textos contêm espaços em branco, caracteres de nova linha e outros caracteres não alfanuméricos, que você retirou usando uma expressão regular. Em seguida, você anexou o valor para cada parte dos dados coletada nesta página a um Objeto e resolveu esse objeto.

      Salve e feche o arquivo.

      Execute o script novamente:

      O navegador abre a página inicial e então abre cada página de livro e registra os dados coletados de cada uma dessas páginas. Esse resultado será impresso em seu console:

      Output

      Opening the browser...... Navigating to http://books.toscrape.com... { bookTitle: 'A Light in the Attic', bookPrice: '£51.77', noAvailable: '22', imageUrl: 'http://books.toscrape.com/media/cache/fe/72/fe72f0532301ec28892ae79a629a293c.jpg', bookDescription: "It's hard to imagine a world without A Light in the Attic. [...]', upc: 'a897fe39b1053632' } { bookTitle: 'Tipping the Velvet', bookPrice: '£53.74', noAvailable: '20', imageUrl: 'http://books.toscrape.com/media/cache/08/e9/08e94f3731d7d6b760dfbfbc02ca5c62.jpg', bookDescription: `"Erotic and absorbing...Written with starling power."--"The New York Times Book Review " Nan King, an oyster girl, is captivated by the music hall phenomenon Kitty Butler [...]`, upc: '90fa61229261140a' } { bookTitle: 'Soumission', bookPrice: '£50.10', noAvailable: '20', imageUrl: 'http://books.toscrape.com/media/cache/ee/cf/eecfe998905e455df12064dba399c075.jpg', bookDescription: 'Dans une France assez proche de la nôtre, [...]', upc: '6957f44c3847a760' } ...

      Neste passo, você coletou dados relevantes para todos os livros na página inicial de books.toscrape.com, mas poderia adicionar ainda muito mais funcionalidades. Cada página de livros, por exemplo, é paginada; como você pega livros dessas outras páginas? Além disso, no lado esquerdo do site que você viu categorias de livros; e se você não quiser todos os livros, mas apenas aqueles de um gênero em particular? Agora, você irá adicionar esses recursos.

      Passo 4 — Coletando dados de várias páginas

      As páginas em books.toscrape.com que são paginadas têm um botão next abaixo do seu conteúdo, enquanto que as páginas que não são paginadas não o tem.

      Você irá usar a presença desse botão para determinar se a página é paginada ou não. Como os dados em cada página têm a mesma estrutura e a mesma marcação, não será necessário escrever um coletor de dados para todas as páginas possíveis. Em vez disso, você irá usar a prática de recursão.

      Primeiro, é necessário alterar um pouco a estrutura do seu código para acomodar a navegação recursiva para várias páginas.

      Abra o pagescraper.js novamente:

      Você irá adicionar uma nova função chamada scrapeCurrentPage() ao seu método scraper(). Essa função irá conter todo o código que coleta dados de uma página em particular e então clica no botão ‘next’ se ele existir. Adicione o conteúdo destacado a seguir:

      ./book-scraper/pageScraper.js scraper()

      const scraperObject = {
          url: 'http://books.toscrape.com',
          async scraper(browser){
              let page = await browser.newPage();
              console.log(`Navigating to ${this.url}...`);
              // Navigate to the selected page
              await page.goto(this.url);
              let scrapedData = [];
              // Wait for the required DOM to be rendered
              async function scrapeCurrentPage(){
                  await page.waitForSelector('.page_inner');
                  // Get the link to all the required books
                  let urls = await page.$$eval('section ol > li', links => {
                      // Make sure the book to be scraped is in stock
                      links = links.filter(link => link.querySelector('.instock.availability > i').textContent !== "In stock")
                      // Extract the links from the data
                      links = links.map(el => el.querySelector('h3 > a').href)
                      return links;
                  });
                  // Loop through each of those links, open a new page instance and get the relevant data from them
                  let pagePromise = (link) => new Promise(async(resolve, reject) => {
                      let dataObj = {};
                      let newPage = await browser.newPage();
                      await newPage.goto(link);
                      dataObj['bookTitle'] = await newPage.$eval('.product_main > h1', text => text.textContent);
                      dataObj['bookPrice'] = await newPage.$eval('.price_color', text => text.textContent);
                      dataObj['noAvailable'] = await newPage.$eval('.instock.availability', text => {
                          // Strip new line and tab spaces
                          text = text.textContent.replace(/(rnt|n|r|t)/gm, "");
                          // Get the number of stock available
                          let regexp = /^.*((.*)).*$/i;
                          let stockAvailable = regexp.exec(text)[1].split(' ')[0];
                          return stockAvailable;
                      });
                      dataObj['imageUrl'] = await newPage.$eval('#product_gallery img', img => img.src);
                      dataObj['bookDescription'] = await newPage.$eval('#product_description', div => div.nextSibling.nextSibling.textContent);
                      dataObj['upc'] = await newPage.$eval('.table.table-striped > tbody > tr > td', table => table.textContent);
                      resolve(dataObj);
                      await newPage.close();
                  });
      
                  for(link in urls){
                      let currentPageData = await pagePromise(urls);
                      scrapedData.push(currentPageData);
                      // console.log(currentPageData);
                  }
                  // When all the data on this page is done, click the next button and start the scraping of the next page
                  // You are going to check if this button exist first, so you know if there really is a next page.
                  let nextButtonExist = false;
                  try{
                      const nextButton = await page.$eval('.next > a', a => a.textContent);
                      nextButtonExist = true;
                  }
                  catch(err){
                      nextButtonExist = false;
                  }
                  if(nextButtonExist){
                      await page.click('.next > a');   
                      return scrapeCurrentPage(); // Call this function recursively
                  }
                  await page.close();
                  return scrapedData;
              }
              let data = await scrapeCurrentPage();
              console.log(data);
              return data;
          }
      }
      
      module.exports = scraperObject;
      
      

      Você define a variável nextButtonExist como falsa inicialmente, para então verificar-se se o botão existe. Se o botão next existir, você define o nextButtonExists como true, para em seguida clicar no botão next. Depois disso, chama essa função de forma recursiva.

      Se o nextButtonExists for falso, ele retorna a matriz scrapedData como de costume.

      Salve e feche o arquivo.

      Execute seu script novamente:

      Ele pode demorar um tempo para ser concluído. Sua aplicação, afinal de contas, está agora coletando dados de mais de 800 livros. Sinta-se livre para fechar o navegador ou pressionar CTRL + C para cancelar o processo.

      Agora, você maximizou as capacidades do seu coletor de dados, mas criou um novo problema no processo. Agora, o problema não existe por haver poucos dados, mas sim muitos dados. No próximo passo, você irá ajustar seu aplicativo para filtrar sua coleta de dados por categoria de livro.

      Passo 5 — Coletando dados por categoria

      Para coletar dados por categoria, será necessário modificar tanto seu arquivo pageScraper.js quanto seu arquivo pageController.js.

      Abra o pageController.js em um editor de texto:

      nano pageController.js
      

      Chame o coletor de dados para que ele colete apenas livros de viagens. Adicione as linhas a seguir:

      ./book-scraper/pageController.js

      const pageScraper = require('./pageScraper');
      async function scrapeAll(browserInstance){
          let browser;
          try{
              browser = await browserInstance;
              let scrapedData = {};
              // Call the scraper for different set of books to be scraped
              scrapedData['Travel'] = await pageScraper.scraper(browser, 'Travel');
              await browser.close();
              console.log(scrapedData)
          }
          catch(err){
              console.log("Could not resolve the browser instance => ", err);
          }
      }
      module.exports = (browserInstance) => scrapeAll(browserInstance)
      

      Agora, você está passando dois parâmetros para seu método pageScraper.scraper(), sendo o segundo parâmetro a categoria de livros que deseja coletar, que neste exemplo é Travel. Mas seu arquivo pageScraper.js ainda não reconhece esse parâmetro. Será necessário ajustar também esse arquivo.

      Salve e feche o arquivo.

      Abra o pageScraper.js:

      Adicione o código a seguir, que irá adicionar seu parâmetro de categoria, navegar até essa página da categoria e então começar a coletar os resultados paginados:

      ./book-scraper/pageScraper.js

      const scraperObject = {
          url: 'http://books.toscrape.com',
          async scraper(browser, category){
              let page = await browser.newPage();
              console.log(`Navigating to ${this.url}...`);
              // Navigate to the selected page
              await page.goto(this.url);
              // Select the category of book to be displayed
              let selectedCategory = await page.$$eval('.side_categories > ul > li > ul > li > a', (links, _category) => {
      
                  // Search for the element that has the matching text
                  links = links.map(a => a.textContent.replace(/(rnt|n|r|t|^s|s$|Bs|sB)/gm, "") === _category ? a : null);
                  let link = links.filter(tx => tx !== null)[0];
                  return link.href;
              }, category);
              // Navigate to the selected category
              await page.goto(selectedCategory);
              let scrapedData = [];
              // Wait for the required DOM to be rendered
              async function scrapeCurrentPage(){
                  await page.waitForSelector('.page_inner');
                  // Get the link to all the required books
                  let urls = await page.$$eval('section ol > li', links => {
                      // Make sure the book to be scraped is in stock
                      links = links.filter(link => link.querySelector('.instock.availability > i').textContent !== "In stock")
                      // Extract the links from the data
                      links = links.map(el => el.querySelector('h3 > a').href)
                      return links;
                  });
                  // Loop through each of those links, open a new page instance and get the relevant data from them
                  let pagePromise = (link) => new Promise(async(resolve, reject) => {
                      let dataObj = {};
                      let newPage = await browser.newPage();
                      await newPage.goto(link);
                      dataObj['bookTitle'] = await newPage.$eval('.product_main > h1', text => text.textContent);
                      dataObj['bookPrice'] = await newPage.$eval('.price_color', text => text.textContent);
                      dataObj['noAvailable'] = await newPage.$eval('.instock.availability', text => {
                          // Strip new line and tab spaces
                          text = text.textContent.replace(/(rnt|n|r|t)/gm, "");
                          // Get the number of stock available
                          let regexp = /^.*((.*)).*$/i;
                          let stockAvailable = regexp.exec(text)[1].split(' ')[0];
                          return stockAvailable;
                      });
                      dataObj['imageUrl'] = await newPage.$eval('#product_gallery img', img => img.src);
                      dataObj['bookDescription'] = await newPage.$eval('#product_description', div => div.nextSibling.nextSibling.textContent);
                      dataObj['upc'] = await newPage.$eval('.table.table-striped > tbody > tr > td', table => table.textContent);
                      resolve(dataObj);
                      await newPage.close();
                  });
      
                  for(link in urls){
                      let currentPageData = await pagePromise(urls);
                      scrapedData.push(currentPageData);
                      // console.log(currentPageData);
                  }
                  // When all the data on this page is done, click the next button and start the scraping of the next page
                  // You are going to check if this button exist first, so you know if there really is a next page.
                  let nextButtonExist = false;
                  try{
                      const nextButton = await page.$eval('.next > a', a => a.textContent);
                      nextButtonExist = true;
                  }
                  catch(err){
                      nextButtonExist = false;
                  }
                  if(nextButtonExist){
                      await page.click('.next > a');   
                      return scrapeCurrentPage(); // Call this function recursively
                  }
                  await page.close();
                  return scrapedData;
              }
              let data = await scrapeCurrentPage();
              console.log(data);
              return data;
          }
      }
      
      module.exports = scraperObject;
      

      Esse bloco de código usa a categoria que você passou para obter a URL onde os livros dessa categoria residem.

      O page.$$eval() pode receber argumentos passando o argumento como um terceiro parâmetro para o método $$eval() e o definindo como o terceiro parâmetro na callback, dessa forma:

      example page.$$eval() function

      page.$$eval('selector', function(elem, args){
          // .......
      }, args)
      

      Isso foi o que você fez em seu código: passou a categoria de livros da qual queria coletar dados, verificou todas as categorias para verificar qual é a correspondente e então retornou a URL dessa categoria.

      Essa URL é então usado para navegar até a página que exibe a categoria de livros da qual deseja coletar dados usando o método page.goto(selectedCategory).

      Salve e feche o arquivo.

      Execute seu aplicativo novamente. Você verá que ele navega até a categoria Travel, abre de forma recursiva os livros dessa categoria página por página e registra os resultados:

      Neste passo, você primeiro coletou dados de diversas páginas e então coletou dados de diversas páginas de uma categoria em particular. No passo final, você irá modificar seu script para coletar dados de diversas categorias e então salvar esses dados coletados em um arquivo JSON em string.

      Passo 6 — Coletando dados de diversas categorias e salvando-os como JSON

      Neste passo final, você fará com que seu script colete dados de todas as categorias que quiser e então altere a forma do seu resultado. Em vez de registrar os resultados, você irá salvá-los em um arquivo estruturado chamado data.json.

      É possível adicionar mais categorias de onde serão coletados os dados. Para fazer isso, é necessário apenas uma linha adicional por gênero.

      Abra o pageController.js:

      Ajuste seu código para incluir categorias adicionais. O exemplo abaixo adiciona HistoricalFiction e Mystery à nossa categoria Travel existente:

      ./book-scraper/pageController.js

      const pageScraper = require('./pageScraper');
      async function scrapeAll(browserInstance){
          let browser;
          try{
              browser = await browserInstance;
              let scrapedData = {};
              // Call the scraper for different set of books to be scraped
              scrapedData['Travel'] = await pageScraper.scraper(browser, 'Travel');
              scrapedData['HistoricalFiction'] = await pageScraper.scraper(browser, 'Historical Fiction');
              scrapedData['Mystery'] = await pageScraper.scraper(browser, 'Mystery');
              await browser.close();
              console.log(scrapedData)
          }
          catch(err){
              console.log("Could not resolve the browser instance => ", err);
          }
      }
      
      module.exports = (browserInstance) => scrapeAll(browserInstance)
      

      Salve e feche o arquivo.

      Execute o script novamente e obeserve-o enquanto coleta dados de todas as três categorias:

      Com o coletor de dados totalmente funcional, seu passo final envolve salvar seus dados em um formato que seja mais útil. Agora, você irá armazená-los em um arquivo JSON usando o módulo fs no Node.js.

      Primeiro, abra o pageController.js novamente:

      Adicione o conteúdo destacado a seguir:

      ./book-scraper/pageController.js

      const pageScraper = require('./pageScraper');
      const fs = require('fs');
      async function scrapeAll(browserInstance){
          let browser;
          try{
              browser = await browserInstance;
              let scrapedData = {};
              // Call the scraper for different set of books to be scraped
              scrapedData['Travel'] = await pageScraper.scraper(browser, 'Travel');
              scrapedData['HistoricalFiction'] = await pageScraper.scraper(browser, 'Historical Fiction');
              scrapedData['Mystery'] = await pageScraper.scraper(browser, 'Mystery');
              await browser.close();
              fs.writeFile("data.json", JSON.stringify(scrapedData), 'utf8', function(err) {
                  if(err) {
                      return console.log(err);
                  }
                  console.log("The data has been scraped and saved successfully! View it at './data.json'");
              });
          }
          catch(err){
              console.log("Could not resolve the browser instance => ", err);
          }
      }
      
      module.exports = (browserInstance) => scrapeAll(browserInstance)
      

      Primeiro, você está solicitando o módulo fs do Node,js em pageController.js. Isso garante que seja possível salvar seus dados como um arquivo JSON. Em seguida, está adicionando código para que quando a coleta de dados for concluída e o navegador for fechado, o programa crie um novo arquivo chamado data.json. Observe que o conteúdo de data.json é JSON em string. Portanto, ao ler o conteúdo de data.json, analise-o sempre como JSON antes de reusar os dados.

      Salve e feche o arquivo.

      Agora, você criou um aplicativo de coleta de dados na Web que coleta livros de várias categorias e então armazena seus dados coletados em um arquivo JSON. À medida que seu aplicativo cresce em complexidade, pode ser desejável armazenar esses dados coletados em um banco de dados ou atendê-los por meio de uma API. A forma como esses dados são consumidos depende somente de você.

      Conclusão

      Neste tutorial, você construiu um rastreador Web que coletou dados de várias páginas de forma recursiva e então os salvou em um arquivo JSON. Em resumo, você aprendeu uma nova maneira de automatizar a coleta de dados de sites.

      O Puppeteer possui muitos recursos que não estavam no âmbito deste tutorial. Para aprender mais, confira Usando o Puppeteer para o controle fácil do Chrome sem periféricos. Visite também a documentação oficial do Puppeteer.



      Source link