One place for hosting & domains

      Puppeteer

      Auslesen einer Website mit Node.js und Puppeteer


      Der Autor wählte den Free and Open Source Fund, um eine Spende im Rahmen des Programms Write for DOnations zu erhalten.

      Einführung

      Web Scraping ist ein Prozess zur Automatisierung der Erfassung von Daten aus dem Web. Der Prozess stellt in der Regel einen „Crawler“ bereit, der automatisch im Web surft und Daten von ausgewählten Seiten ausliest. Es gibt viele Gründe dafür, Daten auslesen zu wollen. In erster Linie macht das die Erfassung von Daten wesentlich schneller, da der manuelle Datenerfassungsprozess eliminiert wird. Zudem ist Scraping eine Lösung, wenn Datenerfassung gewünscht oder benötigt wird, die Website jedoch keine API bereitstellt.

      In diesem Tutorial erstellen Sie mit Node.js und Puppeteer eine Web-Scraping-Anwendung. Ihre Anwendung wird mit dem Fortschreiten komplexer. Zuerst werden Sie Ihre Anwendung so codieren, dass sie Chromium öffnet und eine spezielle Website lädt, die als Web-Scraping-Sandbox konzipiert ist: books.toscrape.com. In den nächsten beiden Schritten werden Sie alle Bücher auf einer Seite von books.toscrape und dann alle Bücher über mehrere Seiten hinweg auslesen. In den verbleibenden Schritten filtern Sie Ihr Scraping nach Buchkategorie und speichern Ihre Daten dann als JSON-Datei.

      Warnung: Die Ethik und Legalität von Web Scraping sind sehr komplex und entwickeln sich ständig weiter. Sie unterscheiden sich außerdem je nach Ihrem Standort, dem Standort der Daten und der entsprechenden Website. Dieses Tutorial wird eine spezielle Website (books.toscrape.com) ausgelesen, die speziell zum Testen von Scraper-Anwendungen entwickelt wurde. Das Scraping anderer Domänen geht über den Umfang dieses Tutorials hinaus.

      Voraussetzungen

      Schritt 1 — Einrichten des Web Scrapers

      Wenn Node.js installiert ist, können Sie mit dem Einrichten Ihres Web Scrapers beginnen. Zuerst erstellen Sie ein root-Verzeichnis für das Projekt und installieren dann die erforderlichen Abhängigkeiten. Dieses Tutorial erfordert nur eine Abhängigkeit. Sie installieren sie mit dem standardmäßigen Paketmanager npm von Node.js. Bei npm ist Node.js vorinstalliert, sodass Sie keine Installation mehr vornehmen müssen.

      Erstellen Sie einen Ordner für dieses Projekt und öffnen Sie ihn:

      • mkdir book-scraper
      • cd book-scraper

      Sie werden alle folgenden Befehle aus diesem Verzeichnis ausführen.

      Wir müssen mit npm oder dem Node-Paketmanager ein Paket installieren. Initialisieren Sie zunächst npm, um eine Datei namens packages.json zu erstellen, die Abhängigkeiten und Metadaten Ihres Projekts verwalten wird.

      Initialisieren Sie npm für Ihr Projekt:

      npm wird eine Sequenz von Eingabeaufforderungen anzeigen. Sie können bei jeder Eingabeaufforderung auf ENTER drücken oder personalisierte Beschreibungen hinzufügen. Stellen Sie sicher, dass Sie ENTER drücken und die Standardwerte lassen, wie sie sind, wenn Sie zur Eingabe von entry point und test command aufgefordert werden. Alternativ können Sie das y-Flag an npmnpm init -y — übergeben; damit werden alle Standardwerte für Sie übergeben.

      Ihre Ausgabe wird etwa wie folgt aussehen:

      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

      Geben Sie yes ein und drücken Sie ENTER. npm speichert diese Ausgabe als Ihre package.json-Datei.

      Verwenden Sie nun npm zum Installieren von Puppeteer:

      • npm install --save puppeteer

      Dieser Befehl installiert sowohl Puppeteer als auch eine Version von Chromium, von der das Puppeteer-Team weiß, dass sie mit der API funktionieren wird.

      Auf Linux-Rechnern kann Puppeteer möglicherweise zusätzliche Abhängigkeiten benötigen.

      Wenn Sie Ubuntu 18.04 verwenden, lesen Sie das Dropdown ‘Debian Dependencies’ im Abschnitt ‘Chrome headless doesn’t launch on UNIX’ in den Fehlerbehebungsdokumenten von Puppeteer. Sie können folgenden Befehl nutzen, um fehlende Abhängigkeiten zu finden:

      Nachdem npm, Puppeteer und weitere Abhängigkeiten installiert sind, erfordert Ihre package.json-Datei eine letzte Konfiguration, bevor Sie mit dem Codieren beginnen. In diesem Tutorial starten Sie Ihre Anwendung mit npm run start aus der Befehlszeile. Sie müssen einige Informationen über dieses start-Skript zu package.json hinzufügen. Vor allem müssen Sie unter der scripts-Anweisung eine Zeile in Bezug auf Ihren start-Befehl hinzufügen.

      Öffnen Sie die Datei in Ihrem bevorzugten Texteditor:

      Suchen Sie nach dem Abschnitt scripts: und fügen Sie die folgenden Konfigurationen hinzu. Denken Sie daran, am Ende der test-Skriptzeile ein Komma zu platzieren; sonst wird Ihre Datei wird nicht korrekt analysiert.

      Output

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

      Sie werden auch feststellen, dass puppeteer nun unter dependencies am Ende der Datei erscheint. Ihre package.json-Datei wird keine weiteren Überarbeitungen mehr benötigen. Speichern Sie Ihre Änderungen und schließen Sie den Editor.

      Sie sind nun bereit, Ihren Scraper zu codieren. Im nächsten Schritt werden Sie eine Browserinstanz einrichten und die grundlegende Funktionalität Ihres Scrapers testen.

      Schritt 2 — Einrichten der Browserinstanz

      Wenn Sie einen traditionellen Browser öffnen, können Sie auf Schaltflächen klicken, mit Ihrer Maus navigieren, Text eingeben, dev-Tools öffnen und mehr. Ein Headless-Browser wie Chromium ermöglicht Ihnen, dasselbe zu tun, aber programmatisch und ohne Benutzeroberfläche. In diesem Schritt werden Sie die Browserinstanz Ihres Scrapers einrichten. Wenn Sie Ihre Anwendung starten, öffnet sie Chromium automatisch und navigiert zu books.toscrape.com. Diese anfänglichen Aktionen werden die Grundlage Ihres Programms bilden.

      Ihr Web Scraper wird vier .js-Dateien benötigen: browser.js, index,js, pageController.js und pageScraper.js. In diesem Schritt erstellen Sie alle vier Dateien und aktualisieren sie dann weiter, sobald Ihr Programm komplexer wird. Beginnen Sie mit browser.js; diese Datei enthält das Skript, das Ihren Browser startet.

      Erstellen und öffnen Sie browser.js in einem Texteditor aus dem root-Verzeichnis Ihres Projekts:

      Zuerst werden Sie Puppeteer erfordern und dann eine async-Funktion namens startBrowser() erstellen. Diese Funktion startet den Browser und gibt eine Instanz davon zurück. Fügen Sie folgenden Code hinzu:

      ./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
      };
      

      Puppeteer bietet eine .launch()-Methode, mit der eine Instanz eines Browsers gestartet wird. Diese Methode gibt eine Zusage zurück; Sie müssen also sicherstellen, dass die Zusage mit einem .then– oder await-Block aufgelöst wird.

      Sie verwenden await um sicherzustellen, dass die Zusage aufgelöst wird, indem diese Instanz um einen try-catch-Codeblock eingeschlossen und dann eine Instanz des Browsers zurückgeben wird.

      Beachten Sie, dass die Methode .launch() einen JSON-Parameter mit mehreren Werten nutzt:

      • headlessfalse bedeutet, dass der Browser mit einer Oberfläche ausgeführt wird, sodass Sie Ihr Skript bei der Ausführung sehen können; true hingegen bedeutet, dass der Browser im headless-Modus ausgeführt wird. Beachten Sie unbedingt, dass Sie bei Bereitstellen Ihres Scrapers in der Cloud headless auf true zurücksetzen müssen. Die meisten virtuellen Rechner sind headless und enthalten keine Benutzeroberfläche; daher kann der Browser nur im headless-Modus ausgeführt werden. Puppeteer umfasst auch einen headful-Modus, der aber ausschließlich für Testzwecke verwendet werden sollte.
      • *ignoreHTTPSErrors *true ermöglicht Ihnen, Websites zu besuchen, die nicht über ein sicheres HTTPS-Protokoll gehostet werden, und jegliche HTTPS-Fehler zu ignorieren.

      Speichern und schließen Sie die Datei.

      Erstellen Sie nun Ihre zweite .js-Datei, index.js:

      Hier werden Sie browser.js und pageController.js erfordern. Dann werden Sie die Funktion startBrowser() aufrufen und die erstellte Browserinstanz an den Seitencontroller übergeben, der die Aktionen steuern wird. Fügen Sie folgenden Code hinzu:

      ./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)
      

      Speichern und schließen Sie die Datei.

      Erstellen Sie Ihre dritte .js-Datei, pageController.js:

      pageController.js steuert Ihren Scraping-Prozess. Es verwendet die Browserinstanz zum Steuern der Datei pageScraper.js, in der alle Scraping-Skripte ausgeführt werden. Schließlich werden Sie sie verwenden, um anzugeben, welche Buchkategorie Sie auslesen möchten. Sie wollen jedoch zunächst sicherstellen, dass Sie Chromium öffnen und zu einer Webseite navigieren können:

      ./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)
      

      Dieser Code exportiert eine Funktion, die die Browserinstanz nimmt und an eine Funktion namens scrapeAll() übergibt. Diese Funktion wiederum übergibt diese Instanz an pageScraper.scraper() als Argument, das sie zum Auslesen von Seiten verwendet wird.

      Speichern und schließen Sie die Datei.

      Erstellen Sie schließlich Ihre letzte .js-Datei, pageScraper.js:

      Hier erstellen Sie ein Objektliteral mit einer url-Eigenschaft und einer scraper()-Methode. Die url ist die Web-URL der Webseite, die Sie auslesen möchten, während die Methode scraper() den Code enthält, der das tatsächliche Scraping ausführt; in diesem Stadium navigiert sie jedoch lediglich zu einer URL. Fügen Sie folgenden Code hinzu:

      ./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;
      

      Puppeteer hat eine newPage()-Methode, die eine neue Seiteninstanz im Browser erstellt. Diese Seiteninstanzen können einiges erledigen. In unserer scraper()-Methode haben Sie eine Seiteninstanz erstellt und dann die Methode page.goto() verwendet, um zur Homepage books.toscrape.com zu navigieren.

      Speichern und schließen Sie die Datei.

      Die Dateistruktur Ihres Programms ist nun fertig. Die erste Ebene der Verzeichnisstruktur Ihres Projekts wird wie folgt aussehen:

      Output

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

      Führen Sie nun den Befehl npm run start aus und sehen Sie, wie Ihre Scraper-Anwendung ausgeführt wird:

      Dadurch wird automatisch eine Chromium-Browserinstanz geöffnet, im Browser eine neue Seite geöffnet und zu books.toscrape.com navigiert.

      In diesem Schritt haben Sie eine Puppeteer-Anwendung erstellt, die Chromium geöffnet und die Homepage für einen Dummy-Online-Buchladen (books.toscrape.com) geladen hat. Im nächsten Schritt werden Sie die Daten für jedes einzelne Buch auf dieser Homepage auslesen.

      Schritt 3 — Auslesen von Daten von einer einzelnen Seite

      Bevor Sie Ihrer Scraper-Anwendung mehr Funktionen hinzufügen, öffnen Sie Ihren bevorzugten Webbrowser und navigieren Sie manuell zur Books-to-scrape-Homepage. Durchsuchen Sie die Website und finden Sie heraus, wie die Daten strukturiert sind.

      Bild der Books-to-scrape-Websites

      Sie finden links einen Kategorienabschnitt und rechts Bücher. Wenn Sie auf ein Buch klicken, navigiert der Browser zu einer neuen URL, die relevante Informationen zu diesem bestimmten Buch anzeigt.

      In diesem Schritt werden Sie dieses Verhalten replizieren, aber mit Code; Sie werden das Navigieren der Website automatisieren und deren Daten konsumieren.

      Wenn Sie zunächst mithilfe der Dev-Tools in Ihrem Browser den Quellcode für die Homepage prüfen, werden Sie feststellen, dass die Seite Daten der einzelnen Bücher unter einem section Tag auflistet. Im Inneren des section-Tags befindet sich jedes Buch unter einem list (li)-Tag; hier finden Sie den Link zur Seite des jeweiligen Buchs, den Preis und die Verfügbarkeit.

      books.toscrape-Quellcode, in dev-Tools angezeigt

      Sie werden diese Buch-URLs auslesen, nach vorrätigen Büchern filtern, zur Seite des jeweiligen Buchs navigieren und Daten des Buchs auslesen.

      Öffnen Sie erneut Ihre pageScraper.js-Datei:

      Fügen Sie den folgenden hervorgehobenen Inhalt hinzu: Sie werden einen weiteren await-Block in der Datei await page.goto(this.url); schachteln:

      ./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;
      
      

      In diesem Codeblock haben Sie die Methode page.waitForSelector() aufgerufen. Diese hat , bis div alle buchbezogenen Daten enthält, die im DOM dargestellt werden sollen; dann haben Sie die Methode page.$$eval() aufgerufen. Diese Methode ruft das URL-Element mit dem Selektor section ol li ab (Sie sollten sicherstellen, dass Sie aus den Methoden page.$$eval() und page.$eval() immer nur eine Zeichenfolge oder Zahl zurückgeben.

      Jedes Buch verfügt über zwei Status; ein Buch ist entweder In Stock (Vorrätig) oder Out of stock (Nicht vorrätig). Sie möchten nur Bücher auslesen, die In Stock sind. Da page.$$eval() ein Array aller übereinstimmenden Elemente zurückgibt, haben Sie dieses Array gefiltert, um sicherzustellen, dass Sie nur mit vorrätigen Büchern arbeiten. Sie haben dazu die Klasse .instock.availability gesucht und ausgewertet. Sie haben dann die Eigenschaft href der Buchlinks zugeordnet und aus der Methode zurückgegeben.

      Speichern und schließen Sie die Datei.

      Führen Sie Ihre Anwendung neu aus:

      Der Browser öffnet sich, navigiert zur Webseite und schließt nach Abschluss der Aufgabe. Überprüfen Sie nun Ihre Konsole; sie enthält alle ausgelesenen URLs:

      Output

      > [email protected] 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' ]

      Das ist ein guter Anfang; Sie möchten jedoch alle relevanten Daten für ein bestimmtes Buch und nicht nur dessen URL auslesen. Sie werden nun diese URLs verwenden, um die einzelnen Seiten zu öffnen und Titel, Autor, Preis, Verfügbarkeit, UPC, Beschreibung und Bild-URL auszulesen.

      Öffnen Sie pageScraper.js neu:

      Fügen Sie den folgenden Code hinzu, der die einzelnen ausgelesenen Links durchläuft, eine neue Seiteninstanz öffnen und dann die entsprechenden Daten abruft:

      ./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;
      

      Sie verfügen über ein Array mit allen URLs. Sie möchten dieses Array durchlaufen, die URL in einer neuen Seite öffnen, Daten auf dieser Seite auslesen, diese Seite schließen und eine neue Seite für die nächste URL im Array öffnen. Beachten Sie, dass Sie diesen Code in einer Zusage eingeschlossen haben. Das liegt daran, dass Sie warten möchten, bis jede Aktion in Ihrer Schleife abgeschlossen ist. Daher wird jede Zusage eine neue URL öffnen und erst aufgelöst, wenn das Programm alle Daten in der URL ausgelesen hat und die Seiteninstanz geschlossen wurde.

      Achtung: Beachten Sie, dass Sie mit einer for-in-Schleife auf die Zusage gewartet haben. Jede andere Schleife wird ausreichen; vermeiden Sie jedoch, mithilfe einer array-iteration-Methode wie forEach oder einer anderen Methode, die eine Callback-Funktion verwendet, über Ihre URL-Arrays zu iterieren. Die Callback-Funktion muss nämlich zunächst die Callback-Warteschlange und die Ereignisschleife durchlaufen; daher werden mehrere Seiteninstanzen auf einmal geöffnet. Dadurch wird Ihr Arbeitsspeicher deutlich stärker belastet.

      Werfen Sie einen genaueren Blick auf Ihre pagePromise-Funktion. Ihr Scraper hat zunächst für jede URL eine neue Seite erstellt und dann die Funktion page.$eval() verwendet, um Selektoren für relevante Details auszuwählen, die Sie auf der neuen Seite auslesen möchten. Einige der Texte enthalten Leerzeichen, Tabs, Zeilenumbrüche und andere nicht-alphanumerische Zeichen, die Sie mit einem regulären Ausdruck verworfen haben. Sie haben dann den Wert für jedes Datenelement, das in dieser Seite ausgelesen wurde, einem Objekt angefügt und dieses Objekt aufgelöst.

      Speichern und schließen Sie die Datei.

      Führen Sie das Skript erneut aus:

      Der Browser öffnet die Homepage, öffnet dann jede Buchseite und protokolliert die ausgelesenen Daten der einzelnen Seiten. Diese Ausgabe wird in Ihrer Konsole ausgedruckt:

      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' } ...

      In diesem Schritt haben Sie für jedes Buch auf der Homepage von books.toscrape.com relevante Daten ausgelesen; Sie können jedoch noch viel mehr Funktionen hinzufügen. Jede Seite mit Büchern zum Beispiel ist paginiert; wie erhalten Sie Bücher von diesen anderen Seiten? Außerdem haben Sie auf der linken Seite der Website Buchkategorien gefunden; was ist, wenn Sie gar nicht alle Bücher sehen möchten, sondern lediglich Bücher aus einem bestimmten Genre? Sie werden nun die entsprechenden Funktionen hinzufügen.

      Schritt 4 — Auslesen von Daten von verschiedenen Seiten

      Seiten auf books.toscrape.com, die paginiert sind, verfügen unter ihrem Inhalt über eine next-Schaltfläche; Seiten, die nicht paginiert sind, haben das nicht.

      Sie werden anhand der Anwesenheit dieser Schaltfläche erkennen, ob eine Seite paginiert ist oder nicht. Da die Daten auf jeder Seite dieselbe Struktur haben und das gleiche Markup aufweisen, müssen Sie nicht für jede mögliche Seite einen eigenen Scraper schreiben. Vielmehr werden Sie die Praxis der Rekursion nutzen.

      Zuerst müssen Sie die Struktur Ihres Codes etwas ändern, um eine rekursive Navigation zu mehreren Seiten zu ermöglichen.

      Öffnen Sie pagescraper.js erneut:

      Sie werden eine neue Funktion namens scrapeCurrentPage() zu Ihrer scraper()-Methode hinzufügen. Diese Funktion wird den gesamten Code enthalten, der Daten von einer bestimmten Seite ausliest, und dann auf die next-Schaltfläche klicken (so vorhanden). Fügen Sie den folgenden hervorgehobenen Code hinzu:

      ./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;
      
      

      Sie setzen die nextButtonExist-Variable zunächst auf false und überprüfen dann, ob die Schaltfläche vorhanden ist. Wenn die next-Schaltfläche vorhanden ist, haben Sie nextButtonExists auf true gesetzt; klicken Sie dann auf die next-Schaltfläche und rufen diese Funktion rekursiv auf.

      Wenn nextButtonExists false ist, wird wie gewöhnlich das Array scrapedData zurückgegeben.

      Speichern und schließen Sie die Datei.

      Führen Sie Ihr Skript erneut aus:

      Das kann eine Weile dauern; Ihre Anwendung liest nun schließlich Daten von über 800 Büchern aus. Sie können entweder den Browser schließen oder Strg+C drücken, um den Prozess zu beenden.

      Sie haben nun die Funktionen Ihres Scrapers maximiert, dabei aber ein neues Problem geschaffen. Jetzt besteht das Problem nicht aus zu wenig Daten, sondern aus zu viel Daten. Im nächsten Schritt werden Sie Ihre Anwendung so anpassen, dass Ihr Scraping nach Buchkategorien gefiltert wird.

      Schritt 5 — Auslesen von Daten nach Kategorie

      Um Daten nach Kategorien auszulesen, müssen Sie sowohl Ihre pageScraper.js – als auch Ihre pageController.js-Datei ändern.

      Öffnen Sie pageController.js in einem Texteditor:

      nano pageController.js
      

      Rufen Sie den Scraper so auf, dass nur Reisebücher ausgelesen werden. Fügen Sie folgenden Code hinzu:

      ./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)
      

      Sie übergeben nun zwei Parameter in Ihre pageScraper.scraper()-Methode, wobei der zweite Parameter die Kategorie von Büchern ist, die Sie auslesen möchten; in diesem Beispiel: Travel. Doch erkennt Ihre pageScraper.js-Datei diesen Parameter noch nicht. Sie müssen auch diese Datei anpassen.

      Speichern und schließen Sie die Datei.

      Öffnen Sie pageScraper.js:

      Fügen Sie den folgenden Code hinzu, der Ihren Kategorieparameter hinzufügen wird, navigieren zu dieser Kategorieseite und dann lesen Sie dann die paginierten Ergebnisse aus:

      ./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;
      

      Dieser Codeblock verwendet die von Ihnen übergebene Kategorie, um die URL zu erhalten, in der sich die Bücher dieser Kategorie befinden.

      Die page.$$eval()-Methode kann Argumente übernehmen, indem das Argument als dritter Parameter an die $$eval()-Methode übergeben und so als dritter Parameter im Callback definiert wird:

      example page.$$eval() function

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

      Genau das haben Sie in Ihrem Code getan; Sie haben die Kategorie von Büchern übergeben, die Sie auslesen möchten, sind alle Kategorien durchlaufen um zu überprüfen, welche übereinstimmt, und dann die URL dieser Kategorie zurückgegeben.

      Diese URL wird dann verwendet, um zu der Seite zu navigieren, die die Kategorie von Büchern anzeigt, die Sie mithilfe der Methode page.goto(selectedCategory) auslesen möchten.

      Speichern und schließen Sie die Datei.

      Führen Sie Ihre Anwendung erneut aus. Sie werden feststellen, dass sie zur Kategorie Travel navigiert, Bücher in dieser Kategorieseite nach Seite rekursiv öffnet und die Ergebnisse protokolliert:

      In diesem Schritt haben Sie Daten über mehrere Seiten hinweg ausgelesen und dann Daten aus einer bestimmten Kategorie über mehrere Seiten hinweg ausgelesen. Im letzten Schritt werden Sie Ihr Skript so ändern, dass Daten über mehrere Kategorien hinweg ausgelesen und dann in einer Zeichenfolgen-förmigen JSON-Datei gespeichert werden.

      Schritt 6 — Auslesen von Daten aus verschiedenen Kategorien und Speichern der Daten als JSON

      In diesem letzten Schritt werden Sie dafür sorgen, dass Ihr Skript Daten aus so vielen Kategorien abliest, wie Sie möchten, und dann die Art der Ausgabe ändern. Anstatt die Ergebnisse zu protokollieren, speichern Sie sie in einer strukturierten Datei namens data.json.

      Sie können schnell mehr Kategorien zum Auslesen hinzufügen; dazu ist nur eine zusätzliche Zeile pro Genre erforderlich.

      Öffnen Sie pageController.js:

      Passen Sie Ihren Code so an, dass zusätzliche Kategorien enthalten sind. Das folgende Beispiel fügt HistoricalFiction und Mystery zu unserer vorhandenen Travel-Kategorie hinzu:

      ./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)
      

      Speichern und schließen Sie die Datei.

      Führen Sie das Skript erneut aus und sehen Sie, wie Daten für alle drei Kategorien ausgelesen werden:

      Da der Scraper nun voll funktional ist, besteht Ihr letzter Schritt darin, Ihre Daten in einem nützlicheren Format zu speichern. Sie werden sie jetzt in einer JSON-Datei mit dem fs-Modul in Node.js speichern.

      Öffnen Sie zunächst pageController.js neu:

      Fügen Sie den folgenden hervorgehobenen Code hinzu:

      ./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)
      

      Zuerst fordern Sie das fs-Modul von Node.js in pageController.js an. Dadurch wird sichergestellt, dass Sie Ihre Daten als JSON-Datei speichern können. Dann fügen Sie Code hinzu, damit das Programm eine neue Datei namens data.json erstellt, sobald das Scraping abgeschlossen ist und der Browser schließt. Beachten Sie, dass der Inhalt von data.json Zeichenfolgen-förmiges JSON ist. Wenn Sie den Inhalt von data.json lesen, analysieren Sie ihn also immer als JSON, bevor Sie die Daten erneut verwenden.

      Speichern und schließen Sie die Datei.

      Sie haben nun eine Web-Scraping-Anwendung erstellt, die Bücher über verschiedene Kategorien hinweg ausliest und die ausgelesenen Daten dann in einer JSON-Datei speichert. Wenn Ihre Anwendung komplexer wird, möchten Sie die ausgelesenen Daten möglicherweise in einer Datenbank speichern oder über eine API bereitstellen. Wie diese Daten verbraucht werden, liegt ganz bei Ihnen.

      Zusammenfassung

      In diesem Tutorial haben Sie einen Web Crawler erstellt, der Daten über mehrere Seiten rekursiv ausliest und dann in einer JSON-Datei speichert. Kurz gesagt: Sie haben eine neue Methode erlernt, um die Datenerfassung von Websites zu automatisieren.

      Puppeteer hat eine Menge von Funktionen, die im Rahmen dieses Tutorials nicht abgedeckt wurden. Um mehr darüber zu erfahren, lesen Sie Verwenden von Puppeteer für einfache Kontrolle über Headless Chrome. Sie können auch die offizielle Dokumentation von Puppeteer besuchen.



      Source link

      Cómo extraer datos de un sitio web utilizando Node.js y Puppeteer


      El autor seleccionó Free and Open Source Fund para recibir una donación como parte del programa Write for DOnations.

      Introducción

      La extracción de datos de la web (web scraping) es un proceso que automatiza la recopilación de datos de Internet. En general, conlleva la implementación de un rastreador (crawler) que navega por la web de forma automática y extrae datos de páginas seleccionadas. Hay muchos motivos por los cuales se puede querer utilizar este proceso. El principal es que agiliza la recopilación en gran medida al eliminar el proceso manual de obtención de datos. La extracción de datos también es una buena solución cuando se quieren o deben obtener datos de un sitio web, pero este no proporciona una API.

      En este tutorial, creará una aplicación de extracción de datos web utilizando Node.js y Puppeteer. La complejidad de su aplicación irá aumentando a medida que avance. Primero, programará su aplicación para que abra Chromium y cargue un sitio web especial diseñado como espacio aislado para la extracción de datos web: books.toscrape.com. En los dos pasos siguientes, extraerá todos los libros de una sola página de books.toscrape y, luego, de varias. En los pasos restantes, filtrará la extracción por categoría de libros y, a continuación, guardará sus datos como archivo JSON.

      Advertencia: Los aspectos éticos y legales de la extracción de datos de la web son muy complejos y están en constante evolución. También difieren según la ubicación desde la que se realice, la ubicación de los datos y el sitio web en cuestión. En este tutorial, se extraen datos de un sitio web especial, books.toscrape.com, que está diseñado específicamente para probar aplicaciones de extracción de datos. La extracción de cualquier otro dominio queda fuera del alcance de este tutorial.

      Requisitos previos

      Con Node.js instalado, puede comenzar a configurar su extractor de datos web. Primero, creará un directorio root de un proyecto y, a continuación, instalará las dependencias requeridas. Este tutorial requiere una sola dependencia que instalará utilizando el administrador de paquetes predeterminado de Node.js npm. npm viene previamente instalado con Node.js, por lo tanto, no es necesario instalarlo.

      Cree una carpeta para este proyecto y posiciónese en ella:

      • mkdir book-scraper
      • cd book-scraper

      Ejecutará todos los comandos subsiguientes desde este directorio.

      Debemos instalar un paquete utilizando npm o el administrador de paquetes de Node. Primero, inicialice npm para crear un archivo packages.json que gestionará las dependencias y los metadatos de su proyecto.

      Inicialice npm para su proyecto:

      npm presentará una secuencia de solicitudes. Puede presionar ENTER en cada una de ellas o añadir descripciones personalizadas. Asegúrese de presionar ENTER y dejar los valores predeterminados en las solicitudes de entrypoint: y test command:. Alternativamente, puede pasar el indicador y a npm, npm init -y, para que complete todos los valores predeterminados de forma automática.

      El resultado tendrá un aspecto similar a 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

      Escriba yes y presione ENTER. npm guardará este resultado como su archivo package.json.

      Ahora, utilice npm para instalar Puppeteer:

      • npm install --save puppeteer

      Este comando instala Puppeteer y una versión de Chromium que el equipo de Puppeteer sabe que funciona con su API.

      En equipos basados en Linux, Puppeteer podría requerir algunas dependencias adicionales.

      Si está utilizando Ubuntu 18.04, consulte el menú desplegable ‘Debian Dependencies’ (Dependencias de Debian) de la sección ‘Chrome headless doesn’t launch on UNIX’ (Chrome desatendido no se inicia en UNIX) en los documentos de resolución de problemas de Puppeteer. Puede utilizar el siguiente comando como ayuda para encontrar dependencias faltantes:

      Ahora que tiene npm, Puppeteer y todas las dependencias adicionales instaladas, su archivo package.json requiere un último ajuste para que pueda comenzar a programar. En este tutorial, iniciará su aplicación desde la línea de comandos con npm run start. Debe añadir cierta información sobre esta secuencia de comandos start en el archivo package.json. Específicamente, debe añadir una línea en la directiva scripts relativa a su comando start.

      Abra el archivo en su editor de texto preferido:

      Busque la sección scripts: y añada las siguientes configuraciones. Recuerde colocar una coma al final de la línea test de la secuencia de comandos; de lo contrario, su archivo no se analizará correctamente.

      Output

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

      También notará que, ahora, aparece puppeteer debajo de dependencies casi al final del archivo. Su archivo package.json no requerirá más ajustes. Guarde sus cambios y cierre el editor.

      Con esto, está listo para comenzar a programar su extractor de datos. En el siguiente paso, configurará una instancia de navegador y probará la funcionalidad básica de su extractor de datos.

      Paso 2: Configurar una instancia de navegador

      Al abrir un navegador tradicional, entre otras cosas, puede hacer clic en botones, navegar con el mouse, escribir y abrir herramientas de desarrollo. Los navegadores desatendidos como Chromium permiten realizar estas mismas tareas, pero mediante programación y sin interfaz de usuario. En este paso, configurará la instancia de navegador de su extractor de datos. Cuando inicie su aplicación, esta abrirá Chromium y se dirigirá a books.toscrape.com de forma automática. Estas acciones iniciales constituirán la base de su programa.

      Su extractor de datos web requerirá cuatro archivos .js: browser.js, index.js, pageController.js y pageScraper.js. En este paso, creará estos cuatro archivos y los actualizará de forma continua a medida que su programa se vuelva más sofisticado. Comience con browser.js; este archivo contendrá la secuencia de comandos que inicia su navegador.

      Desde el directorio root de su proyecto, cree y abra browser.js en un editor de texto:

      Primero, solicitará Puppeteer con require y creará una función async denominada startBrowser(). Esta función iniciará el navegador y devolverá una instancia de él. Añada el siguiente 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
      };
      

      Puppeteer cuenta con un método .launch() que inicia una instancia de un navegador. Este método devuelve una promesa que debe asegurarse de que se resuelva utilizando un bloque .then o await.

      Utiliza await para asegurarse de que la promesa se resuelva, envuelve esta instancia en un bloque de código try-catch y, luego, devuelve una instancia del navegador.

      Tenga en cuenta que el método .launch() toma un parámetro JSON con varios valores:

      • headless: con el valor false, el navegador se ejecuta con una interfaz para que pueda ver la ejecución de su secuencia de comandos y, con true, se ejecuta en modo desatendido. Tenga en cuenta que si desea implementar su extractor de datos en la nube, debe establecer headless en true. La mayoría de los equipos virtuales se ejecutan en modo desatendido y no incluyen interfaz de usuario, por tanto, solo pueden ejecutar el navegador en ese modo. Puppeteer también incluye un modo headful, pero debe utilizarse únicamente para fines de prueba.
      • ignoreHTTPSErrors: con el valor true, le permite visitar sitios web que no están alojados con un protocolo HTTPS seguro e ignorar cualquier error relacionado con HTTPS.

      Guarde y cierre el archivo.

      Ahora, cree su segundo archivo .js, index.js:

      Aquí, utilizará require para solicitar browser.js y pageController.js. Luego, invocará la función startBrowser() y pasará la instancia de navegador creada a nuestro controlador de página, que dirigirá sus acciones. Añada el siguiente código:

      ./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)
      

      Guarde y cierre el archivo.

      Cree su tercer archivo .js, pageController.js:

      pageController.js controla el proceso de extracción de datos. Utiliza la instancia de navegador para controlar el archivo pageScraper.js, que es donde se ejecutan todas las secuencias de comandos de extracción de datos. Más adelante, lo utilizará para especificar de qué categoría de libros desea extraer datos. Sin embargo, por ahora, solo desea asegurarse de que puede abrir Chromium y navegar a una página 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)
      

      Este código exporta una función que toma la instancia de navegador y la pasa a otra función denominada scrapeAll(). Esta función, a su vez, pasa la instancia como argumento a pageScraper.scraper(), que la utiliza para extraer datos de las páginas.

      Guarde y cierre el archivo.

      Por último, cree su cuarto archivo .js, pageScraper.js:

      Aquí, creará un literal de objeto con una propiedad url y un método scraper(). La propiedad url es la URL de la página web de la que desea extraer datos y el método scraper() contiene el código que realizará la extracción en sí, aunque, en este punto, simplemente navega a una URL. Añada el siguiente código:

      ./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;
      

      Puppeteer cuenta con un método newPage() que crea una instancia de página nueva en el navegador. Se pueden hacer varias cosas con las instancias de página. En nuestro método scraper(), creó una instancia de página y, luego, utilizó el método page.goto() para navegar a la página de inicio de books.toscrape.com.

      Guarde y cierre el archivo.

      Con esto, completó la estructura de archivos de su programa. El primer nivel del árbol de directorios de su proyecto tendrá este aspecto:

      Output

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

      Ahora, ejecute el comando npm run start y observe la ejecución de su aplicación de extracción de datos:

      Abrirá una instancia de Chromium y una página nueva en el navegador, y se dirigirá a books.toscrape.com de forma automática.

      En este paso, creó una aplicación de Puppeteer que abre Chromium y carga la página de inicio de una librería en línea ficticia: books.toscrape.com. En el siguiente paso, extraerá datos de todos los libros de esa página de inicio.

      Antes de añadir más funcionalidades a su aplicación de extracción de datos, abra su navegador web preferido y diríjase de forma manual a la página de inicio de Books to Scrape. Navegue por el sitio para comprender cómo están estructurados los datos.

      Imagen del sitio web de Books to Scrape

      Verá una sección de categorías a la izquierda y los libros exhibidos a la derecha. Al hacer clic en un libro, el navegador se dirige a una nueva URL que muestra información pertinente sobre ese libro en particular.

      En este paso, replicará este comportamiento, pero mediante programación: automatizará la tarea de navegar por el sitio web y consumir sus datos.

      Primero, si analiza el código fuente de la página de inicio utilizando las herramienta de desarrollo de su navegador, notará que la página enumera los datos de cada libro debajo de una etiqueta section. Dentro de la etiqueta section, cada libro está debajo de una etiqueta list (li), y es aquí donde se encuentra el enlace a la página separada del libro, su precio y su disponibilidad.

      Vista del código fuente de books.to.scrape con herramientas de desarrollo

      Extraerá datos de estas URL de los libros, filtrará los libros que están disponibles y navegará a la página separada de cada libro para extraer sus datos.

      Vuelva a abrir su archivo pageScraper.js:

      Añada el siguiente contenido resaltado: Anidará otro bloque de 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;
      
      

      En este bloque de código, invocó el método page.waitForSelector(). Con esto, esperó el div que contiene toda la información relacionada con el libro que se debe reproducir en el DOM y, luego, invocó el método page.$$eval(). Este método obtiene el elemento de URL con el selector section ol li (asegúrese de que siempre se devuelva solo una cadena o un número con los métodos page.$eval() y page.$$eval()).

      Cada libro puede tener dos estados: disponible, In Stock, o no disponible, Out of stock. Solo desea extraer datos de los libros In Stock. Como page.$$eval() devuelve una matriz de todos los elementos coincidentes, la filtró para asegurarse de estar trabajando únicamente con los libros que están disponibles. Para hacerlo, buscó y evaluó la clase .instock.availability. Luego, identificó la propiedad href de los enlaces del libro y la devolvió del método.

      Guarde y cierre el archivo.

      Vuelva a ejecutar su aplicación:

      El navegador se abrirá, se dirigirá a la página web y se cerrará cuando la tarea se complete. Ahora, observe su consola; contendrá todas las URL extraídas:

      Output

      > [email protected] 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' ]

      Es un gran comienzo, pero desea extraer todos los datos pertinentes de un libro determinado, no solo su URL. Ahora, utilizará estas URL para abrir cada página y extraer el título, el autor, el precio, la disponibilidad, el UPC, la descripción y la URL de la imagen de cada libro.

      Vuelva a abrir pageScraper.js:

      Añada el siguiente código, que recorrerá en bucle cada uno de los enlaces extraídos, abrirá una instancia de página nueva y, luego, obtendrá los datos pertinentes:

      ./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;
      

      Tiene una matriz de todas las URL. Lo que desea hacer es recorrer en bucle esta matriz, abrir una URL en una página nueva, extraer los datos de esa página, cerrarla y abrir una nueva para la siguiente URL de la matriz. Tenga en cuenta que envolvió este código en una promesa. Esto se debe a que desea esperar a que se complete cada acción de su bucle. Por lo tanto, cada promesa abre una URL nueva y no se resuelve hasta que el programa haya extraído todos sus datos y la instancia de esa página se haya cerrado.

      Advertencia: Tenga en cuenta que esperó la promesa utilizando un bucle for-in. Puede utilizar cualquier otro bucle, pero evite recorrer en iteración sus matrices de URL con métodos de iteración de matrices, como forEach, o cualquier otro método que utilice una función de devolución de llamada. Esto se debe a que la función de devolución de llamada debe pasar, primero, por la cola de devolución de llamadas y el bucle de evento, por lo tanto, se abrirán varias instancias de la página a la vez. Esto consumirá mucha más memoria.

      Observe con mayor detenimiento su función pagePromise. Primero, su extractor creó una página nueva para cada URL y, luego, utilizó la función page.$eval() para apuntar los selectores a los detalles pertinentes que deseaba extraer de la página nueva. Algunos de los textos contienen espacios en blanco, pestañas, líneas nuevas y otros caracteres no alfanuméricos, que eliminó utilizando una expresión regular. Luego, anexó el valor de cada dato extraído de esta página a un objeto y resolvió ese objeto.

      Guarde y cierre el archivo.

      Vuelva a ejecutar la secuencia de comandos:

      El navegador abre la página de inicio y, luego, abre la página de cada libro y registra los datos extraídos de cada una de ellas. Se imprimirá este resultado en su consola:

      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' } ...

      En este paso, extrajo datos pertinentes de cada libro de la página de inicio de books.toscrape.com, pero podría añadir muchas funcionalidades más. Por ejemplo, todas las páginas de libros están paginadas; ¿cómo puede obtener libros de estas otras páginas? Además, observó que hay categorías de libros en el lado izquierdo del sitio web; ¿y si no desea extraer todos los libros, sino solo los libros de un género en particular? Ahora, añadirá estas funciones.

      Las páginas de books.toscrape.com que están paginadas tienen un botón next debajo de su contenido; las páginas que no lo están, no.

      Utilizará la presencia de este botón para determinar si una página está paginada o no. Como los datos de todas las páginas tienen la misma estructura y marcación, no escribirá un extractor para cada página posible. En su lugar, utilizará la técnica de recursión.

      Primero, debe realizar algunos cambios en la estructura de su código para admitir la navegación recursiva a varias páginas.

      Vuelva a abrir pagescraper.js:

      Agregará una nueva función denominada scrapeCurrentPage() a su método scraper(). Esta función contendrá todo el código que extrae datos de una página en particular y, luego, hará clic en el botón “siguiente”, si está presente. Añada el siguiente código resaltado:

      ./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;
      
      

      Primero, configure la variable nextButtonExist en false y, luego, compruebe si el botón aparece. Si el botón next está presente, configure nextButtonExists en true y proceda a hacer clic en él“; luego, se invoca esta función de forma recursiva.

      Cuando nextButtonExists tiene un valor false, devuelve la matriz de scrapedData como de costumbre.

      Guarde y cierre el archivo.

      Vuelva a ejecutar su secuencia de comandos:

      Esto puede tomar un tiempo; después de todo, su aplicación está extrayendo datos de más de 800 libros. Puede cerrar el navegador o presionar CTRL + C para cancelar el proceso.

      Ha maximizado las capacidades de su extractor de datos, pero, en el proceso, generó un problema. Ahora, el problema no es tener pocos datos, sino demasiados. En el siguiente paso, ajustará su aplicación para filtrar la extracción de datos por categoría de libros.

      Para extraer datos por categoría, deberá modificar sus archivos pageScraper.js y pageController.js.

      Abra pageController.js en un editor de texto:

      nano pageController.js
      

      Invoque el extractor de modo que solo extraiga libros de viajes. Añada el siguiente código:

      ./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)
      

      Ahora, está pasando dos parámetros a su método pageScraper.scraper(); el segundo es la categoría de libros de la que desea extraer datos, que, en este ejemplo, es Travel. Pero su archivo pageScraper.js todavía no reconoce este parámetro. También deberá ajustar este archivo.

      Guarde y cierre el archivo.

      Abra pageScraper.js:

      Agregue el siguiente código, que añadirá su parámetro de categoría, navegará a esa página de categoría y, luego, comenzará a extraer datos de los 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;
      

      Este bloque de código utiliza la categoría que pasó para obtener la URL en la que se encuentran los libros de esa categoría.

      El método page.$$eval() puede tomar argumentos al pasarlos como tercer parámetro al método $$eval() y definirlos como tales en la devolución de llamada de la siguiente manera:

      example page.$$eval() function

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

      Esto es lo que hizo en su código: pasó la categoría de libros de la que quería extraer datos, marcó todas las categorías para determinar cuál coincidía y, luego, devolvió la URL de esa categoría.

      Luego, utilizó esa URL para navegar a la página que muestra la categoría de libros de la que desea extraer datos utilizando el método page.goto(selectedCategory).

      Guarde y cierre el archivo.

      Vuelva a ejecutar su aplicación. Observará que navega a la categoría Travel, abre los libros de esa categoría de forma recursiva, página por página, y registra los resultados:

      En este paso, extrajo datos de varias páginas y, luego, de varias páginas de una categoría en particular. En el paso final, modificará su secuencia de comandos para extraer datos de varias categorías y, luego, guardará los datos extraídos en un archivo JSON convertido a cadena JSON.

      En este paso final, modificará su secuencia de comandos para extraer datos de todas las categorías que desee y, luego, cambiará la forma de su resultado. En lugar de registrar los resultados, los guardará en un archivo estructurado denominado data.json.

      Puede añadir más categorías para extraer de datos de forma rápida. Para hacerlo, solo se requiere una línea adicional por género.

      Abra pageController.js:

      Ajuste su código para incluir categorías adicionales. En el ejemplo a continuación, se suman HistoricalFiction y Mystery a nuestra categoría 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)
      

      Guarde y cierre el archivo.

      Vuelva a ejecutar la secuencia de comandos y observe cómo extrae datos de las tres categorías:

      Ahora que el extractor está totalmente operativo, su paso final es guardar los datos en un formato más útil. Ahora, los almacenará en un archivo JSON utilizando el módulo fs de Node.js.

      Primero, vuelva a abrir pageController.js:

      Añada el siguiente código resaltado:

      ./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)
      

      Primero, solicita el módulo fs de Node.js en pageController.js. Esto garantiza que pueda guardar sus datos como archivo JSON. Luego, añade código para que, cuando se complete la extracción de datos y se cierre el navegador, el programa cree un archivo nuevo denominado data.json. Tenga en cuenta que el contenido de data.json está convertido a cadena JSON. Por tanto, al leer el contenido de data.json, siempre se lo debe analizar como JSON antes de volver a utilizar los datos.

      Guarde y cierre el archivo.

      Con esto, ha creado una aplicación de extracción de datos web que extrae libros de varias categorías y, luego, almacena los datos extraídos en un archivo JSON. A medida que su aplicación se vaya volviendo más compleja, puede resultarle conveniente almacenar los datos extraídos en una base de datos o proporcionarlos a través de una API. La manera en la que se consumen estos datos es una decisión personal.

      Conclusión

      En este tutorial, creó un rastreador web que extrajo datos de varias páginas de forma recursiva y los guardó en un archivo JSON. En resumen, aprendió una nueva manera de automatizar la recopilación de datos de sitios web.

      Puppeteer cuenta con muchas características que no se abordaron en este tutorial. Para obtener más información, consulte Cómo usar Puppeteer para controlar Chrome desatendido de forma sencilla. También puede consultar la documentación oficial de Puppeteer.



      Source link

      Comment moissonner un site web en utilisant Node.js et Puppeteer


      L’auteur a choisi le Free and Open Source Fund comme récipiendaire d’un don dans le cadre du programme Write for DOnations.

      Introduction

      Le moissonnage est une technique d’automatisation de la collecte des données depuis le web. Le processus déploie généralement un « collecteur » qui surfe automatiquement le web et moissonne les données des pages sélectionnées. Il existe de nombreuses raisons pour lesquelles vous pouvez vouloir extraire des données. En premier lieu, en éliminant le processus manuel de recueil de données, la collecte des données est beaucoup plus rapide. Vous pouvez également utiliser le moissonnage si vous souhaitez ou avez besoin de collecter des données mais que le site web ne dispose pas d’une API pour le faire.

      Au cours de ce tutoriel, vous allez créer une application de grattage web en utilisant Node.js et Puppeteer. Votre application deviendra de plus en plus complexe à mesure que vous progresserez. Vous allez tout d’abord coder votre application pour ouvrir Chromium et charger un site web spécial conçu comme un bac à sable de moissonnage : books.toscrape.com. Les deux prochaines étapes consisteront à extraire tous les livres sur une seule page (books.toscrape), puis tous les livres qui se trouvent sur plusieurs pages. Au cours des étapes restantes, vous allez filtrer votre moissonnage par catégorie de livres. Vous enregistrerez vos données en tant que fichier JSON.

      Avertissement : le moissonnage est éthiquement et légalement très complexe et en constante évolution. À ce titre, il est également très différent en fonction de votre région, de l’emplacement des données et du site web en question. Ce tutoriel moissonne un site web spécifique, books.toscrape.com, qui a été spécialement conçu pour tester des applications de moissonage. Le fait de remplacer ce domaine par un autre domaine ne relève pas de la portée de ce tutoriel.

      Conditions préalables

      Étape 1 — Configuration de l’application de Web Scraping

      Une fois Node.js installé, vous pouvez commencer à configurer votre application de moissonnage. Tout d’abord, vous allez créer un répertoire root de projet. Ensuite vous installerez les dépendances requises. Ce tutoriel ne nécessite qu’une seule dépendance que vous installerez en utilisant le gestionnaire de paquets npm par défaut de Node.js. npm est préinstallé avec Node.js, il est donc inutile de l’installer.

      Créez un dossier pour ce projet. Ensuite vous pouvez y entrer :

      • mkdir book-scraper
      • cd book-scraper

      Vous exécuterez toutes les commandes ultérieures depuis ce répertoire.

      Nous devons installer un paquet en utilisant npm ou le gestionnaire de paquets de nœuds. Tout d’abord, initialisez npm afin de créer un fichier packages.json qui permettra de gérer les dépendances et les métadonnées de votre projet.

      Initialisez npm pour votre projet :

      npm présentera une série d’invites. Vous pouvez appuyer sur ENTRÉE pour chaque invite ou ajouter des descriptions personnalisées. Veillez à bien appuyer sur ENTRÉE et à laisser les valeurs par défaut lorsqu’on vous y invite pour entry point: et test command:. Sinon, vous pouvez également faire transmettre le drapeau y sur npmnpm init -y—. Cela soumettra toutes les valeurs par défaut.

      Vous obtiendrez un résultat similaire à ceci :

      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

      Tapez yes et appuyez sur ENTER. npm pour enregistrer ce résultat en tant que votre fichier package.json.

      Maintenant, utilisez npm pour installer Puppeteer :

      • npm install --save puppeteer

      Cette commande installe à la fois Puppeteer et une version de Chromium validée par l’équipe de Puppeteer comme compatible avec son API.

      Sur les machines Linux, il se peut que Puppeteer nécessite des dépendances supplémentaires.

      Si vous utilisez Ubuntu 18.04, vérifiez la liste déroulante « Dépendances Debian » de la section « Chrome headless ne se lance pas sur UNIX » des documents de dépannage de Puppeteer. Vous pouvez utiliser la commande suivante pour trouver toutes les dépendances manquantes :

      Une fois npm, Puppeteer et toute dépendance supplémentaire installés, vous devrez procéder à une dernière configuration sur votre fichier package.json avant de commencer le codage. Au cours de ce tutoriel, vous allez lancer votre application depuis la ligne de commande avec  npm run start. Vous devez ajouter des informations sur ce script start dans package.json. Vous devez tout particulièrement ajouter une ligne sous la directive script liée à votre commande start.

      Ouvrez le fichier dans votre éditeur de texte préféré :

      Trouvez la section scripts et ajoutez les configurations suivantes. N’oubliez pas de placer une virgule à la fin de la ligne de script test. Dans le cas contraire, l’analyse de votre fichier ne se fera pas correctement.

      Output

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

      Vous remarquerez également que, maintenant puppeteer apparaît sous dependencies près de la fin du fichier. Il ne sera pas nécessaire d’effectuer de révisions supplémentaires sur votre fichier package.json. Enregistrez vos modifications et fermez votre éditeur.

      Vous êtes maintenant prêt à commencer à coder votre application de moissonnage. Au cours de l’étape suivante, vous allez configurer une instance de navigateur et tester la fonctionnalité de base de votre application de moissonnage.

      Étape 2 — Configuration de l’instance de navigateur

      Lorsque vous ouvrez un navigateur traditionnel, vous pouvez cliquer sur des boutons, naviguer avec votre souris, utiliser votre clavier, ouvrir des outils de développement et bien plus encore. Avec un navigateur headless comme Chromium vous pouvez faire exactement ces mêmes choses, mais par programmation, sans interface utilisateur. Au cours de cette étape, vous allez configurer l’instance de navigateur de votre application de Web Scraping. Au lancement de votre application, elle ouvrira Chromium automatiquement et se rendra sur books.toscrape.com. Ces actions initiales constitueront la base de votre programme.

      Votre application de moissonnage aura besoin de quatre fichiers .js : browser.js, index,js, pageController.js, et pageScraper.js.  Au cours de cette étape, vous allez créer les quatre fichiers. Ensuite, à mesure que votre programme deviendra de plus en plus sophistiqué, vous devrez les mettre continuellement à jour. Commencez par browser.js. Ce fichier contiendra le script qui démarre votre navigateur.

      Depuis le répertoire root de votre projet, créez et ouvrez browser.js dans un éditeur de texte :

      Tout d’abord, vous aurez besoin de Puppeteer. Vous devrez ensuite créer une fonction async appelée startBrowser(). Cette fonction lancera le navigateur et renverra une instance de celui-ci. Ajoutez le code suivant :

      ./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
      };
      

      Puppeteer dispose d’une méthode .launch() qui lance une instance d’un navigateur. Cette méthode renvoie une Promise. Vous devez donc vous assurer que la promesse est résolue en utilisant un bloc .then ou await.

      Utilisez await pour vous assurer que la promesse se résout, en enveloppant cette instance autour d’un bloc de code try-catch, puis en renvoyant une instance du navigateur.

      Notez que la méthode .launch() utilise un paramètre JSON avec plusieurs valeurs :

      • headlessfalse signifie que le navigateur s’exécutera avec une Interface qui vous permet de regarder votre script s’exécuter, tandis que true signifie que le navigateur s’exécutera en mode headless. Notez bien que, si vous souhaitez déployer votre application de moissonnage vers le cloud, reconfigurez headless sur true. La plupart des machines virtuelles sont headless et ne disposent pas d’une interface utilisateur. Elles peuvent donc exécuter le navigateur uniquement en mode headless. Puppeteer comprend également un mode headful mais il doit être uniquement utilisé pour réaliser des tests.
      • ignoreHTTPSErrorstrue vous permet de consulter des sites web qui ne sont pas hébergés sur un protocole HTTPS sécurisé et ignorent les erreurs liées à HTTPS.

      Enregistrez et fermez le fichier.

      Maintenant, créez votre deuxième fichier .js, index.js :

      Ici, vous aurez besoin de browser.js et de pageController.js. Ensuite, appelez la fonction startBrowser() et transmettez l’instance du navigateur créée à notre contrôleur de page, qui dirigera ses actions. Ajoutez le code suivant :

      ./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)
      

      Enregistrez et fermez le fichier.

      Créez votre troisième fichier .js, pageController.js :

      pageController.js contrôle votre processus de moissonnage. Il utilise l’instance du navigateur pour contrôler le fichier pageScraper.js sur lequel tous les scripts de moissonnage s’exécutent. Vous l’utiliserez éventuellement pour spécifier la catégorie de livres que vous souhaitez moissonner. Cependant, pour le moment, votre objectif est de pouvoir seulement ouvrir Chromium et naviguer vers une page 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)
      

      Ce code exporte une fonction qui prend l’instance de navigateur et la transmet à une fonction appelée scrapeAll(). À son tour, cette fonction transmet cette instance à pageScraper.scraper() en tant qu’argument qui l’utilise pour moissonner des pages.

      Enregistrez et fermez le fichier.

      Enfin, créez votre dernier fichier .js, pageScraper.js :

      Ici, vous allez créer un objet littéral avec une propriété url et une méthode scraper(). L’url est l’URL web de la page web que vous souhaitez moissonner. De son côté, la méthode scraper() contient le code qui exécutera votre moissonnage, même si à ce stade, elle se contente de se rendre sur une URL. Ajoutez le code suivant :

      ./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;
      

      Puppeteer dispose d’une méthode newPage() qui crée une nouvelle instance de page dans le navigateur. Ces instances de page peuvent faire plusieurs choses. Avec notre méthode scraper(), vous avez créé une instance de page puis utilisé la méthode page.goto() pour naviguer vers la page d’accueil de books.toscrape.com.

      Enregistrez et fermez le fichier.

      Maintenant, la structure des fichiers de votre programme est achevée. Le premier niveau de l’arborescence de répertoire de votre projet ressemblera à ceci :

      Output

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

      Maintenant, exécutez la commande npm run start et regardez votre application de moissonnage s’exécuter :

      Elle ouvrira automatiquement une instance de navigateur Chromium, ouvrira une nouvelle page dans le navigateur et naviguera vers books.toscrape.com.

      Au cours de cette étape, vous avez créé une application Puppeteer qui a ouvert Chromium et chargé la page d’accueil d’une librairie en ligne factice—books.toscrape.com. Au cours de l’étape suivante, vous allez moissonner les données de chaque livre qui se trouve sur cette page d’accueil.

      Étape 3 — Scraping des données à partir d’une seule page

      Avant d’ajouter plus de fonctionnalités à votre application de moissonnage, ouvrez votre navigateur web préféré et naviguez manuellement à la page d’accueil de books to scrape. Parcourez le site et faites-vous une idée sur la manière dont les données sont structurées.

      Image des sites web de books to scrape

      Vous trouverez une section de catégories à gauche et les livres affichés à droite. Lorsque vous cliquez sur un livre, le navigateur se dirige vers une nouvelle URL qui affiche les informations concernant ce livre spécifique.

      Au cours de cette étape, vous allez reproduire ce comportement, mais avec un code. Vous automatiserez l’activité de navigation sur le site web et la consommation de ses données.

      Tout d’abord, si vous inspectez le code source de la page d’accueil en utilisant les outils de développement de votre navigateur, vous remarquerez que la page répertorie les données de chaque livre sous une balise section. À l’intérieur de la balise section, chaque livre se trouve sous une balise list (li). Vous trouverez ici le lien vers la page dédiée du livre, le prix et le stock disponible.

      vue du code source de books.toscrape dans les outils de développement

      Vous effectuerez le scraping de ces URL de livres, appliquerez un filtre pour voir les livres en stock, naviguerez vers chaque page du livre et en réalisant le scraping de ses données.

      Rouvrez votre fichier pageScraper.js:

      Ajoutez le contenu en surbrillance suivant. Vous imbriquerez un autre bloc await à l’intérieur 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;
      
      

      Dans ce bloc de code, vous avez appelé la méthode page.waitForSelector(). Cette méthode a attendu que le div qui contient toutes les informations liées à des livres soit rendu dans le DOM. Ensuite, vous avez appelé la méthode page.$$eval(). Cette méthode obtient l’élément URL avec le sélecteur section ol li (veillez à toujours renvoyer une chaîne ou un nombre à partir des méthodes page.$eval() et page.$$eval()).

      Chaque livre peut avoir deux statuts, il est soit En stock ou En rupture de stock. Vous allez extraire les livres qui sont En Stock. Étant donné que page.$$eval() renvoie un tableau de tous les éléments correspondants, filtrez ce tableau pour avoir la certitude de bien travailler qu’avec des livres en stock. Pour cela, recherchez et évaluez la catégorie .instock.availability. Ensuite, mappez la propriété href des liens du livre et renvoyez-la à partir de la méthode.

      Enregistrez et fermez le fichier.

      Ré-exécutez votre application :

      Le navigateur s’ouvrira, ira sur la page web, puis se fermera une fois la tâche achevée. Maintenant, vérifiez votre console. Elle contiendra toutes les URL moissonnées :

      Output

      > [email protected] 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' ]

      C’est un excellent début, mais en réalité, vous souhaitez extraire toutes les données pertinentes d’un livre spécifique, pas seulement son URL. Maintenant, utilisez ces URL pour ouvrir chaque page et moissonner le titre, l’auteur, le prix, la disponibilité, l’UPC, la description et l’URL d’image.

      Rouvrez pageScraper.js:

      Ajoutez le code suivant. Il parcourra chaque lien moissonné, ouvrira une nouvelle instance de page, puis récupéra les données pertinentes :

      ./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;
      

      Vous obtenez un tableau contenant toutes les URL. Parcourez ce tableau, ouvrez l’URL dans une nouvelle page, moissonnez les données de cette page, fermez-la et ouvrez une nouvelle page pour la prochaine URL du tableau. Notez que vous avez enveloppé ce code dans une promesse, car vous souhaitez avoir la possibilité d’attendre que chaque action de votre boucle s’achève. Par conséquent, chaque promesse ouvre une nouvelle URL et ne se résoudra pas tant que le programme n’aura pas moissonné toutes les données de l’URL. Ensuite, l’instance de la page se ferme.

      Avertissement : notez que vous avez attendu que la promesse s’exécute en utilisant une boucle for-in. Toute autre boucle sera suffisante. Évitez cependant d’itérer vos tableaux d’URL en utilisant une méthode d’itération de tableau comme forEach ou toute autre méthode qui utilise une fonction de rappel. En effet, la fonction de rappel devra tout d’abord passer par une file d’attente de rappels ainsi qu’une boucle d’événements, ce qui générera l’ouverture simultanée de plusieurs instances de page. Votre mémoire sera alors soumise à une plus forte contrainte.

      Regardez votre fonction pagePromise de plus près. Tout d’abord, votre application de moissonage a créé une nouvelle page pour chaque URL. Ensuite, vous avez utilisé la fonction page.$eval() pour cibler les sélecteurs des détails pertinents que vous avez voulu moissonner sur la nouvelle page. Certains textes contiennent des espaces, des onglets, de nouvelles lignes et d’autres caractères non alphanumériques que vous avez éliminés en utilisant une expression régulière. Ensuite, vous avez annexé la valeur de chaque partie de données moissonnée dans cette page à un objet et résolu l’objet en question.

      Enregistrez et fermez le fichier.

      Exécutez le script à nouveau :

      Le navigateur ouvre la page d’accueil puis chaque page du livre. Il enregistre ensuite les données moissonnées de chacune de ces pages. Le résultat suivant s’affichera sur votre 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' } ...

      Au cours de cette étape, vous avez moissonné les données pertinentes pour chaque livre qui se trouve sur la page d’accueil de books.toscrape.com, mais vous pouvez ajouter beaucoup plus de fonctionnalités. Si, par exemple, chacune des pages des livres est paginée, comment pouvez-vous obtenir des livres depuis ces autres pages ? En outre, sur le côté gauche du site web, vous avez trouvé les catégories de livres. Que faire si vous ne voulez pas de tous les livres, mais seulement ceux d’un genre particulier ? Vous allez maintenant ajouter ces fonctionnalités.

      Étape 4 — Moissonnage de données à partir de plusieurs pages

      Sur les pages paginées de books.toscrape.com, un bouton next apparaît sous leur contenu, tandis que les pages qui ne sont pas paginées n’en ont pas.

      La présence de ce bouton vous permettra de déterminer si la page est paginée ou non. Étant donné que les données de chaque page ont la même structure et le même marquage, vous n’aurez pas à écrire une application de Scraping pour chaque page. Au contraire, vous pourrez utiliser la pratique récurrente.

      Tout d’abord, vous devez quelque peu modifier la structure de votre code pour pouvoir naviguer sur plusieurs pages de manière récurrente.

      Rouvrez pagescraper.js :

      Ajoutez une nouvelle fonction appelée scrapeCurrentPage() à votre méthode scraper(). Cette fonction contiendra l’intégralité du code qui moissonne les données à partir d’une page donnée. Ensuite, cliquez sur le bouton suivant s’il est présent. Ajoutez le code en surbrillance suivant :

      ./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;
      
      

      Initialement, configurez la variable nextButtonExist sur false, ensuite vérifiez si le bouton existe. Si le bouton next existe, configurez nextButtonExists sur true. Vous pouvez ensuite cliquer sur le bouton next et appeler cette fonction de manière récurrente.

      Si nextButtonExists est configuré sur false, il renvoie le tableau scrapedData comme d’habitude.

      Enregistrez et fermez le fichier.

      Exécutez à nouveau votre script :

      Il est possible que cela vous prenne un certain temps. Après tout, votre application est en train d’effectuer le Scraping des données de plus de 800 livres. N’hésitez pas à fermer le navigateur ou à appuyer sur CTRL+C pour annuler le processus.

      Vous avez maintenant maximisé les capacités de votre application de moissonnage, mais vous avez créé un nouveau problème au cours du processus. En effet, plutôt que d’avoir trop peu de données, vous en avez trop. Au cours de l’étape suivante, vous allez affiner votre application afin de filtrer votre moissonnage par catégorie de livres.

      Étape 5 — Moissonnage des données par catégorie

      Pour moissonner des données par catégorie, vous devez modifier à la fois votre fichier pageScraper.js et votre fichier pageController.js.

      Ouvrez pageController.js dans un éditeur de texte :

      nano pageController.js
      

      Appelez l’application de moissonnage afin qu’elle moissonne uniquement les livres de voyage. Ajoutez le code suivant :

      ./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)
      

      Maintenant, vous êtes en train de transmettre deux paramètres dans votre méthode pageScraper.scraper(). Le deuxième paramètre correspond à la catégorie des livres que vous souhaitez moissonner, qui dans cet exemple est Travel. Mais votre fichier pageScraper.js ne reconnaît pas encore ce paramètre. Vous devez également ajuster ce fichier.

      Enregistrez et fermez le fichier.

      Ouvrez pageScraper.js:

      Ajoutez le code suivant. Il ajoutera votre paramètre de catégorie. Naviguez sur cette page de catégories, puis commencez à moissonner vos données à partir des résultats paginés :

      ./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;
      

      Ce bloc de code utilise la catégorie que vous avez transmise pour obtenir l’URL où se trouvent les livres de la catégorie en question.

      Le page.$$eval() peut prendre des arguments en transmettant l’argument en tant que troisième paramètre à la méthode $$eval(), et en le définissant comme le troisième paramètre dans le rappel de la manière suivante :

      example page.$$eval() function

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

      C’est ce que vous avez fait dans votre code. Vous avez passé la catégorie des livres que vous avez voulu moissonner, mappé toutes les catégories pour vérifier celles qui y correspondent et êtes revenu ensuite à l’URL de cette catégorie.

      Cette URL permet ensuite de naviguer vers la page qui affiche la catégorie des livres que vous souhaitez moissonner en utilisant la méthode page.goto(selectedCategory)

      Enregistrez et fermez le fichier.

      Exécutez votre application à nouveau. Vous remarquerez qu’elle se dirige vers la catégorie Travel, ouvre de manière récurrente les livres de cette catégorie, page par page, et journalise les résultats :

      Au cours de cette étape, vous avez moissonné des données sur plusieurs pages, puis celles d’une catégorie particulière. Au cours de l’étape finale, vous allez modifier votre script pour moissonner des données sur plusieurs catégories et enregistrer ensuite ces données moissonnées sur un fichier JSON composé de chaines.

      Étape 6 — Moissonnage des données de plusieurs catégories et sauvegarde des données en tant que JSON

      Au cours de cette dernière étape, vous allez moissonner les données de votre script sur autant de catégories que vous le souhaitez, puis changer votre manière de gérer les résultats. Plutôt que d’enregistrer les résultats, vous allez les sauvegarder dans un fichier structuré appelé data.json.

      Vous pouvez rapidement ajouter plus de catégories à extraire, ce qui nécessite uniquement une ligne supplémentaire par genre.

      Ouvrez pageController.js :

      Ajustez votre code pour inclure des catégories supplémentaires. L’exemple ci-dessous vous permet d’ajouter HistoricalFiction et Mystery à notre catégorie Travel existante :

      ./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)
      

      Enregistrez et fermez le fichier.

      Exécutez le script à nouveau et regardez les données de ces trois catégories être moissonnées :

      Lorsque votre application de moissonnage est entièrement fonctionnelle, la dernière étape consiste à sauvegarder vos données sous un format plus pratique. Vous allez maintenant les stocker dans un fichier JSON en utilisant le module fs dans Node.js.

      Tout d’abord, rouvrez pageController.js :

      Ajoutez le code en surbrillance suivant :

      ./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)
      

      Tout d’abord, vous avez besoin du module fs de Node,js dans pageController.js. Vous pouvez ainsi enregistrer vos données en tant que fichier JSON. Ensuite, ajoutez un code de sorte que, une fois le Scraping achevé et le navigateur fermé, le programme créera un nouveau fichier appelé data.json. Notez que le contenu de data.json est stringified JSON. Par conséquent, lors de la lecture du contenu de data.json, analysez-le toujours en tant que JSON avant de réutiliser les données.

      Enregistrez et fermez le fichier.

      Vous avez maintenant développé une application de moissonnage qui moissonne les livres de plusieurs catégories et stocke ensuite les données que vous avez moissonnées dans un fichier JSON. Lorsque votre application devient plus complexe, vous pouvez stocker ces données moissonnées dans une base de données ou les utiliser avec une une API. Vous êtes ensuite libre d’utiliser ces données de la manière dont vous le souhaitez.

      Conclusion

      Vous avez développé un collecteur qui moissonne des données sur plusieurs pages de manière récurrente, puis les enregistre dans un fichier JSON. En résumé, vous avez appris à utiliser une nouvelle méthode d’automatiser la collecte des données à partir de sites web.

      Puppeteer dispose d’un grand nombre de fonctionnalités qui ne se trouvent pas dans ce tutoriel. Pour en savoir plus, consultez Utiliser Puppeteer pour un meilleur contrôle de Headless Chrome. Vous pouvez également consulter la documentation officielle de Puppeteer.



      Source link