Web Scraping mit PHP: eine Schritt-für-Schritt-Anleitung

Lernen Sie, wie Sie Ihren eigenen einfachen Web Scraper in PHP von Grund auf erstellen und programmieren können.
2 min read
Web scraping with PHP

Dank seiner umfangreichen Bibliotheken und Tools ist PHP eine ausgezeichnete Sprache für die Erstellung von Web Scrapern. PHP wurde eigens für die Web-Entwicklung konzipiert und erledigt Web-Scraping-Aufgaben spielend und zuverlässig.

In diesem Artikel lernen Sie einige der verschiedenen Methoden kennen, die für das Scrapen von Websites mit PHP zur Verfügung stehen. Insbesondere erfahren Sie, wie Sie Websites mithilfe von curl, file_get_contents, dem Symfony BrowserKit und dem Komponenten Symfony Panther scrapen können. Außerdem stellen wir Ihnen einige typische Probleme vor, die beim Web Scraping auftreten können, und wir erklären Ihnen, wie diese zu vermeiden sind.

Inhalt

Web Scraping mit PHP  

In diesem Abschnitt stellen wir Ihnen einige gängige Methoden zum Web Scraping – sowohl für einfache als auch für komplexe/dynamische Websites – vor.

Bitte beachten Sie Folgendes: Auch wenn wir im Rahmen dieses Tutorials mehrere Methoden behandeln, ist unsere Auflistung keineswegs vollständig.  

Voraussetzungen  

Um dieses Tutorial durchzuarbeiten, benötigen Sie die neueste Version von PHP und Composer, einem Abhängigkeitsmanager für PHP. Zum Testen dieser Anleitung wurden PHP 8.1.18 und Composer 2.5.5 verwendet.

Nach der Einrichtung von PHP und Composer erstellen Sie ein Verzeichnis mit dem Namen php-web-scraping und wechseln mit cd in dieses Verzeichnis:

mkdir php-web-scraping
cd $_

Für den Rest des Tutorials werden Sie in diesem Verzeichnis arbeiten.

curl

curl ist eine praktisch allgegenwärtige Low-Level-Bibliothek und ein in C programmiertes CLI-Tool, das zum Abrufen des Inhalts einer Webseite über HTTP oder HTTPS verwendet werden kann. Auf fast allen Plattformen ist die Unterstützung von curl in PHP standardmäßig aktiviert.

In diesem Abschnitt scrapen Sie eine einfache Webseite, die eine nach Bevölkerungszahl geordnete Liste von Ländern (basierend auf Schätzungen der Vereinten Nationen) enthält. Sie werden die Links im Menü einschließlich der Linktexte extrahieren.

Erstellen Sie zunächst eine Datei mit dem Namen curl.php und initialisieren Sie curl in dieser Datei mithilfe der Funktion curl_init:

<?php
$ch = curl_init();

Bestimmen Sie anschließend die Optionen für den Abruf der Webseite. Dazu gehört auch das Festlegen der URL und der HTTP-Methode (GET, POST usw.) mit der Funktion curl_setopt:

curl_setopt($ch, CURLOPT_URL, 'https://en.wikipedia.org/wiki/List_of_countries_by_population_(United_Nations)');

curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

In diesem Code legen Sie als Ziel-URL die Webseite und als Methode GET fest. CURLOPT_RETURNTRANSFER weist curl zur Rückgabe der HTML-Antwort an.

Sobald curl bereit ist, können Sie mithilfe von curl_exec die Anfrage stellen:

$response = curl_exec($ch);

Das Abrufen der HTML-Daten ist beim Web Scraping lediglich der erste Schritt. Um Daten aus der HTML-Antwort zu extrahieren, sind mehrere Techniken anzuwenden. Die einfachste Methode besteht darin, reguläre Ausdrücke für eine sehr einfache HTML-Extraktion zu verwenden. Bitte beachten Sie jedoch, dass Sie mit regex kein beliebiges HTML parsen können (für sehr einfaches Parsen reicht regex jedoch aus).

Extrahieren Sie z. B. die <a>-Tags, die die Attribute href und title aufweisen und ein <span> enthalten:

if(! empty($ch)) {
    preg_match_all(
        '/<a href="([^"]*)" title="([^"]*)"><span>([^<]*)<\/span><\/a>/',
        $response, $matches, PREG_SET_ORDER
    );
    foreach($matches as $link) {
        echo $link[1] . " => " . $link[3] . "\n";
    }
}

Danach geben Sie mit der Funktion curl_close die Ressourcen frei:

curl_close($ch);

Führen Sie den Code wie folgt aus:

php curl.php

Die korrekte Extraktion der Links sollte angezeigt werden:

php curl.php

Mit curl steuern Sie auf einer sehr niedrigen Ebene, wie eine Webseite über HTTP/HTTPS abgerufen werden soll. Sie können die einzelnen Verbindungseinstellungen genau anpassen und sogar zusätzliche Maßnahmen wie Proxy-Server (mehr dazu später), User-Agents und Timeouts hinzufügen.  

Darüber hinaus ist curl in den meisten Betriebssystemen standardmäßig installiert, weshalb es sich hervorragend für das Schreiben eines plattformübergreifenden Web Scrapers eignet.

Wie Sie gesehen haben, reicht curl allein jedoch nicht aus und Sie benötigen zum korrekten Scrapen von Daten einen HTML-Parser. Da curl auch kein JavaScript auf einer Webseite ausführen kann, können Sie mit curl keine dynamischen Webseiten und Single-Page-Anwendungen (SPAs) scrapen.

file_get_contents

Die Funktion file_get_contents wird in erster Linie zum Lesen des Inhalts einer Datei verwendet. Durch Übergabe einer HTTP-URL können Sie allerdings auch HTML-Daten von einer Webseite abrufen. Somit kann file_get_contents die Verwendung von curl im vorherigen Code ersetzen.

In diesem Abschnitt scrapen Sie die gleiche Seite wie zuvor, doch dieses Mal ist der Scraper fortgeschrittener und Sie können die Namen aller Länder aus der Tabelle extrahieren.

Erstellen Sie eine Datei mit dem Namen file_get-contents.php und beginnen Sie mit der Übergabe einer URL an file_get_contents:

<?php

$html = file_get_contents('https://en.wikipedia.org/wiki/List_of_countries_by_population_(United_Nations)');

Die Variable $html enthält nun den HTML-Code der Webseite.

Ganz ähnlich wie im vorherigen Beispiel ist das Abrufen der HTML-Daten lediglich der erste Schritt. Um das Ganze etwas aufzupeppen, wählen Sie mithilfe von libxml Elemente mit XPath-Selektoren aus. Hierfür müssen Sie zunächst ein DOMDocument initialisieren und den HTML-Code in dieses Dokument laden:

$doc = new DOMDocument;
libxml_use_internal_errors(true);
$doc->loadHTML($html);
libxml_clear_errors();

Hier wählen Sie die Länder in folgender Reihenfolge aus: das erste tbody-Element, ein tr-Element innerhalb des tbody-Elements, das erste td-Element im tr-Element sowie ein a mit title-Attribut innerhalb des tdElements.

Der folgende Code initialisiert eine DOMXpath-Klasse und verwendet evaluate, um das Element mithilfe des XPath-Selektors auszuwählen:

$xpath = new DOMXpath($doc);

$countries = $xpath->evaluate('(//tbody)[1]/tr/td[1]//a[@title=true()]');

Jetzt müssen Sie nur noch eine Schleife über die Elemente ziehen und folgenden Text ausgeben:

foreach($countries as $country) {
    echo $country->textContent . "\n";
}

Führen Sie den Code wie folgt aus:

php file_get_contents.php
php file_get_contents.php

Wie Sie sehen, lässt sich file_get_contents einfacher verwenden als curl. Die Funktion wird häufig eingesetzt, um den HTML-Code einer Webseite schnell abzurufen. Sie weist jedoch die gleichen Nachteile wie curl auf: Man benötigt einen zusätzlichen HTML-Parser und kann keine dynamischen Webseiten und SPAs scrapen. Darüber hinaus fehlen die in curl verfügbaren Feineinstellungen. Trotzdem stellt die Funktion aufgrund ihrer Einfachheit eine gute Wahl zum Scrapen einfacher statischer Websites dar.

Symfony BrowserKit

Symfony BrowserKit ist eine Komponente des Symfony-Frameworks, die das Verhalten eines echten Browsers simuliert. Sie können daher mit der Webseite wie in einem echten Browser interagieren, z. B. auf Schaltflächen/Links klicken, Formulare absenden und im Verlauf vor und zurück springen.

In diesem Abschnitt werden Sie den Blog von Bright Data aufrufen, PHP in das Suchfeld eingeben und das Suchformular absenden. Anschließend scrapen Sie die Titel der einzelnen Artikel aus dem Suchergebnis:  

Blog von Bright Data, Abschnitt PHP

Um Symfony BrowserKit verwenden zu können, müssen Sie zusammen mit Composer die Komponente BrowserKit installieren:

composer require symfony/browser-kit

Zudem muss die Komponente HttpClient installiert werden, um HTTP-Anfragen über das Internet tätigen zu können:

composer require symfony/http-client

BrowserKit unterstützt standardmäßig die Auswahl von Elementen mithilfe von XPath-Selektoren. In diesem Beispiel verwenden wir CSS-Selektoren. Installieren Sie hierfür auch die Komponente CssSelector:

composer require symfony/css-selector

Erstellen Sie eine Datei mit dem Namen symfony-browserkit.php. Initialisieren Sie in dieser Datei den HttpBrowser:

<?php
require "vendor/autoload.php";

use Symfony\Component\BrowserKit\HttpBrowser;

$client = new HttpBrowser();

Verwenden Sie die Funktion request, um eine GET-Anfrage zu stellen:

$crawler = $client->request('GET', 'https://brightdata.com/blog');

Zur Auswahl des Formulars, in dem sich die Suchschaltfläche befindet, müssen Sie die Schaltfläche selbst auswählen und die Funktion form verwenden, um das umschließende Formular zu erhalten. Die Schaltfläche kann über die Funktion filter und Übergabe ihrer ID ausgewählt werden. Nach der Auswahl des Formulars können Sie dieses mithilfe der Funktion submit der Klasse Httpbrowser absenden.

Durch Übergabe eines Hashes der Eingabewerte kann die Funktion submit das Formular vor dem Absenden ausfüllen. Im folgenden Code wurde der Eingabe mit dem Namen q der Wert PHP zugewiesen, was der Eingabe von PHP in das Suchfeld entspricht:

$form = $crawler->filter('#blog_search')->form();

$crawler = $client->submit($form, ['q' => 'PHP']);

Die Funktion submit liefert die resultierende Webseite. Von dort aus können Sie die Titel der Artikel mithilfe des CSS-Selektors .col-md-4.mb-4 h5 extrahieren:

$crawler->filter(".col-md-4.mb-4 h5")->each(function ($node) {
    echo $node->text() . "\n";
});

Führen Sie den Code wie folgt aus:

php symfony-browserkit.php
php symfony-browserkit.php

Obwohl Symfony BrowserKit hinsichtlich der Interaktion mit Webseiten einen Fortschritt gegenüber den beiden vorherigen Methoden darstellt, ist es dennoch begrenzt, da es kein JavaScript ausführen kann. Dies bedeutet, dass mit BrowserKit keine dynamischen Websites und SPAs gescrapt werden können.

Symfony Panther

Symfony Panther ist eine weitere Symfony-Komponente, die die BrowserKit-Komponente umschließt. Symfony Panther bietet allerdings einen entscheidenden Vorteil: Anstatt einen Browser zu simulieren, führt es den Code unter Verwendung des WebDriver-Protokolls zur Fernsteuerung eines echten Browsers in einem realen Browser aus. Somit können Sie jede Website – einschließlich dynamischer Websites und SPAs – scrapen.

In diesem Abschnitt laden Sie die OpenWeather-Startseite, geben den Namen Ihrer Stadt in das Suchfeld ein, führen die Suche durch und rufen das aktuelle Wetter für Ihre Stadt ab:

OpenWeather-Startseite

Zunächst muss der Symfony Panther mit Composer installiert werden:

composer require symfony/panther

Darüber hinaus müssen Sie dbrekelmans/browser-driver-installer installieren, damit der auf Ihrem System installierte Browser automatisch erkannt und der richtige Treiber dafür installiert wird. Bitte stellen Sie sicher, dass auf Ihrem System entweder ein Firefox- oder ein Chromium-basierter Browser installiert ist:

composer require dbrekelmans/bdi

Um den geeigneten Treiber im Verzeichnis drivers zu installieren, führen Sie das Tool bdi aus:

vendor/bin/bdi detect drivers

Erstellen Sie eine Datei mit dem Namen symfony-panther.php und initialisieren Sie zunächst einen Panther-Client:

<?php
require 'vendor/autoload.php';

use Symfony\Component\Panther\Client;


$client = Client::createFirefoxClient();

Hinweis: Bei manchen Browsern muss anstelle von createFirefoxClient createChromeClient bzw. createSeleniumClient verwendet werden.

Da Panther im Hintergrund Symfony BrowserKit nutzt, sind die nächsten Codes dem Code im Abschnitt Symfony BrowserKit recht ähnlich.

Laden Sie zunächst die Webseite mithilfe der Funktion request. Beim Laden der Seite wird diese anfangs von einem div mit der Klasse owm-loader überdeckt, das den Ladebalken anzeigt. Bevor Sie mit der Seite interagieren können, müssen Sie warten, bis dieses div verschwunden ist. Hierfür kann die Funktion waitForStaleness verwendet werden, die einen CSS-Selektor nutzt und darauf wartet, dass dieser aus dem DOM entfernt wird.

Nach dem Verschwinden des Ladebalkens müssen Sie die Cookies akzeptieren, damit die Cookie-Leiste geschlossen wird. Dazu bietet sich die Funktion selectButton an, die eine Schaltfläche anhand ihres Textes suchen kann. Sobald die Schaltfläche erscheint, führt die Funktion click einen Klick auf sie aus:

$client->request('GET', 'https://openweathermap.org/');
try {
    $crawler = $client->waitForStaleness(".owm-loader");
} catch (Facebook\WebDriver\Exception\NoSuchElementException $e) {

}
$crawler->selectButton('Allow all')->click();

Hinweis: Je nachdem, wie schnell die Seite geladen wird, kann der Ladebalken verschwinden, bevor die Funktion waitForStaleness ausgeführt wird. Dadurch wird eine Ausnahme ausgelöst. Aus diesem Grund wurde diese Zeile in einen try-catch-Block eingeschlossen.

OpenWeather

Geben Sie nun Kolkata in die Suchleiste ein. Wählen Sie die Suchleiste mithilfe der Funktion filter aus und verwenden Sie die Funktion sendKeys, um Eingaben in der Suchleiste zu machen. Klicken Sie anschließend auf die Schaltfläche Search:

$crawler->filter('input[placeholder="Search city"]')->sendKeys('Kolkata');
$crawler->selectButton('Search')->click();

Sobald die Schaltfläche angeklickt wird, öffnet sich ein Feld mit Vorschlägen zur automatischen Vervollständigung. Sie können die Funktion waitForVisibility um zu warten, bis die Liste mit Vorschlägen angezeigt wird, und dann auf das erste Element klicken, indem Sie wie zuvor die Kombination aus filter und click anwenden:

$crawler = $client->waitForVisibility(".search-dropdown-menu li");
$crawler->filter(".search-dropdown-menu li")->first()->click();

Verwenden Sie abschließend waitForElementToContain, um das Laden der Ergebnisse abzuwarten, und extrahieren Sie die derzeitige Temperatur mithilfe der Funktion filter:

$crawler = $client->waitForElementToContain(".orange-text+h2", "Kolkata");
$temp = $crawler->filter(".owm-weather-icon+span.orange-text+h2")->text();

echo $temp;

Hier warten Sie darauf, dass das Element mit dem Selektor .orange-text+h2 Kolkata enthält. Dies zeigt an, dass die Ergebnisse geladen worden sind.

Führen Sie den Code wie folgt aus:

php symfony-panther.php

Ihre Ausgabe sieht so aus:

Herausforderungen beim Web Scraping und mögliche Lösungen  

Auch wenn PHP das Schreiben von Web Scrapern erleichtert, kann sich die Navigation in realen Scraping-Projekten als recht komplex erweisen. Es können zahlreiche Situationen auftreten, die Probleme aufwerfen, welche es zu lösen gilt. Solche Probleme können durch bestimmte Faktoren wie die Datenstruktur (z. B. die Paginierung) oder auch durch Anti-Bot-Maßnahmen der Website-Betreiber (z. B. Honeypot-Fallen) verursacht werden.  

In diesem Abschnitt erfahren Sie mehr über häufig auftretende Probleme und deren Lösung.

Navigieren auf paginierten Websites

 

Beim Scraping nahezu jeder realen Website tritt vermutlich die Situation ein, dass nicht alle Daten auf einmal geladen werden – mit anderen Worten: Die Daten sind paginiert. Es können zwei Arten der Paginierung auftreten:

  1. Alle Seiten liegen unter separaten URLs. Die Seitenzahl wird mittels eines Abfrageparameters oder eines Pfadparameters (z. B. example.com?page=3 bzw. example.com/page/3) übergeben.
  2. Wenn die Schaltfläche Next angeklickt wird, werden die neuen Seiten mit JavaScript geladen.  

Im ersten Szenario können Sie die Seiten in einer Schleife laden und sie als separate Webseiten scrapen. Beispiel: Unter Verwendung von file_get_contents ruft der folgende Code die ersten zehn Seiten einer Beispielwebsite ab:

for($page = 1; $page <= 10; $page++) {
    $html = file_get_contents('https://example.com/page/{$page}');
    // DO the scraping
}

Im zweiten Szenario benötigen Sie eine Lösung, die JavaScript ausführen kann, wie z. B. Symfony Panther. In diesem Beispiel müssen Sie auf die entsprechende Schaltfläche klicken, damit die nächste Seite geladen wird. Vergessen Sie nicht, etwas abzuwarten, bis die neue Seite geladen ist:

for($page = 1; $page <= 10; $page++>) {
    // Do the scraping

    // Load the next page
    $crawler->selectButton("Next")->click();
    $client->waitForElementToContain(".current-page", $page+1)
}

Hinweis: Sie sollten eine geeignete Wartelogik einsetzen, die für die jeweilige auszulesende Website sinnvoll ist.  

Rotierende Proxys

 

Ein Proxy-Server fungiert als Vermittler zwischen Ihrem Computer und dem Ziel-Webserver. Er verhindert, dass der Webserver Ihre IP-Adresse erkennt, und gewährleistet auf diese Weise Ihre Anonymität.

Sie sollten sich jedoch nicht auf einen einzigen Proxy-Server verlassen, da dieser gesperrt werden könnte. Stattdessen sollten Sie mehrere Proxyserver verwenden und diese abwechselnd nutzen. Der folgende Code bietet Ihnen eine einfache Lösung, bei der ein Array von Proxys verwendet und einer davon nach dem Zufallsprinzip ausgewählt wird:  

$proxy      =   array();
$proxy[]    =   '1.2.3.4';
$proxy[]    =   '5.6.7.8';

// Add more proxies

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://example.com");
curl_setopt($ch, CURLOPT_PROXY, $proxy[array_rand($proxy)]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15);


$result =   curl_exec($ch);
curl_close($ch);

Umgang mit CAPTCHAs

 

Viele Websites verwenden CAPTCHAs, um sicherzustellen, dass es sich beim Benutzer um einen Menschen und nicht um einen Bot handelt. Bedauerlicherweise bedeutet dies, dass Ihr Web Scraper entdeckt werden kann.

Es gibt einerseits sehr einfache CAPTCHAs, wie z. B. ein simples Kontrollkästchen mit der Frage „Sind Sie ein Mensch?“, und andererseits fortgeschrittene Algorithmen wie hCaptcha oder reCAPTCHA von Google. Einfache CAPTCHAs können Sie vermutlich mithilfe simpler Webseitenmanipulationen (z. B. Ankreuzen eines Kontrollkästchens) lösen, doch für fortgeschrittene CAPTCHAs wird ein spezielles Tool wie 2Captcha benötigt. 2Captcha ist ein menschenbasierter Anti-CAPTCHA-Dienst. Sie müssen lediglich die erforderlichen Angaben an die 2Captcha-API weitergeben, und schon wird das gelöste CAPTCHA zurückgegeben.  

Um 2Captcha nutzen zu können, müssen Sie ein Konto anlegen und einen API-Schlüssel anfordern.

Installieren Sie 2Captcha mit Composer:

composer require 2captcha/2captcha

Legen Sie in Ihrem Code eine Instanz von TwoCaptcha an:

$solver = new \TwoCaptcha\TwoCaptcha('YOUR_API_KEY');

Anschließend können Sie 2Captcha zum Lösen von CAPTCHAs verwenden:

// Normal captcha
$result = $solver->normal('path/to/captcha.jpg');

// ReCaptcha
$result = $solver->recaptcha([
    'sitekey' => '6Le-wvkSVVABCPBMRTvw0Q4Muexq1bi0DJwx_mJ-',
    'url'   => 'https://mysite.com/page/with/recaptcha',
    'version' => 'v3',
]);

// hCaptcha

$result = $solver->hcaptcha([
    'sitekey'   => '10000000-ffff-ffff-ffff-000000000001',
    'url'       => 'https://www.site.com/page/',
]);

Vermeiden von Honeypot-Fallen

 

Honeypot-Fallen dienen der Bot-Abwehr. Sie imitieren einen Dienst oder ein Netzwerk, um Scraper und Crawler anzulocken und sie von ihrem eigentlichen Ziel abzulenken. Wenngleich Honeypots zur Vorbeugung gegen Bot-Angriffe nützlich sind, können sie sich in Sachen Web Scraping als problematisch erweisen. Sie möchten schließlich nicht, dass Ihr Scraper in einem Honeypot festsitzt!

Es gibt allerlei Maßnahmen, die Sie ergreifen können, um nicht in eine Honeypot-Falle zu geraten. So sind zum Beispiel Honeypot-Links oftmals versteckt, so dass sie ein echter Benutzer nicht sieht, ein Bot sie aber aufspüren kann. Um diese Falle zu umgehen, ist es ratsam, nicht auf versteckte Links zu klicken (Links mit den CSS-Eigenschaften display: none oder visibility: none).

Zudem können Sie die rotierende Proxys nutzen, so dass Sie sich auch dann noch über andere Proxys verbinden können, wenn eine der IP-Adressen des Proxy-Servers im Honeypot gefangen und blockiert ist.

Fazit

Dank der überragenden Bibliotheken und der ausgefeilten Frameworks von PHP lassen sich Web Scraper ganz einfach erstellen. In diesem Artikel haben Sie Folgendes gelernt:

  • Scrapen einer statischen Website mithilfe von curl und regex
  • Scrapen einer statischen Website mithilfe von file_get_contents und libxml
  • Scrapen einer statischen Website mithilfe von Symfony BrowserKit und Eingabeformularen
  • Scrapen einer komplexen dynamischen Website mithilfe von Symfony Panther

Zudem haben Sie erfahren, dass das Web Scraping mit PHP bei Anwendung der beschriebenen Methoden unglücklicherweise mit zusätzlichen Anforderungen verbunden ist. So müssen Sie z. B. unter Umständen mehrere Proxys einrichten und Ihren Scraper sorgfältig konstruieren, um Honeypots zu umgehen.

Wenn Sie sich nicht weiter mit diesen komplexen Aspekten befassen möchten, dann empfehlen wir Ihnen den umfassenden Web-Scraping-Service Bright Data Web Scraper IDE. Dieser hilft Ihnen dabei, Websites ganz mühelos zu scrapen.