One place for hosting & domains

      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

      Compreendendo bancos de dados relacionais


      Introdução

      Os sistemas de gerenciamento de banco de dados (SGDB, no inglês DMBS) são programas de computador que permitem que usuários interajam com um banco de dados. Um DBMS permite que os usuários controlem o acesso a um banco de dados, gravem dados, executem consultas e façam outras tarefas relacionadas ao gerenciamento de banco de dados.

      No entanto, para realizar qualquer uma dessas tarefas, o DBMS deve possuir algum modelo subjacente que defina como os dados são organizados. O modelo relacional é uma abordagem para organizar dados que vem sendo bastante usada em softwares de banco de dados desde que foram criados no final dos anos 60. Tanto é que, no momento em que este artigo está sendo escrito, quatro dos cinco DBMS mais populares são relacionais.

      Este artigo conceitual descreve o histórico do modelo relacional, como os bancos de dados relacionais organizam dados e como são usados hoje.

      História do modelo relacional

      Os bancos de dados são clusters de informações logicamente modelados, ou dados. Qualquer coleção de dados é um banco de dados, independentemente de como ou onde é armazenada. Mesmo um armário de arquivos que contém informações de folha de pagamento é um banco de dados, assim como uma pilha de formulários hospitalares ou uma coleção de informações de cliente de uma empresa espalhada em vários locais. Antes do armazenamento e gerenciamento de dados com computadores ser uma prática comum, bancos de dados físicos como esses eram os únicos disponíveis para as organizações governamentais e empresariais que precisavam armazenar informações.

      Em meados do século XX, pesquisas na área da ciência da computação levaram à criação de máquinas com maior poder de processamento, bem como maior capacidade de armazenamento local e externo. Esses avanços levaram os cientistas da computação a começar a reconhecer o potencial que essas máquinas tinham para armazenar e gerenciar quantidades cada vez maiores de dados.

      No entanto, não havia nenhuma teoria para explicar como os computadores poderiam organizar os dados de maneiras lógicas e significativas. Uma coisa é armazenar dados não ordenados em uma máquina, e outra, muito mais complicada, é projetar sistemas que permitam adicionar, recuperar, classificar e gerenciar os dados de maneira consistente e prática. A necessidade de uma estrutura lógica para armazenar e organizar dados levou a uma série de propostas sobre como tirar proveito dos computadores para gerenciar dados.

      Um modelo de banco de dados inicial foi o modelo hierárquico, no qual os dados são organizados em uma estrutura que se assemelha a uma árvore, semelhante aos sistemas de arquivos modernos. O exemplo a seguir mostra como o layout de parte de um banco de dados hierárquico usado para categorizar animais se pareceria:

      Exemplo de um banco de dados hierárquico: categorização de animais

      O modelo hierárquico foi amplamente implementado nos primeiros sistemas de gerenciamento banco de dados, mas também se mostrou um pouco inflexível. Neste modelo, mesmo que os registros individuais possam ter vários “filhos”, cada registro só pode ter um “pai” na hierarquia. Por conta disso, esses primeiros bancos de dados hierárquicos ficavam limitados a representar apenas relações “um a um” e “um para muitos”. Essa falta de relações”muitos para muitos” poderia levar a problemas quando se estivesse trabalhando com pontos de dados que você gostaria de associar a mais de um pai.

      No final dos anos 60, Edgar F. Codd, um cientista da computação que trabalhava na IBM, criou o modelo relacional de gerenciamento de banco de dados. O modelo relacional de Codd permitiu que registros individuais fossem associados a mais de uma tabela, permitindo assim relações “muitos para muitos” entre pontos de dados, além de relacionamentos “um para muitos”. Isso proporcionou maior flexibilidade do que outros modelos existentes no que diz respeito ao projeto de banco de dados. Isso fez com que os sistemas de gerenciamento de banco de dados relacionais (RDBMSs) pudessem atender a uma gama muito maior de necessidades de negócios.

      Codd propôs uma linguagem para gerenciar dados relacionais, conhecida como Alpha, que influenciou o desenvolvimento de linguagens de banco de dados posteriores. Dois dos colegas de Codd na IBM, Donald Chamberlin e Raymond Boyce, criaram uma dessas linguagens inspirada no Alpha. Eles deram a ela o nome de SEQUEL, abreviação de Structured English Query Language. No entanto, por causa de uma marca registrada existente, eles reduziram o nome de sua linguagem para SQL (conhecida mais formalmente como Structured Query Language).

      Devido às restrições de hardware, os primeiros bancos de dados relacionais ainda eram proibitivamente lentos, e foi necessário algum tempo até que a tecnologia se tornasse difundida. Mas em meados dos anos 80, o modelo relacional de Codd já havia sido implementado em diversos produtos de gerenciamento de banco de dados comercial tanto da IBM quando de seus concorrentes. Esses fornecedores também seguiram a iniciativa da IBM desenvolvendo e implementando seus próprios dialetos de SQL. Em 1987, tanto o American National Standards Institute quanto a International Organization for Standardization haviam ratificado e publicado padrões para SQL, solidificando seu status como a linguagem aceita para gerenciar RDBMSs.

      O uso difundido do modelo relacional em várias indústrias fez com que ele se tornasse reconhecido como o modelo padrão para o gerenciamento de dados. Mesmo com o surgimento de vários bancos de dados NoSQL nos últimos anos, os bancos de dados relacionais continuam sendo as ferramentas dominantes para armazenar e organizar dados.

      Como os bancos de dados relacionais organizam os dados

      Agora que você compreende de maneira geral a história do modelo relacional, vamos dar uma olhada mais de perto em como o modelo organiza os dados.

      Os elementos mais fundamentais no modelo relacional são as relações, que os usuários e RDBMS modernos reconhecem como tabelas. Uma relação é um conjunto de tuplas, ou linhas em uma tabela, com cada tupla compartilhando um conjunto de atributos, ou colunas:

      Diagrama exemplo de como as relações, tuplas e atributos se relacionam entre si

      Uma coluna é a menor estrutura organizacional de um banco de dados relacional e representa as diversas facetas que definem os registros na tabela. Daí seu nome mais formal, atributos. Você pode pensar em cada tupla como sendo uma instância única de qualquer tipo de pessoas, objetos, eventos ou associações que a tabela possua. Essas instâncias podem representar coisas como funcionários em uma empresa, vendas de um negócio on-line ou resultados de testes de laboratório. Por exemplo, em uma tabela que contenha registros de funcionário de professores em uma escola, as tuplas podem possuir atributos como name (nome), subjects (disciplinas), start_date (data de início), e assim por diante.

      Ao criar colunas, você especifica um tipo de dados que dita qual o tipo das entradas que são permitidas nessa coluna. Os RDBMSs muitas vezes implementam seus próprios tipos de dados únicos, que podem não ser diretamente intercambiáveis com tipos de dados semelhantes de outros sistemas. Alguns tipos de dados comuns incluem datas, strings, inteiros e booleanos.

      No modelo relacional, cada tabela contém pelo menos uma coluna que pode ser usada para identificar de maneira única cada linha, chamada de chave primária. Isso é importante, pois significa que os usuários não precisam saber onde seus dados ficam fisicamente armazenados em uma máquina. Em vez disso, seu DBMS pode manter o controle de cada registro e retorná-los conforme necessário. Por sua vez, isso significa que os registros não possuem ordem lógica definida, e os usuários têm a capacidade de retornar seus dados em qualquer ordem ou sob o efeito do filtro que quiserem.

      Se você tiver duas tabelas que gostaria de se associar uma com a outra, uma maneira de fazer isso é com uma chave estrangeira. Uma chave estrangeira é essencialmente uma cópia da chave primária de uma tabela (a tabela “pai”) inserida em uma coluna em outra tabela (o “filho”). O exemplo a seguir destaca a relação entre duas tabelas. Uma é usada para registrar informações sobre funcionários em uma empresa e a outra é usada para acompanhar as vendas dela. Neste exemplo, a chave primária da tabela EMPLOYEES (funcionários) é usada como a chave estrangeira na tabela SALES (vendas):

      Diagrama exemplo de como a chave primária da tabela de funcionários funciona como chave estrangeira da tabela SALES

      Se você tentar adicionar um registro à tabela filho e o valor inserido na coluna de chaves estrangeiras não existir na chave primária da tabela de origem, a declaração de inserção será inválida. Isso ajuda a manter a integridade de nível de relação, já que as linhas em ambas as tabelas sempre estarão relacionadas corretamente.

      Os elementos estruturais do modelo relacional ajudam a manter os dados armazenados de forma organizada, mas o armazenamento de dados só é útil se for possível recuperá-los. Para recuperar informações de um RDBMS, é possível emitir uma consulta, ou uma solicitação estruturada para um conjunto de informações. Como mencionado anteriormente, a maioria dos bancos de dados relacionais usa SQL para gerenciar e consultar dados. O SQL permite que você filtre e manipule os resultados da consulta com uma variedade de cláusulas, predicados e expressões, dando-lhe um controle fino sobre quais dados aparecerão no conjunto de resultados.

      Vantagens e limitações dos bancos de dados relacionais

      Com a estrutura organizacional subjacente de bancos de dados relacionais em mente, vamos considerar algumas de suas vantagens e desvantagens.

      Hoje em dia, tanto o SQL quanto os bancos de dados que o implementam se desviam do modelo relacional de Codd de várias maneiras. Por exemplo, o modelo de Codd determina que cada linha em uma tabela deve ser única, enquanto que, por razões de praticidade, a maioria dos bancos de dados relacionais modernos permite linhas duplicadas. Existem algumas pessoas que não consideram os bancos de dados SQL como verdadeiros bancos de dados relacionais se não conseguem aderir às especificações de Codd para o modelo relacional. No entanto, em termos práticos, qualquer DBMS que use SQL e de certa forma acate ao modelo relacional de dados provavelmente será referido como um sistema de gerenciamento de banco de dados relacional.

      Embora os bancos de dados relacionais tenham rapidamente crescido em popularidade, algumas das desvantagens do modelo relacional começaram a se tornar aparentes à medida que os dados se tornaram mais valiosos e as empresas começaram a armazenar uma maior quantidade deles. Por exemplo, pode ser difícil dimensionar um banco de dados relacional horizontalmente. O dimensionamento horizontal, ou ampliamento, é a prática de adicionar mais máquinas a uma pilha existente para espalhar a carga e permitir um maior tráfego e processamento mais rápido. Isso é muitas vezes contrastado com o dimensionamento vertical, que envolve atualizar o hardware de um servidor existente, geralmente adicionando mais RAM ou CPU.

      A razão pela qual é difícil dimensionar um banco de dados relacional de dados horizontalmente tem a ver com o fato de que o modelo relacional foi projetado para garantir a consistência, ou seja, clientes que consultarem o mesmo banco de dados sempre receberão os mesmos dados. Se você fosse dimensionar um banco de dados relacional horizontalmente em várias máquinas, tornaria-se difícil garantir a consistência, pois os clientes poderiam escrever dados em um nó mas não nos outros. Provavelmente haveria um atraso entre a gravação inicial e o momento em que os outros nós fossem atualizados para refletir as alterações, resultando em inconsistências entre eles.

      Outra limitação apresentada pelos RDBMSs é que o modelo relacional foi projetado para gerenciar dados estruturados, ou dados que se alinhem a um tipo de dados pré-definido ou que pelo menos estejam organizados de alguma forma pré-determinada, tornando-os facilmente categorizáveis e pesquisáveis. No entanto, com a propagação da computação pessoal e o aumento da internet no início dos anos 90, dados não estruturados — como mensagens de e-mail, fotos, vídeos, etc — se tornaram mais comuns.

      Nada disso é para dizer que os bancos de dados relacionais não são úteis. Muito pelo contrário, o modelo relacional ainda é a estrutura dominante para o gerenciamento de dados após mais de 40 anos. Sua prevalência e longevidade significam que os bancos de dados relacionais são uma tecnologia madura, que por si só é uma de suas principais vantagens. Existem muitos aplicativos projetados para trabalhar com o modelo relacional, bem como muitos profissionais administradores de banco de dados que são especialistas no assunto. Também há uma grande variedade de recursos disponíveis on-line e em livros para aqueles que querem começar com bancos de dados relacionais.

      Outra vantagem dos bancos de dados relacionais é que quase todos os RDBMS suportam transações. Uma transação consiste em uma ou mais declarações SQL individuais realizadas em sequência como uma única unidade de trabalho. As transações apresentam uma abordagem tudo ou nada, o que significa que cada declaração SQL na transação deve ser válida; caso contrário, toda a transação falhará. Isso é muito útil para garantir a integridade dos dados ao fazer alterações em várias linhas ou tabelas.

      Por fim, os bancos de dados relacionais são extremamente flexíveis. Eles foram usados para construir uma grande variedade de aplicativos diferentes e continuam funcionando de maneira eficiente, mesmo com grandes quantidades de dados. O SQL também é extremamente poderoso, permitindo adicionar e alterar os dados em tempo real, bem como alterar a estrutura de esquemas e tabelas do banco de dados sem afetar os dados existentes.

      Conclusão

      Graças à sua flexibilidade e design para a integridade dos dados, os bancos de dados relacionais ainda representam a principal maneira de os dados serem gerenciados e armazenados mais de cinquenta anos após terem sido concebidos pela primeira vez. Mesmo com o surgimento de diversos bancos de dados NoSQL nos últimos anos, compreender o modelo relacional e como trabalhar com RDBMSs é muito importante para quem quiser construir aplicativos que tirem proveito do poder dos dados.

      Para aprender mais sobre alguns RDBMSs populares de código aberto, recomendamos que você consulte nossa comparação entre vários bancos de dados SQL relacionais de código aberto. Se tiver interesse em aprender mais sobre bancos de dados de maneira geral, recomendamos que verifique nossa biblioteca completa de conteúdos relacionados a banco de dados.



      Source link

      Como Usar o Tipo de Dados MySQL BLOB para Armazenar Imagens com PHP no Ubuntu 18.04


      O autor selecionou Girls Who Code para receber uma doação como parte do programa Write for DOnations.

      Introdução

      Um Binary Large Object (BLOB) é um tipo de dados MySQL que pode armazenar dados binários como imagens, multimedia e arquivos PDF.

      Ao criar aplicações que requerem um banco de dados fortemente acoplado onde imagens devem estar sincronizadas com dados relacionados (por exemplo, um portal de funcionários, um banco de dados de estudantes, ou uma aplicação financeira), você pode achar que é conveniente armazenar imagens como fotos de passaporte e assinaturas de alunos em um banco de dados MySQL, juntamente com outras informações relacionadas.

      É aqui que o tipo de dados MySQL BLOB entra. Esta abordagem de programação elimina a necessidade de criar um sistema de arquivos separado para armazenar imagens. O esquema também centraliza o banco de dados, tornando-o mais portátil e seguro porque os dados estão isolados do sistema de arquivos. A criação de backups também é mais simples, pois você pode criar um único arquivo de dump do MySQL que contém todos os seus dados.

      A recuperação de dados é mais rápida e, ao criar registros, você pode garantir que as regras de validação de dados e a integridade referencial sejam mantidas especialmente ao utilizar as transações do MySQL.

      Neste tutorial, você usará o tipo de dados MySQL BLOB para armazenar imagens com PHP no Ubuntu 18.04.

      Pré-requisitos

      Para seguir com este guia, você precisará do seguinte:

      Passo 1 — Criando um Banco de Dados

      Você começará criando um banco de dados de exemplo para seu projeto. Para fazer isso, faça um SSH em seu servidor e, em seguida, execute o seguinte comando para fazer login no seu servidor MySQL como root:

      Digite a senha do root do seu banco de dados MySQL e clique em ENTER para continuar.

      Em seguida, execute o seguinte comando para criar um banco de dados. Neste tutorial, iremos nomeá-lo como test_company:

      • CREATE DATABASE test_company;

      Assim que o banco de dados for criado, você verá a seguinte saída:

      Output

      Query OK, 1 row affected (0.01 sec)

      Em seguida, crie uma conta test_user no servidor MySQL e lembre-se de substituir PASSWORD por uma senha forte:

      • CREATE USER 'test_user'@'localhost' IDENTIFIED BY 'PASSWORD';

      Você verá o seguinte resultado:

      Output

      Query OK, 0 rows affected (0.01 sec)

      Para conceder ao test_user privilégios completos no banco de dados test_company, execute:

      • GRANT ALL PRIVILEGES ON test_company.* TO 'test_user'@'localhost';

      Certifique-se de obter a seguinte saída:

      Output

      Query OK, 0 rows affected (0.01 sec)

      Finalmente, libere a tabela de privilégios para que o MySQL recarregue as permissões:

      Certifique-se de ver a seguinte saída:

      Output

      Query OK, 0 rows affected (0.01 sec)

      Agora que o banco de dados test_company e o test_user estão prontos, você prosseguirá com a criação de uma tabela products para armazenar produtos de exemplo. Você usará esta tabela mais tarde para inserir e recuperar registros para demonstrar como o MySQL BLOB funciona.

      Faça log-off do servidor MySQL:

      Em seguida, faça login novamente com as credenciais do test_user que você criou:

      Quando solicitado, digite a senha para o test_user e tecle ENTER para continuar. Em seguida, alterne para o banco de dados test_company digitando o seguinte:

      Assim que o banco de dados test_company for selecionado, o MySQL exibirá:

      Output

      Database changed

      Em seguida, crie uma tabela products executando:

      • CREATE TABLE `products` (product_id BIGINT PRIMARY KEY AUTO_INCREMENT, product_name VARCHAR(50), price DOUBLE, product_image BLOB) ENGINE = InnoDB;

      Este comando cria uma tabela com nome products. A tabela tem quatro colunas:

      • product_id: esta coluna utiliza um tipo de dados BIGINT para acomodar uma grande lista de produtos até um máximo de 2⁶³-1 items. Você marcou a coluna como PRIMARY KEY para identificar unicamente os produtos. Para que o MySQL trate da geração de novos identificadores para as colunas inseridas, você usou a palavra-chave AUTO_INCREMENT.

      • product_name: esta coluna contém os nomes dos produtos. Você usou o tipo de dados VARCHAR já que este campo geralmente irá lidar com alfanuméricos com um máximo de 50 caracteres — o limite de 50 é apenas um valor hipotético usado para o propósito deste tutorial.

      • price: para fins de demonstração, sua tabela products contém a coluna de preço para armazenar o preço de varejo dos produtos. Como alguns produtos podem ter valores de ponto flutuante (por exemplo, 23.69, 45.36, 102.99), você utilizou o tipo de dados DOUBLE.

      • product_image: esta coluna utiliza um tipo de dados BLOB para armazenar os dados do binário real das imagens dos produtos.

      Você usou o InnoDB storage ENGINE para que a tabela suporte uma grande variedade de recursos, incluindo as transações do MySQL. Após executar isso para criar a tabela de products, você verá a seguinte saída:

      Output

      Query OK, 0 rows affected (0.03 sec)

      Saia do seu servidor MySQL:

      Você receberá a seguinte saída:

      Output

      Bye

      A tabela products agora está pronta para armazenar alguns registros, incluindo imagens de produtos e você a preencherá com alguns produtos no próximo passo.

      Neste passo, você criará um script PHP que se conectará ao banco de dados MySQL que você criou no Passo 1. O script preparará três produtos de exemplo e os inserirá na tabela products.

      Para criar o código PHP, abra um novo arquivo com seu editor de texto:

      • sudo nano /var/www/html/config.php

      Em seguida, digite as seguintes informações no arquivo e substitua PASSWORD pela senha do test_user que você criou no Passo 1:

      /var/www/html/config.php

      <?php
      
      define('DB_NAME', 'test_company');
      define('DB_USER', 'test_user');
      define('DB_PASSWORD', 'PASSWORD');
      define('DB_HOST', 'localhost');
      
      $pdo = new PDO("mysql:host=" . DB_HOST . "; dbname=" . DB_NAME, DB_USER, DB_PASSWORD);
      $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
      $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
      
      

      Salve e feche o arquivo.

      Neste arquivo, você usou quatro constantes PHP para se conectar ao banco de dados MySQL que você criou no Passo 1:

      • DB_NAME esta constante contém o nome do banco de dados test_company.

      • DB_USER: esta variável contém o nome de usuário test_user.

      • DB_PASSWORD : esta constante armazena a PASSWORD MySQL da conta do test_user.

      • DB_HOST: isso representa o servidor onde o banco de dados está. Neste caso, você está usando o servidor localhost.

      A seguinte linha em seu arquivo inicia um PHP Data Object (PDO) e se conecta ao banco de dados MySQL:

      ...
      $pdo = new PDO("mysql:host=" . DB_HOST . "; dbname=" . DB_NAME, DB_USER, DB_PASSWORD);
      ...
      

      Perto do final do arquivo, você definiu alguns atributos PDO:

      • ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION: este atributo instrui o PDO a lançar uma exceção que pode ser logada para fins de depuração.
      • ATTR_EMULATE_PREPARES, false: esta opção aumenta a segurança ao informar ao o mecanismo de banco de dados do MySQL para fazer a preparação ao invés do PDO.

      Você incluirá o arquivo /var/www/html/config.php em dois scripts PHP que você criará a seguir para inserir e recuperar registros, respectivamente.

      Primeiro, crie o script PHP /var/www/html/insert_products.php para inserir registros na tabela products:

      • sudo nano /var/www/html/insert_products.php

      Em seguida, adicione as seguintes informações no arquivo /var/www/html/insert_products.php

      /var/www/html/insert_products.php

      <?php
      
      require_once 'config.php';
      
      $products = [];
      
      $products[] = [
                    'product_name' => 'VIRTUAL SERVERS',
                    'price' => 5,
                    'product_image' => file_get_contents("https://i.imgur.com/VEIKbp0.png")
                    ];
      
      $products[] = [
                    'product_name' => 'MANAGED KUBERNETES',
                    'price' => 30,
                    'product_image' => file_get_contents("https://i.imgur.com/cCc9Gw9.png")
                    ];
      
      $products[] = [
                    'product_name' => 'MySQL DATABASES',
                    'price' => 15,
                    'product_image' => file_get_contents("https://i.imgur.com/UYcHkKD.png" )
                    ];
      
      $sql = "INSERT INTO products(product_name, price, product_image) VALUES (:product_name, :price, :product_image)";
      
      foreach ($products as $product) {
          $stmt = $pdo->prepare($sql);
          $stmt->execute($product);
      }
      
      echo "Records inserted successfully";
      

      Salve e feche o arquivo.

      No arquivo, você incluiu o arquivo config.php no topo. Este é o primeiro arquivo que você criou para definir as variáveis de banco de dados e se conectar ao banco de dados. O arquivo também inicia um objeto PDO e o armazena em uma variável $pdo.

      Em seguida, você criou uma matriz de dados dos produtos para serem inseridos no banco de dados. Além de product_name e price, que são preparados como strings e valores numéricos respectivamente, o script utiliza a função integrada do PHP file_get_contents para ler imagens de uma origem externa e as passar como strings para a coluna product_image.

      Em seguida, você preparou uma instrução SQL e usou a instrução PHP foreach{...} para inserir cada produto no banco de dados.

      Para executar o arquivo /var/www/html/insert_products.php execute-o na janela do seu navegador usando a seguinte URL. Lembre-se de substituir your-server-IP pelo endereço IP público do seu servidor:

      http://your-server-IP/insert_products.php
      

      Após executar o arquivo, você verá uma mensagem de sucesso em seu navegador, confirmando que os registros foram inseridos no banco de dados.

      Uma mensagem mostrando que os registros foram inseridos com sucesso no banco de dados

      Você inseriu com sucesso três registros contendo imagens de produtos na tabela products. No próximo passo, você criará um script PHP para recuperar esses registros e exibi-los no seu navegador.

      Passo 3 — Exibindo Informações de Produtos do Banco de Dados MySQL

      Com as informações e imagens dos produtos no banco de dados, você agora irá programar outro script PHP que consulta e exibe informações dos produtos em uma tabela HTML no seu navegador.

      Para criar o arquivo, digite o seguinte:

      • sudo nano /var/www/html/display_products.php

      Em seguida, digite as seguintes informações no arquivo:

      /var/www/html/display_products.php

      <html>
        <title>Using BLOB and MySQL</title>
        <body>
      
        <?php
      
        require_once 'config.php';
      
        $sql = "SELECT * FROM products";
        $stmt = $pdo->prepare($sql);
        $stmt->execute();
        ?>
      
        <table border="1" align = 'center'> <caption>Products Database</caption>
          <tr>
            <th>Product Id</th>
            <th>Product Name</th>
            <th>Price</th>
            <th>Product Image</th>
          </tr>
      
        <?php
        while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
            echo '<tr>';
            echo '<td>' . $row['product_id'] . '</td>';
            echo '<td>' . $row['product_name'] . '</td>';
            echo '<td>' . $row['price'] . '</td>';
            echo '<td>' .
            '<img src = "data:image/png;base64,' . base64_encode($row['product_image']) . '" width = "50px" height = "50px"/>'
            . '</td>';
            echo '</tr>';
        }
        ?>
      
        </table>
        </body>
      </html>
      

      Salve as alterações no arquivo e feche-o.

      Aqui, você incluiu novamente o arquivo config.php para se conectar ao banco de dados. Em seguida, você preparou e executou uma instrução SQL usando o PDO para recuperar todos os itens da tabela products usando o comando SELECT * FROM products

      Depois, você criou uma tabela HTML e a preencheu com os dados dos produtos usando a instrução PHP while() {...}. A linha $row = $stmt->fetch(PDO::FETCH_ASSOC) consulta o banco de dados e armazena o resultado na variável $row como uma matriz multidimensional, que você então exibiu em uma coluna de tabela HTML usando a sintaxe $row['column_name'].

      As imagens da coluna product_image são incluídas dentro das tags <img src = "">. Você usou os atributos width e height para redimensionar as imagens para um tamanho menor que pode se encaixar na coluna HTML.

      Para converter os dados mantidos pelo tipo de dados BLOB de volta para imagens, você usou a função PHP integrada base64_encode e a seguinte sintaxe para o esquema Data URI:

      data:media_type;base64, base_64_encoded_data
      

      Neste caso, o image/png é o media_type e a string codificada em Base64 da coluna product_image é o base_64_encoded_data.

      Em seguida, execute o arquivo display_products.php em um navegador web digitando o seguinte endereço:

      http://your-server-IP/display_products.php
      

      Após executar o arquivo display_products.php em seu navegador, você verá uma tabela HTML com uma lista de produtos e imagens associadas.

      List of products from MySQL database

      Isso confirma que o script PHP para recuperar imagens do MySQL está funcionando como esperado.

      Conclusão

      Neste guia, você utilizou o tipo de dados MySQL BLOB para armazenar e exibir imagens com PHP no Ubuntu 18.04. Você também viu as vantagens básicas de armazenar imagens em um banco de dados, ao invés de armazená-las em um sistema de arquivos. Elas incluem a portabilidade, segurança e facilidade de backup. Se você estiver construindo uma aplicação como um portal de estudantes ou o banco de dados de funcionários que exige que informações e imagens relacionadas sejam armazenadas em conjunto, então essa tecnologia pode ser de grande uso para você.

      Para obter mais informações sobre os tipos de dados suportados no MySQL, siga o guia MySQL Data Types. Se você estiver interessado em mais conteúdos relacionados ao MySQL e ao PHP, verifique os seguintes tutoriais:



      Source link