One place for hosting & domains

      Erstellen einer benutzerdefinierten Paginierung mit React


      Einführung

      Wir sind oft an der Erstellung von Webanwendungen beteiligt, bei denen wir große Mengen von Datensätzen von einem Remote-Server, einer API oder einer Datenbank abrufen müssen. Wenn Sie beispielsweise ein Zahlungssystem entwickeln, könnten Tausende von Transaktionen abgerufen werden. Wenn es sich um eine Social Media-Anwendung handelt, könnten viele Benutzerkommentare, -profile oder -aktivitäten abgerufen werden. Was auch immer der Fall sein mag, es gibt verschiedene Lösungen, um Daten so darzustellen, dass der Endbenutzer, der mit der Anwendung interagiert, nicht überfordert wird.

      Eine Methode zur Handhabung großer Datensätze ist die Verwendung der Paginierung. Die Paginierung funktioniert effektiv, wenn Sie die Größe des Datensatzes (die Gesamtzahl der Datensätze im Datensatz) bereits kennen. Zweitens laden Sie nur den erforderlichen Datenblock aus dem Gesamtdatensatz basierend auf der Interaktion des Endbenutzers mit der Paginierungssteuerung. Diese Technik wird bei der Anzeige der Suchergebnisse in der Google-Suche verwendet.

      In diesem Tutorial lernen Sie, wie Sie mit React eine benutzerdefinierte Paginierungskomponente für die Paginierung großer Datensätze erstellen. Sie werden eine paginierte Ansicht der Länder der Welt erstellen – einen Datensatz mit einer bekannten Größe.

      Hier ist eine Demo davon, was Sie in diesem Tutorial erstellen werden:

      Screenshot der Demo-Anwendung — zeigt die Länder der Welt

      Voraussetzungen

      Um diesem Tutorial zu folgen, benötigen Sie:

      • Auf Ihrem Rechner nodejsinstalliertes [Node]. Die Schritte hierfür finden Sie unter Installieren von Node.js und Erstellen einer lokalen Entwicklungsumgebung.
      • Das Befehlszeilen-Paket [create-react-app][`create-react-app] zur Erstellung des Boilerplate-Codes für Ihre React-Anwendung. Wenn Sienpm < 5.2verwenden, müssen Siecreate-react-app` möglicherweise als globale Abhängigkeit installieren.
      • Schließlich geht dieses Tutorial davon aus, dass Sie bereits mit React vertraut sind. Sollte dies nicht der Fall sein, können Sie in der Reihe Codieren in React.js nachlesen, um mehr über React zu erfahren.

      Dieses Tutorial wurde mit Node v14.2.0, nmp v6.14.4, react v16.13.1 und react-scripts v3.4.1 verifiziert.

      Schritt 1 — Einrichten des Projekts

      Starten Sie eine neue React-Anwendung unter Verwendung des Befehls create-react-app. Sie können diese Anwendung beliebig benennen. In diesem Tutorial wird die Anwendung react-pagination genannt:

      • npx create-react-app react-pagination

      Als Nächstes installieren Sie die für Ihre Anwendung erforderlichen Abhängigkeiten. Verwenden Sie zunächst das Terminalfenster zur Navigation zum Projektverzeichnis:

      Führen Sie den folgenden Befehl aus, um die erforderlichen Abhängigkeiten zu installieren:

      • npm install bootstrap@4.1.0 prop-types@15.6.1 react-flags@0.1.13 countries-api@2.0.1 node-sass@4.14.1

      Dadurch werden bootstrap, prop-types, react-flags, countries-api und node-sass installiert.

      Sie haben das Paket bootstrap als Abhängigkeit für Ihre Anwendung installiert, da Sie etwas Standardgestaltung benötigen. Sie verwenden auch Stile aus der pagination-Komponente von Bootstrap verwenden.

      Um Bootstrap in die Anwendung einzubinden, bearbeiten Sie die Datei src/index.js:

      Und fügen Sie die folgende Zeile vor den anderen import-Anweisungen hinzu:

      src/index.js

      import "bootstrap/dist/css/bootstrap.min.css";
      

      Jetzt ist Bootstrap-Styling in Ihrer gesamten Anwendung verfügbar.

      Außerdem haben Sie react-flags als Abhängigkeit für Ihre Anwendung installiert. Um Zugriff auf die Flaggen-Symbole aus Ihrer Anwendung zu erhalten, müssen Sie die Symbolbilder in das Verzeichnis public Ihrer Anwendung kopieren.

      Erstellen Sie in Ihrem Verzeichnis public ein Verzeichnis img:

      Kopieren Sie die Bilddateien in flags zu img:

      • cp -R node_modules/react-flags/vendor/flags public/img

      Damit stellen Sie Ihrer Anwendung eine Kopie aller Bilder von react-flag bereit.

      Nachdem Sie nun einige Abhängigkeiten eingebunden haben, starten Sie die Anwendung, indem Sie den folgenden Befehl mit npm aus dem Projektverzeichnis react-pagination ausführen:

      Nachdem Sie die Anwendung gestartet haben, kann die Entwicklung beginnen. Beachten Sie, dass eine Browser-Registerkarte mit einer Live Neuladefunktionalität für Sie geöffnet wurde, um während der Entwicklung mit der Anwendung synchron zu bleiben.

      Zu diesem Zeitpunkt sollte die Ansicht der Anwendung wie im folgenden Screenshot dargestellt aussehen:

      Anfangsansicht – Willkommen bei React-Bildschirm

      Sie sind nun bereit, mit der Erstellung von Komponenten zu beginnen.

      Schritt 2 — Erstellen der Komponente CountryCard

      In diesem Schritt erstellen Sie die Komponente CountryCard. Die Komponente CountryCard gibt den Namen, die Region und die Flagge eines bestimmten Landes wieder.

      Zuerst erstellen wir ein Verzeichnis components im Verzeichnis src:

      Anschließend erstellen wir eine neue Datei CountryCard.js im Verzeichnis src/works:

      • nano src/components/CountryCard.js

      Und fügen den folgenden Code-Ausschnitt hinzu:

      src/components/CountryCard.js

      import React from 'react';
      import PropTypes from 'prop-types';
      import Flag from 'react-flags';
      
      const CountryCard = props => {
        const {
          cca2: code2 = '', region = null, name = {}
        } = props.country || {};
      
        return (
          <div className="col-sm-6 col-md-4 country-card">
            <div className="country-card-container border-gray rounded border mx-2 my-3 d-flex flex-row align-items-center p-0 bg-light">
              <div className="h-100 position-relative border-gray border-right px-2 bg-white rounded-left">
                <Flag country={code2} format="png" pngSize={64} basePath="./img/flags" className="d-block h-100" />
              </div>
              <div className="px-3">
                <span className="country-name text-dark d-block font-weight-bold">{ name.common }</span>
                <span className="country-region text-secondary text-uppercase">{ region }</span>
              </div>
            </div>
          </div>
        )
      }
      
      CountryCard.propTypes = {
        country: PropTypes.shape({
          cca2: PropTypes.string.isRequired,
          region: PropTypes.string.isRequired,
          name: PropTypes.shape({
            common: PropTypes.string.isRequired
          }).isRequired
        }).isRequired
      };
      
      export default CountryCard;
      

      Die Komponente CountryCard erfordert eine country-Prop, die die Daten über das wiederzugebende Land enthält. Wie in den propTypes für die Komponente CountryCard zu sehen ist, muss das Prop-Objekt country die folgenden Daten enthalten:

      • cca2 – 2-stelliger Ländercode
      • region – die Länderregion (z. B. „Afrika“)
      • name.common – der gebräuchliche Name des Landes (z. B. „Nigeria“)

      Hier ist ein Beispiel für ein Länderobjekt:

      {
        cca2: "NG",
        region: "Africa",
        name: {
          common: "Nigeria"
        }
      }
      

      Beachten Sie auch, wie Sie die Länderflagge mit dem Paket react-flags wiedergeben können. In der Dokumentation zu react-flags erfahren Sie mehr über die benötigten Props und die Verwendung des Pakets.

      Sie haben nun eine einzelne Komponente CountryCard fertiggestellt. Letztendlich werden Sie CountryCards mehrfach verwenden, um verschiedene Flaggen und Länderinformationen in Ihrer Anwendung anzuzeigen.

      In diesem Schritt erstellen Sie die Komponente Pagination. Die Komponente Pagination enthält die Logik für das Erstellen, Rendern und Wechseln der Seiten auf der Paginierungssteuerung.

      Erstellen Sie eine neue Datei Pagination.js im Verzeichnis src/components:

      • nano src/components/Pagination.js

      Und fügen den folgenden Code-Ausschnitt hinzu:

      src/components/Pagination.js

      import React, { Component, Fragment } from 'react';
      import PropTypes from 'prop-types';
      
      class Pagination extends Component {
        constructor(props) {
          super(props);
          const { totalRecords = null, pageLimit = 30, pageNeighbours = 0 } = props;
      
          this.pageLimit = typeof pageLimit === 'number' ? pageLimit : 30;
          this.totalRecords = typeof totalRecords === 'number' ? totalRecords : 0;
      
          // pageNeighbours can be: 0, 1 or 2
          this.pageNeighbours = typeof pageNeighbours === 'number'
            ? Math.max(0, Math.min(pageNeighbours, 2))
            : 0;
      
          this.totalPages = Math.ceil(this.totalRecords / this.pageLimit);
      
          this.state = { currentPage: 1 };
        }
      }
      
      Pagination.propTypes = {
        totalRecords: PropTypes.number.isRequired,
        pageLimit: PropTypes.number,
        pageNeighbours: PropTypes.number,
        onPageChanged: PropTypes.func
      };
      
      export default Pagination;
      

      Die Komponente Pagination kann vier spezielle Props aufnehmen, wie im Objekt propTypes angegeben.

      • onPageChanged ist eine Funktion, die nur dann mit Daten des aktuellen Paginierungsstatus aufgerufen wird, wenn sich die aktuelle Seite ändert.
      • totalRecords gibt die Gesamtzahl der zu paginierenden Datensätze an. Es ist erforderlich.
      • pageLimit gibt die Anzahl der Datensätze an, die pro Seite angezeigt werden sollen. Wenn sie nicht angegeben wird, ist sie gemäß der Definition in der constructor() auf 30 voreingestellt.
      • pageNeighbours gibt die Anzahl der zusätzlichen Seitennummern an, die auf jeder Seite der aktuellen Seite angezeigt werden sollen. Der Mindestwert ist 0, und der maximale Wert ist 2. Wir hier nichts angegeben, wird der Standardwert 0 gemäß der Definition in der constructor() verwendet.

      Das folgende Bild veranschaulicht die Wirkung verschiedener Werte der Prop pageNeighbours:

      Darstellung der PageNeighbours

      In der Funktion constructor() berechnen Sie die Gesamtseiten wie folgt:

      this.totalPages = Math.ceil(this.totalRecords / this.pageLimit);
      

      Beachten Sie, dass Sie hier Math.ceil() verwenden, um sicherzustellen, dass Sie einen ganzzahligen Wert für die Gesamtzahl der Seiten erhalten. Dadurch wird auch sichergestellt, dass die überschüssigen Datensätze auf der letzten Seite erfasst werden, insbesondere in Fällen, in denen die Anzahl der überschüssigen Datensätze geringer ist als die Anzahl der pro Seite anzuzeigenden Datensätze.

      Schließlich haben Sie den Status mit der Eigenschaft currentPage auf 1 initialisiert. Sie benötigen diese Statuseigenschaft, um intern den Überblick über die aktuell aktive Seite zu behalten.

      Als Nächstes erstellen Sie die Methode zur Erzeugung der Seitennummern.

      Fügen Sie nach import aber vor der Klasse Pagination die folgenden Konstanten und die Funktion range hinzu:

      src/components/Pagination.js

      // ...
      
      const LEFT_PAGE = 'LEFT';
      const RIGHT_PAGE = 'RIGHT';
      
      /**
       * Helper method for creating a range of numbers
       * range(1, 5) => [1, 2, 3, 4, 5]
       */
      const range = (from, to, step = 1) => {
        let i = from;
        const range = [];
      
        while (i <= to) {
          range.push(i);
          i += step;
        }
      
        return range;
      }
      

      Fügen Sie in der Klasse Pagination nach dem constructor die folgende Methode fetchPageNumbers hinzu:

      src/components/Pagination.js

      class Pagination extends Component {
        // ...
      
        /**
         * Let's say we have 10 pages and we set pageNeighbours to 2
         * Given that the current page is 6
         * The pagination control will look like the following:
         *
         * (1) < {4 5} [6] {7 8} > (10)
         *
         * (x) => terminal pages: first and last page(always visible)
         * [x] => represents current page
         * {...x} => represents page neighbours
         */
        fetchPageNumbers = () => {
          const totalPages = this.totalPages;
          const currentPage = this.state.currentPage;
          const pageNeighbours = this.pageNeighbours;
      
          /**
           * totalNumbers: the total page numbers to show on the control
           * totalBlocks: totalNumbers + 2 to cover for the left(<) and right(>) controls
           */
          const totalNumbers = (this.pageNeighbours * 2) + 3;
          const totalBlocks = totalNumbers + 2;
      
          if (totalPages > totalBlocks) {
            const startPage = Math.max(2, currentPage - pageNeighbours);
            const endPage = Math.min(totalPages - 1, currentPage + pageNeighbours);
            let pages = range(startPage, endPage);
      
            /**
             * hasLeftSpill: has hidden pages to the left
             * hasRightSpill: has hidden pages to the right
             * spillOffset: number of hidden pages either to the left or to the right
             */
            const hasLeftSpill = startPage > 2;
            const hasRightSpill = (totalPages - endPage) > 1;
            const spillOffset = totalNumbers - (pages.length + 1);
      
            switch (true) {
              // handle: (1) < {5 6} [7] {8 9} (10)
              case (hasLeftSpill && !hasRightSpill): {
                const extraPages = range(startPage - spillOffset, startPage - 1);
                pages = [LEFT_PAGE, ...extraPages, ...pages];
                break;
              }
      
              // handle: (1) {2 3} [4] {5 6} > (10)
              case (!hasLeftSpill && hasRightSpill): {
                const extraPages = range(endPage + 1, endPage + spillOffset);
                pages = [...pages, ...extraPages, RIGHT_PAGE];
                break;
              }
      
              // handle: (1) < {4 5} [6] {7 8} > (10)
              case (hasLeftSpill && hasRightSpill):
              default: {
                pages = [LEFT_PAGE, ...pages, RIGHT_PAGE];
                break;
              }
            }
      
            return [1, ...pages, totalPages];
          }
      
          return range(1, totalPages);
        }
      }
      

      Hier definieren Sie zunächst zwei Konstanten: LEFT_PAGE und RIGHT_PAGE. Diese Konstanten werden verwendet, um Punkte anzugeben, an denen Sie Seitenkontrollen für das Verschieben nach links bzw. rechts haben.

      Außerdem haben Sie eine Hilfsfunktion range() definiert, die Ihnen bei der Erstellung von Nummernbereichen helfen kann.

      Anmerkung: Wenn Sie eine Dienstprogrammbibliothek wie Lodash in Ihrem Projekt verwenden, können Sie stattdessen die Funktion .range() von Lodash bereitgestellt. Der folgende Code-Ausschnitt zeigt den Unterschied zwischen der gerade definierten Funktion range() und der von Lodash:

      range(1, 5); // returns [1, 2, 3, 4, 5]
      _.range(1, 5); // returns [1, 2, 3, 4]
      

      Als Nächstes haben Sie die Methode fetchPageNumbers() in der Klasse Pagination definiert. Diese Methode handhabt die Kernlogik für die Erstellung der Seitennummern, die auf der Paginierungssteuerung angezeigt werden sollen. Sie möchten, dass die erste Seite und die letzte Seite immer sichtbar sind.

      Zuerst haben Sie einige Variablen definiert. totalNumbers stellt die Gesamtzahl der Seitennummern dar, die auf der Steuerung angezeigt werden. totalBlocks steht für die Gesamtseitennummern, die angezeigt werden sollen, plus zwei zusätzliche Blöcke für die Indikatoren der linken und der rechten Seite.

      Wenn totalPages nicht größer als totalBlocks ist, geben Sie einen Zahlenbereich von 1 bis totalPages zurück. Andernfalls geben Sie den Bereich der Seitennummern mit LEFT_PAGE und RIGHT_PAGE an Punkten zurück, an denen die Seiten nach links bzw. rechts verschoben werden.

      Beachten Sie jedoch, dass Ihre Paginierungssteuerung sicherstellt, dass die erste Seite und die letzte Seite immer sichtbar sind. Die Steuerungen für die linke und rechte Seite erscheinen nach innen.

      Jetzt fügen Sie die Methode render() hinzu, mit der Sie die Paginierungssteuerung rendern können.

      Fügen Sie in der Klasse Pagination nach der Methode constructor und fetchPageNumbers die folgende Methode render hinzu:

      src/components/Pagination.js

      class Pagination extends Component {
        // ...
      
        render() {
          if (!this.totalRecords || this.totalPages === 1) return null;
      
          const { currentPage } = this.state;
          const pages = this.fetchPageNumbers();
      
          return (
            <Fragment>
              <nav aria-label="Countries Pagination">
                <ul className="pagination">
                  { pages.map((page, index) => {
      
                    if (page === LEFT_PAGE) return (
                      <li key={index} className="page-item">
                        <a className="page-link" href="https://www.digitalocean.com/community/tutorials/#" aria-label="Previous" onClick={this.handleMoveLeft}>
                          <span aria-hidden="true">&laquo;</span>
                          <span className="sr-only">Previous</span>
                        </a>
                      </li>
                    );
      
                    if (page === RIGHT_PAGE) return (
                      <li key={index} className="page-item">
                        <a className="page-link" href="https://www.digitalocean.com/community/tutorials/#" aria-label="Next" onClick={this.handleMoveRight}>
                          <span aria-hidden="true">&raquo;</span>
                          <span className="sr-only">Next</span>
                        </a>
                      </li>
                    );
      
                    return (
                      <li key={index} className={`page-item${ currentPage === page ? ' active' : ''}`}>
                        <a className="page-link" href="https://www.digitalocean.com/community/tutorials/#" onClick={ this.handleClick(page) }>{ page }</a>
                      </li>
                    );
      
                  }) }
      
                </ul>
              </nav>
            </Fragment>
          );
        }
      }
      

      Hier generieren Sie das Array der Seitennummern durch Aufruf der zuvor erstellten Methode fetchPageNumbers(). Dann rendern Sie jede Seitennummer mit Array.prototype.map(). Beachten Sie, dass Sie Click-Event-Handler für jede erstellte Seitennummer registrieren, um Klicks zu verarbeiten.

      Beachten Sie auch, dass die Paginierungssteuerung nicht gerendert wird, wenn das Prop totalRecords nicht korrekt an die Komponente Pagination übergeben wurde oder in Fällen, in denen nur 1 Seite vorhanden ist.

      Schließlich definieren Sie die Methoden für die Ereignishandler.

      Fügen Sie in der Klasse Pagination nach der Methode constructor und fetchPageNumbers und der Methode render Folgendes hinzu:

      src/components/Pagination.js

      class Pagination extends Component {
        // ...
      
        componentDidMount() {
          this.gotoPage(1);
        }
      
        gotoPage = page => {
          const { onPageChanged = f => f } = this.props;
          const currentPage = Math.max(0, Math.min(page, this.totalPages));
          const paginationData = {
            currentPage,
            totalPages: this.totalPages,
            pageLimit: this.pageLimit,
            totalRecords: this.totalRecords
          };
      
          this.setState({ currentPage }, () => onPageChanged(paginationData));
        }
      
        handleClick = page => evt => {
          evt.preventDefault();
          this.gotoPage(page);
        }
      
        handleMoveLeft = evt => {
          evt.preventDefault();
          this.gotoPage(this.state.currentPage - (this.pageNeighbours * 2) - 1);
        }
      
        handleMoveRight = evt => {
          evt.preventDefault();
          this.gotoPage(this.state.currentPage + (this.pageNeighbours * 2) + 1);
        }
      }
      

      Sie definieren die Methode gotoPage(), die den Status ändert und die currentPage auf die angegebene Seite setzt. Sie stellt sicher, dass das Argument page einen Mindestwert von 1 und einen maximalen Wert der Gesamtzahl der Seiten hat. Schließlich ruft sie die Funktion onPageChanged() auf, die als Prop übergeben wurde, wobei die Daten den neuen Paginierungsstatus angeben.

      Wenn die Komponente gemountet wird, gehen Sie zur ersten Seite, indem Sie this.gotoPage(1) aufrufen, wie in der Lebenszyklus-Methode this.gotoPage(1) gezeigt.

      Beachten Sie, wie Sie (this.pageNeighbours * 2) in handleMoveLeft() und handleMoveRight() verwenden, um die Seitennummern basierend auf der aktuellen Seitennummer nach links bzw. rechts zu verschieben.

      Hier ist eine Demonstration der Interaktion der Bewegung von links nach rechts.

      Links-rechts-Bewegung der Interaktion

      Sie haben nun die Komponente Pagination abgeschlossen. Benutzer können mit der Navigationssteuerung in dieser Komponente interagieren, um verschiedene Seiten von Flaggen anzuzeigen.

      Schritt 4 — Erstellen der Komponente App

      Nachdem Sie nun eine Komponente CountryCard und Pagination haben, können Sie sie in Ihrer Komponente App verwenden.

      Ändern Sie die Datei App.js im Verzeichnis src:

      Ersetzen Sie den Inhalt von App.js durch die folgenden Codezeilen:

      src/App.js

      import React, { Component } from 'react';
      import Countries from 'countries-api';
      import './App.css';
      import Pagination from './components/Pagination';
      import CountryCard from './components/CountryCard';
      
      class App extends Component {
        state = { allCountries: [], currentCountries: [], currentPage: null, totalPages: null }
      
        componentDidMount() {
          const { data: allCountries = [] } = Countries.findAll();
          this.setState({ allCountries });
        }
      
        onPageChanged = data => {
          const { allCountries } = this.state;
          const { currentPage, totalPages, pageLimit } = data;
          const offset = (currentPage - 1) * pageLimit;
          const currentCountries = allCountries.slice(offset, offset + pageLimit);
      
          this.setState({ currentPage, currentCountries, totalPages });
        }
      }
      
      export default App;
      

      Hier initialisieren Sie den Status der Komponente App mit den folgenden Attributen:

      • allCountries – dies ist ein Array aller Länder in Ihrer Anwendung. Initialisiert auf ein leeres Array ([]).
      • currentCountries – dies ist ein Array aller Länder, die auf der aktuell aktiven Seite angezeigt werden sollen. Initialisiert auf ein leeres Array ([]).
      • currentPage – die Seitennummer der aktuell aktiven Seite. Initialisiert auf null.
      • totalPages – die Gesamtzahl der Seiten für alle Ländereinträge. Initialisiert auf null.

      Als Nächstes rufen Sie in der Lebenszyklusmethode componentDidMount() alle Länder der Welt mit dem Paket countries-api ab, indem Sie Countries.findAll() abrufen. Dann aktualisieren Sie den Status der Anwendung, indem Sie allCountries so einstellen, dass es alle Länder der Welt enthält. Um mehr über das Paket countries-apizu erfahren, können Sie die [Dokumentation countries-api] einsehen.

      Schließlich haben Sie die Methode onPageChanged() definiert, die jedes Mal aufgerufen wird, wenn Sie über die Paginierungssteuerung zu einer neuen Seite navigieren. Diese Methode wird an das Prop onPageChanged der Komponente Pagination übergeben.

      Es gibt zwei Zeilen, die bei dieser Methode beachtet werden sollten. Die erste ist diese Zeile:

      const offset = (currentPage - 1) * pageLimit;
      

      Der Wert offset gibt den Startindex zum Abrufen der Datensätze für die aktuelle Seite an. Die Verwendung von (currentPage - 1) stellt sicher, dass der Offset null ist. Nehmen wir zum Beispiel an, dass Sie 25 Datensätze pro Seite anzeigen, und Sie betrachten derzeit Seite 5. Dann ist der offset ((5 - 1) * 25 = 100).

      Wenn Sie beispielsweise Datensätze bei Bedarf aus einer Datenbank abrufen, ist dies eine Beispiel-SQL-Abfrage, die Ihnen zeigt, wie Offset verwendet werden kann:

      SELECT * FROM `countries` LIMIT 100, 25
      

      Da Sie keine Datensätze aus einer Datenbank oder einer externen Quelle abrufen, benötigen Sie eine Möglichkeit, den erforderlichen Teil der Datensätze zu extrahieren, der für die aktuelle Seite angezeigt werden soll.

      Die zweite ist diese Zeile:

      const currentCountries = allCountries.slice(offset, offset + pageLimit);
      

      Hier verwenden Sie die Methode Array.prototype.slice(), um den erforderlichen Teil der Datensätze aus allCountries zu extrahieren, indem Sie offset als Startindex für Slice und (offset + pageLimit) als den Index, vor dem Slice beendet werden soll, übergeben.

      Anmerkung: In diesem Tutorial haben Sie keine Datensätze aus einer externen Quelle abgerufen. In einer realen Anwendung werden Sie wahrscheinlich Datensätze aus einer Datenbank oder einer API abrufen. Die Logik zum Abrufen der Datensätze kann in die Methode onPageChanged() der Komponente App eingebunden werden.

      Nehmen wir an, Sie haben einen fiktiven API-Endpunkt /api/countries?page={current_page}&limit={page_limit}. Der folgende Ausschnitt zeigt, wie Sie mithilfe des HTTaxiosP-Pakets [axios] Länder bei Bedarf von der API abrufen können:

      onPageChanged = data => {
        const { currentPage, totalPages, pageLimit } = data;
      
        axios.get(`/api/countries?page=${currentPage}&limit=${pageLimit}`)
          .then(response => {
            const currentCountries = response.data.countries;
            this.setState({ currentPage, currentCountries, totalPages });
          });
      }
      

      Jetzt können Sie die Komponente App beenden, indem Sie die Methode render() hinzufügen.

      Fügen Sie in der Klasse App, jedoch nach componentDidMount und onPageChanged, die folgende Methode render hinzu:

      src/App.js

      class App extends Component {
        // ... other methods here ...
      
        render() {
          const { allCountries, currentCountries, currentPage, totalPages } = this.state;
          const totalCountries = allCountries.length;
      
          if (totalCountries === 0) return null;
      
          const headerClass = ['text-dark py-2 pr-4 m-0', currentPage ? 'border-gray border-right' : ''].join(' ').trim();
      
          return (
            <div className="container mb-5">
              <div className="row d-flex flex-row py-5">
                <div className="w-100 px-4 py-5 d-flex flex-row flex-wrap align-items-center justify-content-between">
                  <div className="d-flex flex-row align-items-center">
                    <h2 className={headerClass}>
                      <strong className="text-secondary">{totalCountries}</strong> Countries
                    </h2>
                    { currentPage && (
                      <span className="current-page d-inline-block h-100 pl-4 text-secondary">
                        Page <span className="font-weight-bold">{ currentPage }</span> / <span className="font-weight-bold">{ totalPages }</span>
                      </span>
                    ) }
                  </div>
                  <div className="d-flex flex-row py-4 align-items-center">
                    <Pagination totalRecords={totalCountries} pageLimit={18} pageNeighbours={1} onPageChanged={this.onPageChanged} />
                  </div>
                </div>
                { currentCountries.map(country => <CountryCard key={country.cca3} country={country} />) }
              </div>
            </div>
          );
        }
      }
      

      In der Methode render() rendern Sie die Gesamtzahl der Länder, die aktuelle Seite, die Gesamtzahl der Seiten, die Steuerung <Pagination> und dann die <CountryCard> für jedes Land auf der aktuellen Seite.

      Beachten Sie, dass Sie die zuvor definierte Methode onPageChanged() an das Prop onPageChanged der Steuerung <Pagination> übergeben haben. Dies ist sehr wichtig für die Erfassung der Seitenänderungen aus der Komponente Pagination. Außerdem zeigen Sie 18 Länder pro Seite an.

      Zu diesem Zeitpunkt sieht die Anwendung wie der folgende Screenshot aus:

      Screenshot der Anwendung mit 248 aufgelisteten Ländern und Seitennummern oben, um durch die einzelnen Seiten zu gehen

      Sie haben nun eine Komponente App, die mehrere Komponenten CountryCard anzeigt, und eine Komponente Pagination, die den Inhalt in separate Seiten aufteilt. Als Nächstes werden Sie das Styling Ihrer Anwendung erkunden.

      Schritt 5 — Hinzufügen von benutzerdefinierten Stilen

      Vielleicht haben Sie bemerkt, dass Sie den zuvor erstellten Komponenten einige benutzerdefinierte Klassen hinzugefügt haben. Lassen Sie uns einige Stil-Regeln für diese Klassen in der Datei src/App.scss definieren.

      Die Datei App.scss wird wie der folgende Ausschnitt aussehen:

      src/App.scss

      /* Declare some variables */
      $base-color: #ced4da;
      $light-background: lighten(desaturate($base-color, 50%), 12.5%);
      
      .current-page {
        font-size: 1.5rem;
        vertical-align: middle;
      }
      
      .country-card-container {
        height: 60px;
        cursor: pointer;
        position: relative;
        overflow: hidden;
      }
      
      .country-name {
        font-size: 0.9rem;
      }
      
      .country-region {
        font-size: 0.7rem;
      }
      
      .current-page,
      .country-name,
      .country-region {
        line-height: 1;
      }
      
      // Override some Bootstrap pagination styles
      ul.pagination {
        margin-top: 0;
        margin-bottom: 0;
        box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
      
        li.page-item.active {
          a.page-link {
            color: saturate(darken($base-color, 50%), 5%) !important;
            background-color: saturate(lighten($base-color, 7.5%), 2.5%) !important;
            border-color: $base-color !important;
          }
        }
      
        a.page-link {
          padding: 0.75rem 1rem;
          min-width: 3.5rem;
          text-align: center;
          box-shadow: none !important;
          border-color: $base-color !important;
          color: saturate(darken($base-color, 30%), 10%);
          font-weight: 900;
          font-size: 1rem;
      
          &:hover {
            background-color: $light-background;
          }
        }
      }
      

      Ändern Sie Ihre Datei App.js so, dass sie auf App.scss anstelle von App.css verweist.

      Anmerkung: Weitere Informationen hierzu finden Sie in der Dokumentation zu Create React App.

      src/App.js

      import React, { Component } from 'react';
      import Countries from 'countries-api';
      import './App.scss';
      import Pagination from './components/Pagination';
      import CountryCard from './components/CountryCard';
      

      Nach dem Hinzufügen der Stile wird die Anwendung nun wie der folgende Screenshot aussehen:

      Screenshot der Anwendung, Seite 1 von 14, mit Stilen

      Sie haben nun eine vollständige Anwendung mit zusätzlichem benutzerdefiniertem Styling. Sie können benutzerdefinierte Stile verwenden, um alle Standardstile, die von Bibliotheken wie Bootstrap bereitgestellt werden, zu ändern und zu verbessern.

      Zusammenfassung

      In diesem Tutorial haben Sie ein benutzerdefiniertes Paginierungs-Widget in Ihrer React-Anwendung erstellt. Obwohl Sie in diesem Tutorial keine Aufrufe an eine API getätigt oder mit einem Datenbank-Backend interagiert haben, kann Ihre Anwendung solche Interaktionen erfordern. Sie sind in keiner Weise auf den in diesem Tutorial verwendeten Ansatz beschränkt. Sie können ihn beliebig erweitern, um den Anforderungen Ihrer Anwendung gerecht zu werden.

      Den vollständigen Quellcode dieses Tutorials finden Sie im Repository [build-react-pagination-demo][pagination-demo ]auf GitHub. Außerdem können Sie eine Live-Demo dieses Tutorials auf Code-Sandbox erhalten.

      Wenn Sie mehr über React erfahren möchten, sehen Sie sich unsere Reihe Codieren in React.js an oder besuchen Sie unsere React-Themenseite für Übungen und Programmierprojekte.



      Source link

      Entwickeln einer Drupal 9-Website auf einem lokalen Rechner mit Docker und DDEV


      Der Autor hat den Diversity in Tech Fund dazu ausgewählt, eine Spende im Rahmen des Programms Write for DOnations zu erhalten.

      Einführung

      DDEV ist ein Open-Source-Tool, das Docker verwendet, um für viele verschiedene PHP-Frameworks lokale Entwicklungsumgebungen zu erstellen. Durch Verwendung der Vorteile von Containerisierung kann DDEV die Arbeit an mehreren Projekten, bei denen verschiedene Tech-Stacks und Cloud-Server zum Einsatz kommen, erheblich vereinfachen. DDEV enthält Vorlagen für WordPress, Laravel, Magento, TYPO3, Drupal und mehr.

      Drupal 9 wurde am 3. Juni 2020 für das Drupal CMS veröffentlicht. Drupal ist bekannt für seine hohe Benutzerfreundlichkeit und eine enorme Bibliothek an Modulen und Themen. Als PHP-Framework ist es beliebt für die Entwicklung und Pflege verschiedener Websites und Anwendungen aller Größen.

      In diesem Tutorial werden Sie mit DDEV auf Ihrem lokalen Rechner eine Drupal 9-Website erstellen. Damit können Sie zunächst Ihre Website einrichten und Ihr Projekt dann später, wenn Sie bereit sind, auf einem Produktionsserver bereitstellen.

      Voraussetzungen

      Um diesem Tutorial zu folgen, benötigen Sie:

      Anmerkung: Es ist möglich, Drupal 9 mit DDEV auf einem Remoteserver zu entwickeln; Sie benötigen jedoch eine Lösung, um in einem Webbrowser auf localhost zuzugreifen. Der DDEV-Befehl ddev share arbeitet mit ngrok zusammen, um für Sie und andere Stakeholder einen sicheren Tunnel zu Ihrem Server einzurichten und Ihre Entwicklungs-Site anzuzeigen. Zur persönlichen Verwendung können Sie auch eine GUI auf Ihrem Remoteserver installieren und über einen Webbrowser innerhalb dieser Oberfläche auf Ihre Entwicklungs-Site zugreifen. Folgen Sie dazu unserem Leitfaden Installieren und Konfigurieren von VNC unter Ubuntu 20.04. Für eine noch schnellere GUI-Lösung können Sie unserem Leitfaden zum Einrichten eines Remotedesktops mit X2Go unter Ubuntu 20.04 folgen.

      Schritt 1 — Installieren von DDEV

      In diesem Schritt installieren Sie DDEV auf Ihrem lokalen Rechner. Option 1 enthält Anweisungen für macOS, während Option 2 Anweisungen für Linux beinhaltet. Dieses Tutorial wurde mit DDEV Version 1.15.0 getestet.

      Option 1 — Installieren von DDEV unter macOS

      DDEV empfiehlt macOS-Benutzern, das Tool mit dem Homebrew-Paketmanager zu installieren. Verwenden Sie folgenden brew-Befehl zum Installieren der neuesten stabilen Version:

      • brew tap drud/ddev && brew install drud/ddev/ddev

      Wenn Sie die absolut neueste Version bevorzugen, können Sie brew zum Installieren von ddev-edge verwenden:

      • brew tap drud/ddev-edge && brew install drud/ddev-edge/ddev

      Wenn Sie bereits eine Version von DDEV installiert haben oder Ihre Version aktualisieren möchten, schließen Sie DDEV und verwenden brew zum Aktualisieren Ihrer Installation:

      • ddev poweroff
      • brew upgrade ddev

      Sobald Sie DDEV installiert oder aktualisiert haben, führen Sie ddev version aus, um Ihre Software zu überprüfen:

      Sie werden eine Ausgabe wie diese sehen:

      Output

      DDEV-Local version v1.15.0 commit v1.15.0 db drud/ddev-dbserver-mariadb-10.2:v1.15.0 dba phpmyadmin/phpmyadmin:5 ddev-ssh-agent drud/ddev-ssh-agent:v1.15.0 docker 19.03.8 docker-compose 1.25.5 os darwin router drud/ddev-router:v1.15.0 web drud/ddev-webserver:v1.15.0

      DDEV enthält eine leistungsstarke CLI oder Befehlszeilenschnittstelle. Führen Sie ddev aus, um sich über einige gängige Befehle zu informieren:

      Sie sehen die folgende Ausgabe:

      Output

      Create and maintain a local web development environment. Docs: https://ddev.readthedocs.io Support: https://ddev.readthedocs.io/en/stable/#support Usage: ddev [command] Available Commands: auth A collection of authentication commands composer Executes a composer command within the web container config Create or modify a ddev project configuration in the current directory debug A collection of debugging commands delete Remove all project information (including database) for an existing project describe Get a detailed description of a running ddev project. exec Execute a shell command in the container for a service. Uses the web service by default. export-db Dump a database to a file or to stdout help Help about any command hostname Manage your hostfile entries. import-db Import a sql file into the project. import-files Pull the uploaded files directory of an existing project to the default public upload directory of your project. list List projects logs Get the logs from your running services. pause uses 'docker stop' to pause/stop the containers belonging to a project. poweroff Completely stop all projects and containers pull Pull files and database using a configured provider plugin. restart Restart a project or several projects. restore-snapshot Restore a project's database to the provided snapshot version. sequelpro This command is not available since sequel pro.app is not installed share Share project on the internet via ngrok. snapshot Create a database snapshot for one or more projects. ssh Starts a shell session in the container for a service. Uses web service by default. start Start a ddev project. stop Stop and remove the containers of a project. Does not lose or harm anything unless you add --remove-data. version print ddev version and component versions Flags: -h, --help help for ddev -j, --json-output If true, user-oriented output will be in JSON format. -v, --version version for ddev Use "ddev [command] --help" for more information about a command.

      Weitere Informationen zur Verwendung der DDEV-CLI finden Sie in der offiziellen DDEV-Dokumentation.

      Nachdem DDEV auf Ihrem lokalen Rechner installiert wurde, sind Sie nun bereit, Drupal 9 zu installieren und mit der Entwicklung einer Website zu beginnen.

      Option 2 — Installieren von DDEV unter Linux

      In einem Linux-Betriebssystem können Sie DDEV mit Homebrew für Linux installieren oder das offizielle Installationsskript verwenden. Beginnen Sie unter Ubuntu mit dem Aktualisieren Ihrer Liste mit Paketen im apt-Paketmanager (Sie können apt in Debian bzw. den äquivalenten Paketmanager, der mit Ihrer Linux-Distribution verknüpft ist, verwenden):

      Installieren Sie nun einige Voraussetzungspakete aus dem offiziellen Repository von Ubuntu:

      • sudo apt install build-essential apt-transport-https ca-certificates software-properties-common curl

      Mit diesen Paketen können Sie das DDEV-Installationsskript aus dem offiziellen GitHub-Repository herunterladen.

      Laden Sie das Skript jetzt herunter:

      • curl -O https://raw.githubusercontent.com/drud/ddev/master/scripts/install_ddev.sh

      Öffnen Sie es vor dem Ausführen des Skripts in nano oder Ihrem bevorzugten Texteditor und inspizieren Sie den Inhalt:

      nano install_ddev.sh
      

      Sobald Sie die Inhalte des Skripts geprüft haben und damit zufrieden sind, speichern und schließen Sie die Datei. Jetzt sind Sie bereit, das Installationsskript auszuführen.

      Verwenden Sie den Befehl chmod, um das Skript ausführbar zu machen:

      Führen Sie das Skript jetzt aus:

      Im Installationsprozess werden Sie ggf. dazu aufgefordert, einige Einstellungen zu bestätigen oder Ihr sudo-Passwort einzugeben. Nach Abschluss der Installation wird DDEV in Ihrem Linux-Betriebssystem verfügbar sein.

      Führen Sie ddev version aus, um Ihre Software zu überprüfen:

      Sie werden eine Ausgabe wie diese sehen:

      Output

      DDEV-Local version v1.15.0 commit v1.15.0 db drud/ddev-dbserver-mariadb-10.2:v1.15.0 dba phpmyadmin/phpmyadmin:5 ddev-ssh-agent drud/ddev-ssh-agent:v1.15.0 docker 19.03.8 docker-compose 1.25.5 os linux router drud/ddev-router:v1.15.0 web drud/ddev-webserver:v1.15.0

      DDEV ist eine leistungsstarke CLI oder Befehlszeilenschnittstelle. Führen Sie ddev ohne etwas anderes aus, um sich über einige gängige Befehle zu informieren:

      Sie sehen die folgende Ausgabe:

      Output

      Create and maintain a local web development environment. Docs: https://ddev.readthedocs.io Support: https://ddev.readthedocs.io/en/stable/#support Usage: ddev [command] Available Commands: auth A collection of authentication commands composer Executes a composer command within the web container config Create or modify a ddev project configuration in the current directory debug A collection of debugging commands delete Remove all project information (including database) for an existing project describe Get a detailed description of a running ddev project. exec Execute a shell command in the container for a service. Uses the web service by default. export-db Dump a database to a file or to stdout help Help about any command hostname Manage your hostfile entries. import-db Import a sql file into the project. import-files Pull the uploaded files directory of an existing project to the default public upload directory of your project. list List projects logs Get the logs from your running services. pause uses 'docker stop' to pause/stop the containers belonging to a project. poweroff Completely stop all projects and containers pull Pull files and database using a configured provider plugin. restart Restart a project or several projects. restore-snapshot Restore a project's database to the provided snapshot version. sequelpro This command is not available since sequel pro.app is not installed share Share project on the internet via ngrok. snapshot Create a database snapshot for one or more projects. ssh Starts a shell session in the container for a service. Uses web service by default. start Start a ddev project. stop Stop and remove the containers of a project. Does not lose or harm anything unless you add --remove-data. version print ddev version and component versions Flags: -h, --help help for ddev -j, --json-output If true, user-oriented output will be in JSON format. -v, --version version for ddev Use "ddev [command] --help" for more information about a command.

      Weitere Informationen zur Verwendung der DDEV-CLI finden Sie in der offiziellen DDEV-Dokumentation.

      Nachdem DDEV auf Ihrem lokalen Rechner installiert ist, sind Sie nun bereit, Drupal 9 bereitzustellen und mit der Entwicklung einer Website zu beginnen.

      Schritt 2 — Bereitstellen einer neuen Drupal 9-Site mit DDEV

      Mit ausgeführtem DDEV werden Sie nun ein Drupal-spezifisches Dateisystem erstellen, Drupal 9 installieren und dann ein standardmäßiges Websiteprojekt initiieren.

      Zuerst erstellen Sie ein root-Verzeichnis für das Projekt und öffnen es. Von hier aus führen Sie alle verbleibenden Befehle aus. In diesem Tutorial wird d9test verwendet; Sie können Ihr Verzeichnis jedoch auch anders nennen. Beachten Sie jedoch, dass DDEV nicht gut mit Bindestrichen in Namen umgehen kann. Es gilt als bewährtes Verfahren, Verzeichnisnamen wie my-project oder drupal-site-1 zu vermeiden.

      Erstellen Sie das root-Verzeichnis für Ihr Projekt und navigieren Sie dort hin:

      DDEV eignet sich hervorragend zum Erstellen von Verzeichnisstrukturen, die mit bestimmten CMS-Plattformen übereinstimmen. Verwenden Sie den Befehl ddev config zum Einrichten einer Verzeichnisstruktur, die für Drupal 9 spezifisch ist:

      • ddev config --project-type=drupal9 --docroot=web --create-docroot

      Sie werden eine Ausgabe wie diese sehen:

      Output

      Creating a new ddev project config in the current directory (/Users/sammy/d9test) Once completed, your configuration will be written to /Users/sammy/d9test/.ddev/config.yaml Created docroot at /Users/sammy/d9test/web You have specified a project type of drupal9 but no project of that type is found in /Users/sammy/d9test/web Ensuring write permissions for d9new No settings.php file exists, creating one Existing settings.php file includes settings.ddev.php Configuration complete. You may now run 'ddev start'.

      Da Sie --project-type=drupal9 an Ihren Befehl ddev config übergeben haben, hat DDEV mehrere Unterverzeichnisse und Dateien erstellt, die die Standardorganisation für eine Drupal-Website darstellen. Die Verzeichnisstruktur Ihres Projekts wird nun wie folgt aussehen:

      A Drupal 9 directory tree

      .
      ├── .ddev
      │   ├── .gitignore
      │   ├── config.yaml
      │   ├── db-build
      │   │   └── Dockerfile.example
      │   └── web-build
      │       └── Dockerfile.example
      └── web
          └── sites
              └── default
                  ├── .gitignore
                  ├── settings.ddev.php
                  └── settings.php
      
      6 directories, 7 files
      

      .ddev/ wird der Hauptordner für die ddev-Konfiguration sein. web/ ist der docroot für Ihr neues Projekt; er enthält mehrere spezifische Dateien mit Einstellungen (settings). Sie verfügen nun über das Grundgerüst für Ihr neues Drupal-Projekt.

      Der nächste Schritt besteht darin, Ihre Plattform zu initialisieren, wodurch die erforderlichen Container und Networking-Konfigurationen erstellt werden. DDEV bindet sich an Ports 80 und 443. Wenn Sie also einen Webserver wie Apache auf Ihrem Rechner ausführen oder etwas anderes verwenden, das diese Ports nutzt, halten Sie diese Dienste vor dem Fortfahren an.

      Verwenden Sie den Befehl ddev start, um Ihre Plattform zu initialisieren:

      Dadurch werden alle Docker-basierten Container für Ihr Projekt erstellt, darunter ein Webcontainer, ein Datenbankcontainer und phpmyadmin. Nach Abschluss der Initialisierung sehen Sie eine Ausgabe wie diese (Ihre Portnummer kann sich davon unterscheiden):

      Output

      ... Successfully started d9test Project can be reached at http://d9test.ddev.site http://127.0.0.1:32773

      Anmerkung: Denken Sie daran, dass DDEV hier im Hintergrund Docker-Container startet. Wenn Sie diese Container anzeigen oder überprüfen möchten, ob sie ausgeführt werden, können Sie den Befehl docker ps verwenden:

      Neben anderen Containern, die Sie derzeit ausführen, finden Sie vier neue Container, die jeweils ein anderes Image ausführen: php-myadmin, ddev-webserver, ddev-router und ddev-dbserver-mariadb.

      ddev start hat Ihre Container erfolgreich erstellt und eine Ausgabe mit zwei URLs geliefert. Zwar steht in dieser Ausgabe, dass Ihr Projekt „unter http://d9test.ddev.site und http://127.0.0.1:32773 erreichbar ist“, doch wenn Sie diese URLs besuchen, wird ein Fehler ausgelöst. Ab Drupal 8 funktionieren der Drupal Core und die contrib-Module wie Abhängigkeiten. Deshalb müssen Sie Drupal zunächst mit Composer, dem Paketmanager für PHP-Projekte, installieren, bevor Sie etwas in Ihrem Webbrowser laden können.

      Eine der nützlichsten und elegantesten Funktionen von DDEV ist, dass Sie Composer-Befehle über die DDEV-CLI in Ihre containerisierte Umgebung übergeben können. Das bedeutet, dass Sie die spezifische Konfiguration Ihres Computers von Ihrer Entwicklungsumgebung trennen können. So müssen Sie die verschiedenen Probleme mit Dateipfaden, Abhängigkeiten und Versionen, die eine lokale PHP-Entwicklung im Allgemeinen begleiten, nicht mehr verwalten. Außerdem können Sie rasch und mit minimalem Aufwand zwischen verschiedenen Frameworks und Tech-Stacks wechseln.

      Verwenden Sie den Befehl ddev composer zum Herunterladen von drupal/recommended-project. Dadurch wird Drupal core mit seinen Bibliotheken und anderen verwandten Ressourcen heruntergeladen und ein Standardprojekt erstellt:

      • ddev composer create "drupal/recommended-project"

      Laden Sie nun eine abschließende Komponente namens Drush oder Drupal Shell herunter. In diesem Tutorial werden wir nur einen drush-Befehl nutzen. Das Tutorial bietet eine Alternative, doch ist drush eine leistungsstarke CLI für die Drupal-Entwicklung, die für zusätzliche Effizienz sorgt.

      Verwenden Sie ddev composer zum Installieren von drush:

      • ddev composer require "drush/drush"

      Sie haben nun ein standardmäßiges Drupal 9-Projekt erstellt und drush installiert. Jetzt werden Sie Ihr Projekt in einem Browser anzeigen und die Einstellungen Ihrer Website konfigurieren.

      Schritt 3 — Konfigurieren des Drupal-9-Projekts

      Nachdem Sie Drupal 9 installiert haben, können Sie Ihr neues Projekt nun in Ihrem Browser öffnen. Dazu können Sie ddev start erneut ausführen und eine der beiden ausgegebenen URLs kopieren. Alternativ können Sie folgenden Befehl verwenden, um Ihre Website in einem neuen Browserfenster automatisch zu starten:

      Der standardmäßige Drupal-Installationsassistent wird angezeigt.

      Drupal 9-Installationsprogramm über Browser

      Hier haben Sie zwei Optionen. Sie können diese Benutzeroberfläche verwenden und dem Assistenten durch die Installation folgen oder zu Ihrem Terminal zurückkehren und über ddev einen drush-Befehl übergeben. Die letztere Option automatisiert den Installationsprozess und setzt admin sowohl als Ihren Benutzernamen als auch als Passwort.

      Option 1 — Verwenden des Assistenten

      Kehren Sie zum Assistenten in Ihrem Browser zurück. Wählen Sie unter Sprache auswählen eine Sprache aus dem Drop-down-Menü aus und klicken Sie auf Speichern und fortfahren. Wählen Sie nun ein Installationsprofil aus. Sie können zwischen Standard, Minimal und Demo wählen. Treffen Sie Ihre Wahl und klicken Sie dann auf Speichern und fortfahren. Drupal überprüft automatisch Ihre Anforderungen, erstellt eine Datenbank und installiert Ihre Site. Ihr letzter Schritt besteht darin, einige Konfigurationen anzupassen. Fügen Sie für die Site einen Namen und eine E-Mail-Adresse, die auf Ihre Domäne endet, hinzu. Wählen Sie dann einen Benutzernamen und ein Passwort. Wählen Sie ein starkes Passwort aus und bewahren Sie Ihre Anmeldedaten an einem sicheren Ort auf. Fügen Sie schließlich eine private E-Mail-Adresse hinzu, die Sie regelmäßig überprüfen, füllen Sie die regionalen Einstellungen aus und klicken Sie auf Speichern und fortfahren.

      Drupal 9-Willkommensnachricht mit einer Warnung zu Berechtigungen

      Ihre neue Site wird mit einer Willkommensnachricht geladen.

      Option 2 — Verwenden der Befehlszeile

      Führen Sie im root-Verzeichnis Ihres Projekts den Befehl ddev exec aus, um mit drush eine standardmäßige Drupal-Site zu installieren:

      • ddev exec drush site:install --account-name=admin --account-pass=admin

      Dadurch wird Ihre Website wie beim Assistenten erstellt, jedoch mit verschiedenen Standardkonfigurationen. Ihr Benutzername und Passwort werden admin lauten.

      Starten Sie nun die Site, um sie in Ihrem Browser anzuzeigen:

      Sie sind nun bereit, mit dem Erstellen Ihrer Website zu beginnen. Es gilt jedoch als bewährte Methode zu überprüfen, ob Ihre Berechtigungen für das Verzeichnis /sites/web/default korrekt sind. Solange Sie lokal arbeiten, ist das kein großes Problem; wenn Sie diese Berechtigungen jedoch an einen Produktionsserver übertragen, stellen sie ein Sicherheitsrisiko dar.

      Schritt 4 — Überprüfen Ihrer Berechtigungen

      Während der Installation mit dem Assistenten oder dem ersten Laden Ihrer Willkommensseite sehen Sie ggf. eine Warnung zu den Berechtigungseinstellungen in Ihrem Verzeichnis /sites/web/default und eine Datei in diesem Verzeichnis: settings.php.

      Nach Ausführung des Installationsskripts wird Drupal versuchen, die Berechtigungen für das Verzeichnis web/sites/default festzulegen, um für alle Gruppen zu lesen und auszuführen: Dies ist eine 555-Berechtigungseinstellung. Außerdem wird es versuchen, Berechtigungen für default/settings.php auf schreibgeschützt oder 444 festzulegen. Wenn Sie auf diese Warnung stoßen, führen Sie im root-Verzeichnis Ihres Projekts diese zwei chmod-Befehle aus. Wenn Sie das nicht tun, besteht ein Sicherheitsrisiko:

      • chmod 555 web/sites/default
      • chmod 444 web/sites/default/settings.php

      Um zu überprüfen, ob Sie die richtigen Berechtigungen haben, führen Sie diesen ls-Befehl mit den Switches a, l, h und d aus:

      • ls -alhd web/sites/default web/sites/default/settings.php

      Überprüfen Sie, ob Ihre Berechtigungen mit der folgenden Ausgabe übereinstimmen:

      Output

      dr-xr-xr-x 8 sammy staff 256 Jul 21 12:56 web/sites/default -r--r--r-- 1 sammy staff 249 Jul 21 12:12 web/sites/default/settings.php

      Sie sind nun bereit, auf Ihrem lokalen Rechner eine Drupal 9-Website zu erstellen.

      Schritt 5 — Erstellen des ersten Beitrags in Drupal

      Um einige Funktionen von Drupal zu testen, erstellen Sie nun mit der Web-Benutzeroberfläche einen Beitrag.

      Klicken Sie auf der ersten Seite Ihrer Website auf die Schaltfläche Inhalt am linken Rand des oberen Menüs. Klicken Sie nun auf die Schaltfläche Inhalt hinzufügen. Eine neue Seite wird angezeigt. Klicken Sie auf Artikel, und eine weitere Seite wird angezeigt.

      Drupal 9-Eingabeaufforderung zum Erstellen eines Artikels

      Fügen Sie einen beliebigen Titel und Content hinzu. Sie können auch ein Bild wie eines der Hintergrundbilder von DigitalOcean hinzufügen. Klicken Sie dann auf die blaue Schaltfläche Speichern.

      Ihr erster Beitrag wird auf der Website angezeigt.

      Drupal 9 – Beitrag erstellt

      Sie entwickeln nun eine Drupal 9-Website auf Ihrem lokalen Rechner, ohne jemals mit einem Server zu interagieren (dank Docker und DDEV). Im folgenden Schritt verwalten Sie den DDEV-Container, der Ihren Workflow aufnehmen wird.

      Schritt 6 — Verwalten des DDEV-Containers

      Wenn Sie Ihr Projekt abgeschlossen haben oder eine Pause machen möchten, können Sie Ihren DDEV-Container anhalten, ohne sich Gedanken über Datenverluste machen zu müssen. DDEV kann zwischen vielen Projekten schnelle Kontextwechsel verwalten; dies ist eine der nützlichsten Funktionen. Ihr Code und Ihre Daten werden stets in Ihrem Projektverzeichnis gespeichert, auch wenn Sie den DDEV-Container angehalten oder gelöscht haben.

      Um Ressourcen freizugeben, können Sie DDEV jederzeit anhalten. Führen Sie im root-Verzeichnis Ihres Projekts folgenden Befehl aus:

      DDEV ist global verfügbar, sodass Sie ddev-Befehle von überall her ausführen können, solange Sie das DDEV-Projekt angeben:

      Außerdem können Sie mit ddev list alle Ihre Projekte auf einmal anzeigen:

      DDEV umfasst viele andere nützliche Befehle.

      Sie können DDEV neu starten und jederzeit lokal weiterentwickeln.

      Zusammenfassung

      In diesem Tutorial haben Sie Docker und die Vorteile von Containerisierung verwendet, um mit Hilfe von DDEV lokal eine Drupal-Site zu erstellen. DDEV lässt sich in zahlreiche IDEs einbinden und bietet natives PHP-Debugging für Atom, PHPStorm und Visual Studio Code (vscode). Nun können Sie mehr über das Erstellen von Entwicklungsumgebungen für Drupal mit DDEV oder das Entwickeln anderer PHP-Frameworks wie WordPress erfahren.



      Source link

      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

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

      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