Zurück zum Blog
Anleitungen
Sorin-Gabriel MaricaLast updated on Mar 31, 202616 min read

Web Scraping mit PHP: Ein praktischer Leitfaden für Bibliotheken, Code und bewährte Praktiken

Web Scraping mit PHP: Ein praktischer Leitfaden für Bibliotheken, Code und bewährte Praktiken
Kurz gesagt: PHP eignet sich dank integrierter Erweiterungen wie cURL und DOMDocument sowie eines umfangreichen Composer-Ökosystems, zu dem Guzzle, Symfony DomCrawler und Symfony Panther für das Headless-Browsing gehören, hervorragend für das Web-Scraping. Dieser Leitfaden führt Sie durch den gesamten Arbeitsablauf: Abrufen von Seiten, Parsen von HTML, Speichern der Ergebnisse in CSV/JSON/MySQL, Fehlerbehandlung und Umgehen von Blockierungen.

Web-Scraping mit PHP ist der Prozess des programmgesteuerten Abrufens von Webseiten und des Extrahierens strukturierter Daten aus deren HTML mithilfe von PHP-Skripten und -Bibliotheken. Wenn Sie bereits in Ihrem Beruf PHP programmieren, gibt es keinen Grund, die Sprache zu wechseln, nur um Daten von Websites abzurufen. PHP wird standardmäßig mit cURL-Bindungen und einem integrierten DOM-Parser ausgeliefert, und Composer bietet Ihnen Zugriff auf bewährte HTTP-Clients, CSS-Selektor-Engines und sogar Headless-Browser.

Dieses Tutorial richtet sich an fortgeschrittene PHP-Entwickler, die eine praktische, codeorientierte Anleitung suchen. Sie beginnen mit Low-Level-cURL-Aufrufen, steigen auf höhere Bibliotheken wie Guzzle und Symfony HttpBrowser um, bearbeiten JavaScript-gerenderte Seiten mit Symfony Panther und schließen mit produktionsrelevanten Aspekten wie Datenspeicherung, Fehlerbehandlung und dem Vermeiden von Blocklisten ab. Jedes Beispiel in diesem PHP-Web-Scraping-Tutorial durchläuft ein einziges Szenario (das Scraping einer öffentlichen Buchliste), sodass du den gesamten Workflow von Anfang bis Ende verfolgen kannst, anstatt zwischen unzusammenhängenden Code-Schnipseln hin und her zu springen.

Warum PHP eine gute Wahl für Web-Scraping ist

PHP ist vielleicht nicht die erste Sprache, die einem in den Sinn kommt, wenn man an Scraping denkt, aber sie bietet mehrere praktische Vorteile. Erstens: Wenn Ihr bestehender Stack bereits auf PHP läuft, bedeutet das Hinzufügen eines Scrapers keinerlei neue Laufzeitabhängigkeiten. Ihr Team kann den Code pflegen, Ihre Deployment-Pipeline bleibt unverändert, und Sie vermeiden den kognitiven Aufwand eines Kontextwechsels zu einer anderen Sprache.

Zweitens eignen sich die integrierten Erweiterungen von PHP überraschend gut für diese Aufgabe. Die curl Erweiterung verarbeitet HTTP-Anfragen, dom und libxml bietet Ihnen einen standardkonformen HTML/XML-Parser und mbstring kümmert sich um die Probleme mit der Zeichenkodierung. Für ein einfaches Scraping müssen Sie nichts zusätzlich installieren.

Drittens füllt das Composer-Ökosystem jede verbleibende Lücke. Guzzle bietet einen modernen HTTP-Client mit Middleware-Unterstützung. Symfony DomCrawler erweitert DOMDocument um CSS-Selektor-Abfragen. Symfony Panther steuert eine echte Chrome- oder Firefox-Instanz für JavaScript-lastige Seiten an. Die Tools sind ausgereift und werden aktiv gepflegt.

Wie sieht es mit PHP vs. Python beim Scraping aus? Python verfügt über eine größere Scraping-spezifische Community und Bibliotheken wie Beautiful Soup und Scrapy, aber das macht PHP nicht zu einer schlechten Wahl. Wenn PHP Ihre stärkste Sprache ist, werden Sie einen funktionierenden Scraper schneller schreiben als in einer Sprache, die Sie noch lernen. Die beste Scraping-Sprache ist die, die Sie um 2 Uhr morgens debuggen können.

PHP-Scraping-Bibliotheken im Überblick

Bevor Sie Code schreiben, ist es hilfreich zu wissen, welche Tools es gibt und wann Sie welches einsetzen sollten. Die folgende Tabelle vergleicht die wichtigsten PHP-Scraping-Bibliotheken anhand der wichtigsten Kriterien: was sie leisten, ob sie JavaScript verarbeiten und wie viel Aufwand das Erlernen erfordert.

Bibliothek / Tool

Zweck

JS-Unterstützung

Lernkurve

Wartungsstatus

cURL (ext-curl)

Low-Level-HTTP-Anfragen

Nein

Niedrig

Integriert, immer verfügbar

Guzzle

HTTP-Client mit Middleware, asynchron

Nein

Niedrig–Mittel

Aktiv gepflegt

DOMDocument + DOMXPath

HTML/XML-Parsing, XPath-Abfragen

Nein

Mittel

Integriert

Symfony DomCrawler

CSS-Selektoren und XPath-Abfragen

Nein

Gering

Aktiv gepflegt

Goutte (veraltet)

Kombiniertes HTTP- und DOM-Crawling

Nein

Niedrig

Veraltet, bitte HttpBrowser verwenden

Symfony HttpBrowser

Nachfolger von Goutte, gleiche API

Nein

Niedrig

Wird aktiv gepflegt

Symfony Panther

Headless-Browser (Chrome/Firefox)

Ja

Mittel–Hoch

Aktiv gepflegt

Scraping-API-Dienst

Verwaltete Anfrage + Parsing-Ebene

Hängt vom Anbieter ab

Sehr niedrig

Extern verwaltet

Ein paar Dinge sind zu beachten. Goutte war jahrelang die erste Wahl unter den „All-in-One“-Scraping-Bibliotheken, wurde jedoch als veraltet eingestuft. Zum Zeitpunkt der Erstellung dieses Artikels ist der empfohlene Migrationspfad Symfony HttpBrowser, der eine nahezu identische API bereitstellt, die auf den Symfony-Komponenten BrowserKit und HttpClient basiert. Wenn Sie ein neues Projekt starten, lassen Sie Goutte ganz weg und steigen Sie direkt auf HttpBrowser um.

Für die meisten Scraping-Aufgaben mit statischen Seiten ist Guzzle (zum Abrufen) in Kombination mit Symfony DomCrawler (zum Parsen) eine solide, ressourcenschonende Lösung. Reservieren Sie Symfony Panther für Seiten, die tatsächlich die Ausführung von JavaScript erfordern, da das Starten eines Headless-Browsers deutlich langsamer und ressourcenintensiver ist.

Einrichten Ihrer PHP-Scraping-Umgebung

Klären wir zunächst die Voraussetzungen. Du benötigst PHP 8.1 oder neuer (für Enum- und Fiber-Unterstützung in modernen Bibliotheken), Composer und eine Handvoll Erweiterungen.

Überprüfen Sie Ihre PHP-Version und die geladenen Erweiterungen:

php -v
php -m | grep -E 'curl|dom|mbstring|json'

Falls eine dieser vier Erweiterungen fehlt, aktivieren Sie sie in Ihrer php.ini oder installieren Sie sie über Ihren System-Paketmanager (zum Beispiel sudo apt install php-curl php-xml php-mbstring unter Debian/Ubuntu).

Als Nächstes initialisieren Sie ein Projektverzeichnis und laden die Bibliotheken, die Sie in diesem Tutorial verwenden werden:

mkdir php-scraper && cd php-scraper
composer init --no-interaction
composer require guzzlehttp/guzzle symfony/dom-crawler symfony/css-selector symfony/browser-kit symfony/http-client

Diese eine composer require Zeile stellt Ihnen Guzzle für HTTP, DomCrawler für das Parsing und Symfony HttpBrowser für den kombinierten Crawling-Workflow zur Verfügung. Wir werden später Symfony Panther hinzufügen, wenn wir Unterstützung für Headless-Browser benötigen.

Erstellen Sie eine scrape.php Datei und fügen Sie den Composer-Autoloader oben ein:

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

Sie sind bereit, Ihre erste Seite abzurufen.

Seiten abrufen mit cURL

Die cURL-Erweiterung von PHP ist das grundlegendste HTTP-Tool in Ihrem Werkzeugkasten. Sie ist zwar ausführlich, bietet Ihnen aber volle Kontrolle über jedes Detail der Anfrage, was nützlich ist, wenn Sie einen bestimmten Browser-Fingerabdruck nachahmen oder Verbindungsprobleme debuggen müssen.

Hier ist eine einfache GET-Anfrage, die die Startseite eines öffentlichen Buchkatalogs abruft (wir verwenden http://books.toscrape.com als unser Demo-Ziel verwenden):

$ch = curl_init();
curl_setopt_array($ch, [
    CURLOPT_URL            => 'http://books.toscrape.com/',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_HTTPHEADER     => [
        'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
        'Accept-Language: en-US,en;q=0.9',
    ],
    CURLOPT_TIMEOUT        => 30,
    CURLOPT_COOKIEJAR      => '/tmp/cookies.txt',
    CURLOPT_COOKIEFILE     => '/tmp/cookies.txt',
]);

$html = curl_exec($ch);

if (curl_errno($ch)) {
    echo 'cURL error: ' . curl_error($ch);
}

curl_close($ch);

Ein paar Dinge, die es zu beachten gilt. CURLOPT_COOKIEJAR und CURLOPT_COOKIEFILE ermöglichen die Persistenz von Cookies über mehrere Anfragen hinweg, was für mehrstufige Scraping-Abläufe unerlässlich ist, bei denen der Server den Sitzungsstatus verfolgt. Das Setzen eines realistischen User-Agent Header lässt Ihre Anfrage wie gewöhnlichen Browser-Traffic aussehen und nicht wie ein bloßes PHP-Skript. Und CURLOPT_FOLLOWLOCATION behandelt 301/302-Weiterleitungen automatisch, sodass Sie diese nicht manuell nachverfolgen müssen.

Für eine POST-Anfrage (z. B. das Absenden eines Suchformulars) tauschen Sie CURLOPT_POST => true und fügen Sie CURLOPT_POSTFIELDS durch Ihre Formulardaten. Der Rest des Boilerplate-Codes bleibt unverändert.

cURL funktioniert zwar, ist aber so low-level, dass du am Ende Wrapper für Header, Wiederholungsversuche und Fehlerbehandlung schreiben musst. Hier kommt Guzzle ins Spiel.

Seiten mit Guzzle abrufen

Guzzle verpackt die cURL- (oder Stream-)Schicht von PHP in eine saubere, objektorientierte API. Installiere es über Composer, falls du das noch nicht getan hast, und rufe dann dieselbe Seite ab:

use GuzzleHttp\Client;

$client = new Client([
    'timeout' => 30,
    'headers' => [
        'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
        'Accept-Language' => 'en-US,en;q=0.9',
    ],
]);

$response = $client->get('http://books.toscrape.com/');
$html = (string) $response->getBody();

Das ist deutlich weniger Boilerplate-Code. Guzzle bietet Ihnen außerdem Middleware-Hooks für Logging, Wiederholungslogik und Header-Injektion, was bedeutet, dass Sie querschnittliche Aspekte zentralisieren können, anstatt curl_setopt Aufrufe überall zu verstreuen.

Parallele Anfragen mit Guzzle-Promises

Wenn du mehrere Seiten scrapen musst, ist das nacheinander Absenden von Anfragen quälend langsam. Guzzle unterstützt Promise-basierte Parallelität durch seine Pool , mit der Sie mehrere Anfragen parallel senden und gleichzeitig den Grad der Parallelität steuern können.

use GuzzleHttp\Client;
use GuzzleHttp\Pool;
use GuzzleHttp\Psr7\Request;

$client = new Client(['timeout' => 30]);

$urls = [
    'http://books.toscrape.com/catalogue/page-1.html',
    'http://books.toscrape.com/catalogue/page-2.html',
    'http://books.toscrape.com/catalogue/page-3.html',
];

$requests = function () use ($urls) {
    foreach ($urls as $url) {
        yield new Request('GET', $url);
    }
};

$pool = new Pool($client, $requests(), [
    'concurrency' => 5,
    'fulfilled'   => function ($response, $index) {
        echo "Page $index fetched: " . $response->getStatusCode() . "\n";
    },
    'rejected'    => function ($reason, $index) {
        echo "Page $index failed: " . $reason->getMessage() . "\n";
    },
]);

$pool->promise()->wait();

Bei einem Parallelitätsgrad von 5 sendet Guzzle bis zu fünf Anfragen gleichzeitig, anstatt auf die Fertigstellung jeder einzelnen zu warten. Bei einem Scrape von 50 Seiten kann dies die Gesamtlaufzeit von Minuten auf Sekunden reduzieren. Laut der Guzzle-Dokumentation zu parallelen Anfragen nutzt die Pool-API intern cURLs Multi-Handle, sodass der Leistungsgewinn real ist und nicht nur syntaktischer Zucker.

HTML-Parsing: DOMDocument und XPath

Sobald Sie rohen HTML-Code in einer Zeichenkette haben, müssen Sie strukturierte Daten daraus extrahieren. Die in PHP integrierte DOMDocument lädt HTML in eine Baumstruktur und DOMXPath erlaubt es Ihnen, diesen Baum mit XPath-Ausdrücken abzufragen.

libxml_use_internal_errors(true); // suppress malformed-HTML warnings

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

$xpath = new DOMXPath($doc);

// Select every book title on the page
$titles = $xpath->query('//article[@class="product_pod"]//h3/a/@title');

foreach ($titles as $node) {
    echo $node->nodeValue . "\n";
}

Der libxml_use_internal_errors(true) Aufruf ist wichtig. Reales HTML ist fast nie gültiges XML, und ohne dieses Flag gibt PHP für jeden nicht geschlossenen Tag oder jedes nicht übereinstimmende Attribut Warnungen aus. Durch deren Unterdrückung kannst du unordentliche Seiten parsen, ohne dass deine Logs überflutet werden.

XPath ist leistungsstark für komplexe Abfragen. Möchten Sie alle Bücher unter 20 £ abrufen? Sie können Achsen und Prädikate kombinieren:

$products = $xpath->query('//article[@class="product_pod"]');

foreach ($products as $product) {
    $title = $xpath->query('.//h3/a/@title', $product)->item(0)->nodeValue;
    $price = $xpath->query('.//p[@class="price_color"]', $product)->item(0)->textContent;

    $numericPrice = (float) str_replace('£', '', $price);
    if ($numericPrice < 20.00) {
        echo "$title: $price\n";
    }
}

DOMDocument plus XPath gibt dir volle Kontrolle und keinerlei externe Abhängigkeiten. Der Nachteil ist die Ausführlichkeit: Selbst eine einfache Abfrage erfordert mehrere Zeilen zur Einrichtung. Hier macht sich Symfony DomCrawler bezahlt.

HTML-Parsing: Symfony DomCrawler und CSS-Selektoren

Symfony DomCrawler baut auf DOMDocument auf, bietet aber eine viel benutzerfreundlichere API. Anstatt XPath von Hand zu schreiben, kannst du CSS-Selektoren (die die meisten Webentwickler bereits kennen) verwenden und Methoden im jQuery-ähnlichen Stil verketten.

use Symfony\Component\DomCrawler\Crawler;

$crawler = new Crawler($html);

$crawler->filter('article.product_pod')->each(function (Crawler $node) {
    $title = $node->filter('h3 a')->attr('title');
    $price = $node->filter('.price_color')->text();
    echo "$title: $price\n";
});

Vergleichen Sie das mit der obigen DOMXPath-Version. Die Absicht ist identisch, aber der DomCrawler-Code ist halb so lang und leichter zu lesen. Die filter() Methode akzeptiert jeden gültigen CSS-Selektor, text() gibt den Textinhalt zurück und attr() extrahiert einen Attributwert.

Wann sollten Sie beim Scraping CSS-Selektoren statt XPath verwenden? CSS-Selektoren decken 90 % der praktischen Fälle ab und sind für jeden, der Frontend-Code schreibt, intuitiver. XPath ist die bessere Wahl, wenn Sie nach oben navigieren müssen (ein übergeordnetes Element basierend auf dem Text eines untergeordneten Elements auswählen), String-Funktionen innerhalb der Abfrage ausführen oder zwischen gleichrangigen Elementen navigieren möchten. Eine gute Faustregel: Beginnen Sie mit CSS-Selektoren und greifen Sie nur dann auf XPath zurück, wenn CSS nicht ausdrücken kann, was Sie benötigen.

Warum Regex für das HTML-Parsing riskant ist

Es ist verlockend, zu preg_match() , wenn man nur einen Wert von einer Seite benötigt. Widerstehen Sie diesem Drang. HTML ist keine reguläre Sprache, und die Regex-basierte Extraktion versagt in dem Moment, in dem sich das Markup auf triviale Weise ändert: ein neues Attribut, ein geänderter Anführungsstil oder zusätzliche Leerzeichen.

// Fragile — breaks if class order changes or attributes are added
preg_match('/<h3 class="title">(.+?)<\/h3>/', $html, $match);

Ein DOM-Parser geht mit all diesen Variationen problemlos um. Sparen Sie sich Regex für wirklich flachen Text (Logdateien, CSV-Zeilen) auf und verwenden Sie DOMDocument oder DomCrawler für alles, was aus einem HTML-Dokument stammt.

Erstellen eines vollständigen Scrapers mit Goutte und seinem Nachfolger

Goutte war die Bibliothek, die das Web-Scraping mit PHP zugänglich machte. Sie kombinierte den HTTP-Client von Guzzle mit Symfonys DomCrawler in einer einzigen Klasse, sodass man mit einem einzigen Aufruf Daten abrufen und parsen konnte. Goutte wurde jedoch offiziell als veraltet eingestuft. Die Betreuer empfehlen die Migration zu Symfony HttpBrowser, das als Teil der Symfony-BrowserKit-Komponente ausgeliefert wird und eine fast identische API bietet.

Hier ist ein vollständiger Scraper, der mit Symfony HttpBrowser erstellt wurde und Buchlisten über mehrere Seiten hinweg abruft:

use Symfony\Component\HttpClient\HttpClient;
use Symfony\Component\BrowserKit\HttpBrowser;

$browser = new HttpBrowser(HttpClient::create([
    'headers' => [
        'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
    ],
]));

$books = [];
$url = 'http://books.toscrape.com/catalogue/page-1.html';

while ($url) {
    $crawler = $browser->request('GET', $url);

    $crawler->filter('article.product_pod')->each(function ($node) use (&$books) {
        $books[] = [
            'title' => $node->filter('h3 a')->attr('title'),
            'price' => $node->filter('.price_color')->text(),
            'stock' => trim($node->filter('.availability')->text()),
        ];
    });

    // Follow the "next" pagination link, or stop
    $nextLink = $crawler->filter('li.next a');
    $url = $nextLink->count() > 0
        ? 'http://books.toscrape.com/catalogue/' . $nextLink->attr('href')
        : null;
}

echo count($books) . " books collected.\n";

Beachten Sie, wie die Paginierungslogik funktioniert. Nach dem Parsen jeder Seite prüft der Scraper, ob ein „Weiter“-Link vorhanden ist. Ist dies der Fall, folgt der Scraper diesem Link und wiederholt den Vorgang. Ist dies nicht der Fall, $url wird null und die Schleife wird beendet. Dieses Muster ist für jede paginierte Liste wiederverwendbar.

Die Umstellung von Goutte ist minimal. Wenn Ihr bestehender Code $goutte = new \Goutte\Client(), ersetzen Sie ihn durch $browser = new HttpBrowser(HttpClient::create()). Das request(), filter(), und selectLink() bleiben unverändert. Die zugrunde liegende HTTP-Schicht wechselt von Guzzle zu Symfony HttpClient, was Ihnen native Async-Unterstützung und eine bessere Integration in das übrige Symfony-Ökosystem bietet.

Ein weiterer Vorteil von HttpBrowser: Er verfolgt Cookies und Sitzungen automatisch über mehrere Anfragen hinweg. Wenn Sie $browser->request() mehrfach aufrufen, verhält sich der Client wie eine echte Browsersitzung und überträgt Cookies ohne zusätzliche Konfiguration.

Scraping von JavaScript-gerenderten Seiten mit Symfony Panther

Scraper für statische Seiten versagen, wenn der benötigte Inhalt nach dem ersten Laden der Seite per JavaScript eingefügt wird. Single-Page-Anwendungen, Feeds mit unendlichem Bildlauf und verzögert geladene Produktraster erfordern alle eine echte Browser-Engine zum Rendern. Symfony Panther schließt diese Lücke, indem es Chrome oder Firefox über das WebDriver-Protokoll steuert.

Installieren Sie Panther und eine ChromeDriver-Binärdatei:

composer require symfony/panther
# Panther can auto-detect a locally installed ChromeDriver,
# or you can install one explicitly:
composer require dbrekelmans/bdi
vendor/bin/bdi detect drivers

Scrapen Sie nun eine Seite, die auf dynamische Inhaltsdarstellung mit PHP setzt:

use Symfony\Component\Panther\Client as PantherClient;

$panther = PantherClient::createChromeClient();
$crawler = $panther->request('GET', 'https://example.com/dynamic-page');

// Wait until the data container is visible in the DOM
$panther->waitFor('.results-container', 10);

$crawler->filter('.results-container .item')->each(function ($node) {
    echo $node->filter('.item-title')->text() . "\n";
});

$panther->quit();

Die waitFor() Methode hält die Ausführung an, bis der angegebene CSS-Selektor im gerenderten DOM erscheint, mit einem Timeout (hier 10 Sekunden), um unendliche Hänger zu verhindern. Dies ist für das Scraping dynamischer Inhalte mit PHP unerlässlich, da der benötigte HTML-Code in der ersten Antwort möglicherweise gar nicht vorhanden ist.

Panther ist leistungsstark, aber ressourcenintensiv. Jeder Request startet einen echten Browserprozess, der Speicher und CPU beansprucht. Verwenden Sie es nur, wenn JavaScript-Rendering wirklich erforderlich ist. Bei Seiten, die Daten über einen einfachen XHR-/API-Aufruf laden, ist es oft schneller, diesen API-Endpunkt im Netzwerk-Tab Ihres Browsers zu finden und ihn direkt mit Guzzle anzurufen.

Verwendung einer Scraping-API für die automatisierte Extraktion

Irgendwann übersteigen die technischen Kosten für die Wartung eines eigenen Scrapers (Proxy-Rotation, CAPTCHA-Lösung, Browser-Fingerprinting, Wiederholungslogik) die Kosten für die Auslagerung dieser Infrastruktur an einen dedizierten Dienst. Das ist der ideale Einsatzbereich für eine Scraping-API.

Das Integrationsmuster ist einfach. Sie senden eine URL an den API-Endpunkt, und dieser gibt den HTML-Code der Seite (oder strukturiertes JSON) zurück, wobei die gesamte Anti-Bot-Abwehr serverseitig erfolgt:

$client = new \GuzzleHttp\Client();

$response = $client->get('https://api.webscrapingapi.com/v1', [
    'query' => [
        'api_key' => 'YOUR_API_KEY',
        'url'     => 'http://books.toscrape.com/',
    ],
]);

$html = (string) $response->getBody();
// Parse $html with DomCrawler as usual

Wann ist eine Scraping-API sinnvoller als ein DIY-Ansatz? Ziehen Sie dies in Betracht, wenn Sie in großem Umfang scrapen (Tausende von Seiten pro Tag), auf Websites mit aggressiven Anti-Bot-Abwehrmaßnahmen abzielen oder wenn Ihr Team keine Zeit hat, Proxy-Pools und Browser-Infrastruktur zu warten. Der Kompromiss besteht zwischen den Kosten pro Anfrage und den Entwicklungsstunden.

Ein Managed Service glänzt auch hinsichtlich des Wartungsaufwands. Wenn eine Zielwebsite ihren Anti-Bot-Stack ändert, aktualisiert ein Scraping-API-Anbieter seine Infrastruktur. Ihr Code bleibt unverändert. Wenn Sie Optionen evaluieren, suchen Sie nach einem Anbieter, der nur erfolgreiche Antworten in Rechnung stellt, damit Sie nicht für fehlgeschlagene Anfragen bezahlen.

Speicherung von gescrapten Daten: CSV, JSON und MySQL

Das Sammeln von Daten ist nur die halbe Arbeit. Sie müssen sie in einem Format speichern, das nachgelagerte Prozesse (Analysen, ML-Pipelines, Dashboards) verarbeiten können.

CSV ist die einfachste Option und eignet sich gut für flache, tabellarische Daten:

$fp = fopen('books.csv', 'w');
fputcsv($fp, ['Title', 'Price', 'Stock']); // header row

foreach ($books as $book) {
    fputcsv($fp, [$book['title'], $book['price'], $book['stock']]);
}

fclose($fp);

JSON bewahrt verschachtelte Strukturen und lässt sich leichter in APIs und NoSQL-Speicher importieren:

file_put_contents(
    'books.json',
    json_encode($books, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
);

MySQL über PDO ist die richtige Wahl, wenn Sie einen abfragbaren, relationalen Speicher benötigen:

$pdo = new PDO('mysql:host=127.0.0.1;dbname=scraper', 'user', 'pass', [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);

$stmt = $pdo->prepare(
    'INSERT INTO books (title, price, stock) VALUES (:title, :price, :stock)'
);

foreach ($books as $book) {
    $stmt->execute([
        ':title' => $book['title'],
        ':price' => $book['price'],
        ':stock' => $book['stock'],
    ]);
}

Die Verwendung von Prepared Statements mit PDO ist nicht optional. Sie schützt Sie vor SQL-Injection, die ein echtes Risiko darstellt, wenn benutzergenerierter oder extern gescrapter Text in eine Datenbank eingefügt wird.

Für dokumentorientierte Daten oder Schemata, die sich häufig ändern, ist MongoDB eine weitere praktikable Option. Das mongodb/mongodb Composer-Paket bietet eine unkomplizierte insertMany() Methode, die Arrays aus assoziativen Arrays direkt akzeptiert. Die Wahl zwischen relationalem und dokumentorientiertem Speicher hängt davon ab, wie strukturiert Ihre gescrapten Daten sind und wofür sie verwendet werden sollen.

Fehlerbehandlung, Wiederholungsversuche und Protokollierung

Ein Scraper, der auf Ihrem Laptop funktioniert, ist nicht dasselbe wie ein Scraper, der in der Produktion zuverlässig läuft. Netzwerk-Timeouts, 5xx-Antworten, Verbindungsabbrüche und Rate-Limit-Fehler sind unvermeidlich, wenn Sie Tausende von HTTP-Anfragen stellen. Wenn Sie von Anfang an Ausfallsicherheit in Ihren Scraper einbauen, bewahren Sie sich vor unbemerktem Datenverlust.

Umschließen Sie jeden HTTP-Aufruf mit einem Try-Catch-Block mit exponentiellem Backoff:

function fetchWithRetry(\GuzzleHttp\Client $client, string $url, int $maxRetries = 3): string
{
    for ($attempt = 1; $attempt <= $maxRetries; $attempt++) {
        try {
            $response = $client->get($url);
            return (string) $response->getBody();
        } catch (\GuzzleHttp\Exception\GuzzleException $e) {
            if ($attempt === $maxRetries) {
                throw $e;
            }
            $wait = (int) pow(2, $attempt); // 2s, 4s, 8s
            sleep($wait);
        }
    }
}

Für strukturierte Protokollierung ist Monolog der De-facto-Standard im PHP-Ökosystem. Das Hinzufügen eines Handlers für rotierende Dateien erfordert zwei Zeilen:

use Monolog\Logger;
use Monolog\Handler\RotatingFileHandler;

$log = new Logger('scraper');
$log->pushHandler(new RotatingFileHandler('logs/scraper.log', 7, Logger::INFO));

$log->info('Fetching page', ['url' => $url]);
$log->error('Request failed', ['url' => $url, 'error' => $e->getMessage()]);

Protokollieren Sie jede Anfrage-URL, jeden Statuscode und alle Ausnahmen. Wenn ein Scrape-Job bei Seite 847 von 1.000 fehlschlägt, sind Protokolle das Einzige, was Ihnen verrät, was schiefgelaufen ist. Diese Fokussierung auf Produktionsreife ist es, was einen Prototyp von einer zuverlässigen Pipeline unterscheidet.

Blockierungen vermeiden: Proxys, Header und Ratenbegrenzung

Websites schätzen es nicht, wenn Bots ihre Server überlasten. Wenn Ihr Scraper Hunderte identischer Anfragen pro Minute von einer einzigen IP-Adresse sendet, müssen Sie damit rechnen, blockiert zu werden. Höfliches Scraping ist sowohl eine ethische Verpflichtung als auch eine praktische Notwendigkeit für lang laufende Projekte.

Wechseln Sie die User-Agent-Strings, damit nicht jede Anfrage denselben Client identifiziert:

$userAgents = [
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
    'Mozilla/5.0 (Macintosh; Intel Mac OS X 13_5) AppleWebKit/605.1.15',
    'Mozilla/5.0 (X11; Linux x86_64; rv:115.0) Gecko/20100101 Firefox/115.0',
];

$headers = ['User-Agent' => $userAgents[array_rand($userAgents)]];

Fügen Sie zufällige Verzögerungen zwischen den Anfragen ein, um vorhersehbare Zeitmuster zu vermeiden:

function politeDelay(int $minMs = 1000, int $maxMs = 3000): void
{
    usleep(random_int($minMs, $maxMs) * 1000);
}

Beachten Sie robots.txt programmgesteuert. Bevor Sie eine Domain scrapen, rufen Sie deren robots.txt und prüfen Sie, ob Ihr Zielpfad unzulässig ist. Sie können dies manuell analysieren oder eine Bibliothek wie spatie/robots-txt:

// Pseudocode — check before scraping
$robots = file_get_contents('http://example.com/robots.txt');
if (str_contains($robots, 'Disallow: /private/')) {
    echo "Skipping disallowed path.\n";
}

Proxy-Rotation ist die wirksamste Verteidigung gegen IP-basierte Blockierungen. Wenn Sie in nennenswertem Umfang scrapen, macht das Routen von Anfragen über einen Pool von Residential-Proxys Ihren Traffic praktisch nicht von organischen Nutzern zu unterscheiden. Sie können Guzzle mit einer einzigen Option so konfigurieren, dass ein Proxy verwendet wird:

$client = new \GuzzleHttp\Client([
    'proxy' => 'http://user:pass@proxy-host:port',
]);

Die Kombination all dieser Techniken (abwechslungsreiche Header, höfliche Verzögerungen, Beachtung von robots.txt und Proxy-Rotation) bietet Ihnen die besten Chancen, zuverlässig zu scrapen, ohne markiert zu werden.

Rechtliche und ethische Überlegungen

Web-Scraping befindet sich in einer rechtlichen Grauzone, die je nach Rechtsordnung variiert. Einige Grundsätze gelten allgemein.

Robots.txt ist ein freiwilliger Standard, kein rechtsverbindlicher Vertrag, aber seine Missachtung schwächt jedes Argument in gutem Glauben, das Sie vorbringen könnten, falls Sie angefochten werden. Behandeln Sie es als eine Grundregel, die Sie stets einhalten.

Die Nutzungsbedingungen der Zielseite können den automatisierten Zugriff ausdrücklich verbieten. Ein Verstoß gegen die Nutzungsbedingungen kann Sie der Gefahr von Vertragsverletzungsansprüchen aussetzen, insbesondere in den Vereinigten Staaten nach Fällen wie hiQ Labs gegen LinkedIn, in denen klargestellt wurde, dass das Scraping öffentlich zugänglicher Daten nicht zwangsläufig einen Verstoß gegen den Computer Fraud and Abuse Act darstellt, wobei jedoch die Durchsetzung der Nutzungsbedingungen nicht behandelt wurde.

Die DSGVO ist relevant, wenn Sie personenbezogene Daten von EU-Bürgern (Namen, E-Mail-Adressen, Profilangaben) scrapen. Nach der DSGVO kann Web-Scraping eine Datenverarbeitung darstellen, was bedeutet, dass Sie eine rechtmäßige Grundlage (in der Regel ein berechtigtes Interesse) benötigen und diese Daten gemäß den Anforderungen der DSGVO behandeln müssen: Zweckbindung, Speicherminimierung und die Erfüllung von Auskunftsersuchen der betroffenen Personen. Im Zweifelsfall sollten Sie einen Rechtsbeistand konsultieren, insbesondere wenn Ihr Scraping auf nutzergenerierte Inhalte abzielt.

Die ethischen Grundsätze sind klar: Scrapen Sie nicht in einem Umfang, der die Leistung der Zielwebsite beeinträchtigt, sammeln Sie keine Daten, für die Sie keine legitime Verwendung haben, und legen Sie Ihre Absichten nach Möglichkeit offen.

Wichtige Erkenntnisse

  • Wählen Sie das richtige Tool für den Seitentyp. Verwenden Sie Guzzle plus DomCrawler für statisches HTML, Symfony Panther für JavaScript-gerenderte Inhalte und eine Scraping-API, wenn die Anti-Bot-Infrastruktur Ihre selbst erstellte Lösung übertrifft.
  • Goutte ist veraltet. Starten Sie neue Projekte mit Symfony HttpBrowser, das denselben Crawling-Workflow bietet, gestützt auf aktiv gepflegte Symfony-Komponenten.
  • Sorgen Sie von Anfang an für Ausfallsicherheit. Exponential-Backoff-Wiederholungsversuche, strukturierte Protokollierung und Eingabevalidierung sind bei Produktions-Scrapern unverzichtbar.
  • Speichern Sie Daten in dem Format, das Ihre nachgelagerten Verbraucher benötigen. CSV für schnelle Analysen, JSON für APIs und Dokumentenspeicher, MySQL/PDO für relationale Abfragen.
  • Scrapen Sie höflich und legal. Wechseln Sie Header und Proxys, beachten Sie robots.txt, fügen Sie Verzögerungen zwischen den Anfragen ein und machen Sie sich mit den Auswirkungen der DSGVO bei der Erhebung personenbezogener Daten vertraut.

FAQ

Ist PHP oder Python besser für Web-Scraping-Projekte geeignet?

Keine der beiden Sprachen ist objektiv überlegen. Python verfügt über ein größeres Scraping-Ökosystem (Beautiful Soup, Scrapy, Selenium-Bindings), was mehr Tutorials und Community-Antworten bedeutet. PHP verfügt über starke integrierte HTTP- und DOM-Erweiterungen, und Composer-Bibliotheken wie Guzzle und DomCrawler sind produktionsreif. Wählen Sie die Sprache, die Ihr Team am besten beherrscht. Ein gut geschriebener PHP-Scraper wird einen schlecht gewarteten Python-Scraper jedes Mal übertreffen.

Kann PHP JavaScript-lastige Single-Page-Anwendungen scrapen?

Ja, aber Sie benötigen einen Headless-Browser. Symfony Panther steuert Chrome oder Firefox über das WebDriver-Protokoll und kann vollständig dynamische Seiten rendern. In einfacheren Fällen, in denen die Seite Daten von einem XHR-Endpunkt abruft, können Sie den Browser komplett überspringen und diesen API-Endpunkt direkt mit einem HTTP-Client aufrufen, was schneller ist und weniger Ressourcen verbraucht.

Die Rechtmäßigkeit hängt von der Rechtsordnung, den Nutzungsbedingungen der Zielseite und der Art der gesammelten Daten ab. Das Scraping öffentlich zugänglicher, nicht personenbezogener Daten ist in vielen Rechtsordnungen generell zulässig. Die DSGVO gilt, wenn Sie personenbezogene Daten von EU-Bürgern verarbeiten, was eine rechtmäßige Grundlage wie ein berechtigtes Interesse erfordert. Prüfen Sie immer die Nutzungsbedingungen der Zielseite und konsultieren Sie einen Rechtsbeistand, bevor Sie personenbezogene Daten in großem Umfang scrapen.

Wie vermeide ich, dass meine IP-Adresse beim Scraping mit PHP gesperrt wird?

Kombinieren Sie mehrere Techniken: Wechseln Sie die User-Agent-Strings, fügen Sie zufällige Verzögerungen zwischen den Anfragen ein (1 bis 3 Sekunden sind ein angemessener Bereich), beachten Sie robots.txt , und leiten Sie den Datenverkehr über einen Pool rotierender Proxys. Vermeiden Sie es, eine Flut von Anfragen von einer einzigen IP-Adresse aus zu senden. Wenn Sie in großem Umfang scrapen, übernimmt ein verwalteter Proxy- oder Scraping-API-Dienst die Rotation und Anti-Erkennung für Sie.

Wie gehe ich beim Scraping mit PHP mit login-geschützten Seiten um?

Senden Sie Anmeldedaten über eine POST-Anfrage (oder über eine Formularübermittlung mit Symfony HttpBrowser) und behalten Sie das resultierende Session-Cookie bei nachfolgenden Anfragen bei. Mit HttpBrowser bleiben Session-Cookies automatisch bestehen. Bei nacktem cURL setzen Sie CURLOPT_COOKIEJAR und CURLOPT_COOKIEFILE auf denselben Pfad. Überprüfen Sie stets, ob Ihre Anmeldung kein CAPTCHA oder keine Zwei-Faktor-Authentifizierung ausgelöst hat, und beachten Sie, dass das Scraping hinter einer Anmeldung gemäß den Nutzungsbedingungen der Website strengere rechtliche Konsequenzen nach sich ziehen kann.

Fazit

Web-Scraping mit PHP ist ein praktischer, gut unterstützter Workflow, sobald man weiß, auf welche Bibliotheken man zurückgreifen muss. Beginnen Sie mit cURL oder Guzzle zum Abrufen, fügen Sie DomCrawler oder DOMXPath zum Parsen hinzu und greifen Sie erst dann auf Symfony Panther zurück, wenn JavaScript-Rendering unvermeidbar ist. Speichern Sie Ihre Daten in dem Format, das Ihre Nutzer erwarten, verpacken Sie alles in Wiederholungslogik und Protokollierung und scrapen Sie stets höflich.

Die Beispiele in diesem Tutorial deckten den gesamten Lebenszyklus ab: von einer rohen HTTP-Anfrage über die Handhabung von Paginierung, gleichzeitiges Abrufen, Datenspeicherung bis hin zu Anti-Block-Strategien. Jede Technik entspricht einem echten Produktionsanliegen, nicht nur einer Spielzeug-Demo.

Wenn Sie feststellen, dass Sie mehr Zeit damit verbringen, Anti-Bot-Abwehrmaßnahmen zu umgehen, als Parsing-Logik zu schreiben, lohnt es sich möglicherweise, die Anfrage-Infrastruktur an einen Dienst wie die Scraper-API von WebScrapingAPI auszulagern, der Proxy-Rotation, CAPTCHAs und Wiederholungsversuche übernimmt, sodass Sie sich auf den Code zur Datenextraktion konzentrieren können, der tatsächlich wichtig ist.

Über den Autor
Sorin-Gabriel Marica, Full-Stack-Entwickler @ WebScrapingAPI
Sorin-Gabriel MaricaFull-Stack-Entwickler

Sorin Marica ist Full-Stack- und DevOps-Entwickler bei WebScrapingAPI, wo er Produktfunktionen entwickelt und die Infrastruktur wartet, die für einen reibungslosen Betrieb der Plattform sorgt.

Los geht’s

Sind Sie bereit, Ihre Datenerfassung zu erweitern?

Schließen Sie sich den über 2.000 Unternehmen an, die WebScrapingAPI nutzen, um Webdaten im Unternehmensmaßstab ohne zusätzlichen Infrastrukturaufwand zu extrahieren.