Web scraping avec PHP : un guide étape par étape

Apprenez à créer et à programmer facilement votre propre web scraper en PHP, en partant de zéro.
3 min de lecture
Web scraping with PHP

Grâce à ses bibliothèques et à ses outils complets, PHP est un excellent langage pour créer des web scrapers. Conçu spécifiquement pour le développement Web, PHP gère les tâches de web scraping avec facilité et fiabilité.

Il existe de nombreuses méthodes différentes pour extraire du contenu des sites Web à l’aide de PHP, et vous en explorerez quelques-unes dans cet article. Plus précisément, vous apprendrez à extraire du contenu des sites Web à l’aide de curl, file_get_contents, Symfony BrowserKit et le composant Panther de Symfony. En outre, vous découvrirez certains problèmes courants auxquels vous pouvez être confronté dans le cadre du web scraping et vous apprendrez à les éviter.

Dans cet article, nous allons aborder les sujets suivants :

Web Scraping avec PHP

Dans cette section, vous découvrirez quelques méthodes couramment utilisées pour le web scraping, qu’il s’agisse de sites basiques ou complexes/dynamiques.

Veuillez noter : bien que nous abordions différentes méthodes dans ce didacticiel, il ne s’agit en aucun cas d’une liste exhaustive.

Prérequis

Pour suivre ce tutoriel, vous devez disposer de la dernière version de PHP et de Composer, un gestionnaire de dépendances pour PHP. Cet article a été testé avec PHP 8.1.18 et Composer 2.5.5.

Une fois PHP et Composer configurés, créez un répertoire nommé php-web-scraping et cd dedans :

mkdir php-web-scraping
cd $_

Vous travaillerez dans ce répertoire pour le reste du didacticiel.

curl

curl est une bibliothèque de bas niveau quasi omniprésente et un outil CLI écrit en C. Il peut être utilisé pour récupérer le contenu d’une page Web via HTTP ou HTTPS. Sur presque toutes les plate-formes, PHP est livré avec la prise en charge de curl activée par défaut.

Dans cette section, vous allez parcourir une page Web très basique qui répertorie les pays par population sur la base d’estimations des Nations Unies. Vous allez extraire les liens du menu ainsi que les textes des liens.

Pour commencer, créez un fichier appelé curl.php , puis initialisez curl dans ce fichier avec la fonction curl_init :

<?php
$ch = curl_init();

Définissez ensuite les options de récupération de la page Web. Cela inclut la définition de l’URL et de la méthode HTTP (GET, POST, etc.) à l’aide de la fonction 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);

Dans ce code, vous définissez l’URL cible sur la page Web et la méthode sur GET. Le CURLOPT_RETURNTRANSFER indique à curl de renvoyer la réponse HTML.

Une fois que curl est prêt, vous pouvez effectuer la demande en utilisant curl_exec :

$response = curl_exec($ch);

La récupération des données HTML n’est que la première étape du web scraping. Pour extraire des données de la réponse HTML, il vous faut utiliser plusieurs techniques. La méthode la plus simple consiste à utiliser des expressions régulières pour une extraction HTML très basique. Notez toutefois qu’il n’est pas possible d’analyser un code HTML ordinaire à l’aide de regex, mais pour une analyse très simple, regex est suffisant.

Par exemple, extrayez les balises <a> dotés d’attributs href et title et qui contiennent un <span>:

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";
    }
}

Libérez ensuite les ressources en utilisant la fonction curl_close :

curl_close($ch);

Exécutez le code comme suit :

php curl.php

Vous devriez voir qu’il extrait correctement les liens :

php curl.php

curl vous permet d’exercer un contrôle de très bas niveau sur la manière dont une page web est récupérée via HTTP/HTTPS. Vous pouvez affiner les différentes propriétés de connexion et même ajouter des mesures supplémentaires, telles que serveurs proxy (nous y reviendrons plus tard), des agents utilisateurs et des délais d’attente.

En outre, curl est installé par défaut sur la plupart des systèmes d’exploitation, ce qui en fait un excellent choix pour écrire un web scraper multi-plate-forme.

Cependant, comme vous l’avez vu, curl ne suffit pas à lui seul et vous avez besoin d’un analyseur HTML pour extraire correctement les données. curl ne peut pas non plus exécuter JavaScript sur une page Web, ce qui signifie que vous ne pouvez pas extraire des pages Web dynamiques et des applications monopages (SPA) avec curl.

file_get_contents

La fonction file_get_contents est principalement utilisée pour lire le contenu d’un fichier. Toutefois, en transmettant une URL HTTP, vous pouvez récupérer des données HTML à partir d’une page Web. Cela signifie que file_get_contents peut remplacer l’utilisation de curl dans le code précédent.

Dans cette section, vous allez parcourir la même page que précédemment, mais cette fois, le scraper sera plus avancé et vous pourrez extraire les noms de tous les pays du tableau.

Créez un fichier nommé file_get-contents.php et commencez par transmettre une URL à file_get_contents :

<?php

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

La variable $html contient désormais le code HTML de la page Web.

Comme dans l’exemple précédent, la récupération des données HTML n’est que la première étape. Pour pimenter les choses, utilisez libxml pour sélectionner des éléments à l’aide des sélecteurs XPath . Pour cela, vous devez d’abord initialiser un DOMDocument et y charger le code HTML :

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

Ici, vous sélectionnez les pays dans l’ordre suivant : le premier élémenttbody  , un élémenttr  à l’intérieur dutbody , le premiertd  dans l’élémenttr  , eta  avec un attributtitle  dans l’élémenttd . {34}

Le code suivant initialise une classe DOMXPath et utilise evaluate pour sélectionner l’élément à l’aide du sélecteur XPath :

$xpath = new DOMXpath($doc);

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

Il ne reste plus qu’à parcourir les éléments en boucle et à imprimer le texte :

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

Exécutez le code comme suit :

php file_get_contents.php
php file_get_contents.php

Comme vous pouvez le constater, file_get_contents est plus simple à utiliser que curl et est souvent utilisé pour récupérer rapidement le code HTML d’une page Web. Cependant, il présente les mêmes inconvénients que curl : vous avez besoin d’un analyseur HTML supplémentaire et vous ne pouvez pas scraper les pages Web dynamiques et les SPA. De plus, vous perdez les contrôles affinés fournis par curl. Cependant, sa simplicité en fait un excellent choix pour le scraping de sites statiques de base.

Symfony BrowserKit

Symfony BrowserKit est un composant du cadre Symfony qui simule le comportement d’un navigateur réel. Cela signifie que vous pouvez interagir avec la page Web comme dans un véritable navigateur ; par exemple, en cliquant sur des boutons/liens, en soumettant des formulaires et en revenant en arrière et en avançant dans l’historique.

Dans cette section, vous allez consulter le blog de Bright Data, saisir PHP dans le champ de recherche et envoyer le formulaire de recherche. Vous allez ensuite extraire les noms des articles à partir du résultat :

Blog de Bright Data, section PHP

Pour utiliser Symfony BrowserKit, vous devez installer le composant BrowserKit avec Composer :

composer require symfony/browser-kit

Vous devez également installer le composant HttpClient pour effectuer des requêtes HTTP sur Internet :

composer require symfony/http-client

BrowserKit prend en charge la sélection d’éléments à l’aide de sélecteurs XPath par défaut. Dans cet exemple, vous utilisez des sélecteurs CSS. Pour cela, vous devez également installer le composant CssSelector :

composer require symfony/css-selector

Créez un fichier nommé symfony-browserkit.php. Dans ce fichier, initialisez HttpBrowser :

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

use Symfony\Component\BrowserKit\HttpBrowser;

$client = new HttpBrowser();

Utilisez la fonction request pour effectuer une requête GET :

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

Pour sélectionner le formulaire contenant le bouton de recherche, vous devez sélectionner le bouton lui-même et utiliser la fonction form pour obtenir le formulaire ci-joint. Le bouton peut être sélectionné à l’aide de la fonction filter en transmettant son identifiant. Une fois le formulaire sélectionné, vous pouvez le soumettre à l’aide de la fonction submit de la classe Httpbrowser .

En transmettant un hachage des valeurs des entrées, la fonction submit peut remplir le formulaire avant qu’il ne soit soumis. Dans le code suivant, l’entrée nommée q a reçu la valeur PHP, ce qui revient à taper PHP dans le champ de recherche :

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

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

La fonction submit renvoie la page résultante. À partir de là, vous pouvez extraire les noms des articles à l’aide du sélecteur CSS .col-md-4.mb-4 h5 :

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

Exécutez le code comme suit :

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

Bien que Symfony BrowserKit soit une avancée par rapport aux deux méthodes précédentes en termes d’interaction avec les pages Web, elle reste limitée car elle ne peut pas exécuter JavaScript. Cela signifie que vous ne pouvez pas scraper des sites Web dynamiques et des SPA à l’aide de BrowserKit.

Symfony Panther

Symfony Panther est un autre composant Symfony qui entoure le composant BrowserKit. Symfony Panther présente toutefois un avantage majeur : au lieu de simuler un navigateur, il exécute le code dans un navigateur réel en utilisant le protocole WebDriver pour contrôler à distance un navigateur réel. Cela signifie que vous pouvez scraper n’importe quel site Web, y compris les sites Web dynamiques et les SPA.

Dans cette section, vous allez charger la page d’accueil d’OpenWeather, taper le nom de votre ville dans le champ de recherche, effectuer la recherche et consulter la météo actuelle de votre ville :

Page d’accueil d’OpenWeather

Pour commencer, installez Symfony Panther avec Composer :

composer require symfony/panther

Vous devez également installer dbrekelmans/browser-driver-installer, qui peut détecter automatiquement le navigateur installé sur votre système et installer le pilote approprié pour celui-ci. Assurez-vous qu’un navigateur basé sur Firefox ou Chromium est installé sur votre système :

composer require dbrekelmans/bdi

Pour installer le pilote approprié dans le répertoire drivers , exécutez l’outil bdi :

vendor/bin/bdi detect drivers

Créez un fichier nommé symfony-panther.php et commencez par initialiser un client Panther :

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

use Symfony\Component\Panther\Client;


$client = Client::createFirefoxClient();

Remarque:  selon votre navigateur, vous devrez peut-être utiliserCreateChromeClient  ouCreateSeleniumClient  au lieu deCreateFirefoxClient.

Comme Panther utilise Symfony BrowserKit en arrière-plan, les codes suivants sont très similaires au code de la section Symfony BrowserKit.

Vous commencez par charger la page Web à l’aide de la fonction request . Lorsque la page se charge, elle est initialement couverte par un div avec la classe owm-loader , qui affiche la barre de progression du chargement. Vous devez attendre que ce div disparaisse avant de commencer à interagir avec la page. Cela peut être fait à l’aide de la fonction WaitForStaleness , qui prend un sélecteur CSS et attend qu’il soit supprimé du DOM.

Une fois la barre de chargement supprimée, vous devez accepter les cookies pour fermer la bannière des cookies. Pour cela, la fonction SelectButton est très pratique, car elle permet de rechercher un bouton à partir de son texte. Une fois que vous avez le bouton, la fonction click effectue un clic dessus :

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

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

Remarque:  selon la rapidité de chargement de la page, la barre de chargement peut disparaître avant l’exécution de la fonctionWaitForStaleness. Cela crée une exception. C’est pourquoi cette ligne a été encapsulée dans un bloc trycatch.

OpenWeather

Il est maintenant temps de taper Kolkata dans la barre de recherche. Sélectionnez la barre de recherche avec la fonction filter et utilisez la fonction SendKeys pour saisir du contenu dans la barre de recherche. Cliquez ensuite sur le bouton Rechercher :

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

Une fois le bouton sélectionné, une boîte de suggestion de saisie semi-automatique apparaît. Vous pouvez utiliser la fonction WaitForVisibility pour attendre que la liste soit visible, puis cliquer sur le premier élément en combinant filter et cliquez sur comme précédemment :

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

Enfin, utilisez WaitForElementToContain pour attendre le chargement des résultats, puis extrayez la température actuelle à l’aide du filtre :

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

echo $temp;

À cet endroit, vous attendez que l’élément avec le sélecteur .orange-text+h2 contienne Kolkata. Cela indique que les résultats ont été chargés.

Exécutez le code comme suit :

php symfony-panther.php

Le résultat devrait ressembler à ceci :

Défis et solutions possibles en matière de web scraping

Même si PHP facilite l’écriture de scrapers web, la navigation dans des projets de scraping réels peut s’avérer complexe. De nombreuses situations peuvent survenir et présenter des défis qui doivent être relevés. Ces problèmes peuvent provenir de facteurs tels que la structure des données (par exemple, la pagination) ou des mesures antibots prises par les propriétaires du site Web (par exemple , des honeypots).

Dans cette section, vous découvrirez certains des défis les plus courants et comment les relever.

Navigation sur des sites Web paginés

Lors du scraping de presque n’importe quel site Web réel, il est probable que vous rencontriez une situation où toutes les données ne sont pas chargées en même temps. Autrement dit, les données sont paginées. Il peut y avoir deux types de pagination :

  1. Toutes les pages se trouvent sur des URL distinctes. Le numéro de page est transmis via un paramètre de requête ou un paramètre de chemin d’accès. Par exemple, example.com?page=3 ou example.com/page/3.
  2. Les nouvelles pages sont chargées à l’aide de JavaScript lorsque le bouton Suivant est sélectionné.

Dans le premier scénario, vous pouvez charger les pages en boucle et les extraire en tant que pages Web distinctes. Par exemple, en utilisant file_get_contents, le code suivant extrait les dix premières pages d’un exemple de site :

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

Dans le second scénario, vous devez utiliser une solution capable d’exécuter du JavaScript, comme Symfony Panther. Dans cet exemple, vous devez cliquer sur le bouton approprié pour charger la page suivante. N’oubliez pas d’attendre un peu le chargement de la nouvelle page :

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

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

Remarque: vous devez substituer une logique d’attente appropriée au site Web dont vous êtes en train d’extraire du contenu.

Proxies rotatifs

Un serveur proxy fait office d’intermédiaire entre votre ordinateur et le serveur Web cible. Il empêche le serveur Web de voir votre adresse IP, préservant ainsi votre anonymat.

Cependant, vous ne devez pas vous fier à un seul serveur proxy car il peut être banni. Au lieu de cela, vous devez utiliser plusieurs serveurs proxy et effectuer des rotations entre eux. Le code suivant fournit une solution très simple dans laquelle un tableau de proxies est utilisé et l’un d’entre eux est choisi au hasard :

$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);

Gérer les CAPTCHAS

Les CAPTCHA sont utilisés par de nombreux sites Web pour s’assurer que l’utilisateur est un humain et non un robot. Malheureusement, cela signifie que votre web scraper peut être bloqué.

Les CAPTCHA peuvent être très primitifs, comme une simple case à cocher demandant « Es-tu un être humain ? » Ils peuvent également utiliser un algorithme plus avancé, comme le reCAPTCHA ou le HCaptcha de Google. Vous pouvez probablement vous en tirer avec des CAPTCHA primitifs en manipulant des pages Web de base (par exemple en cochant une case), mais pour combattre les CAPTCHA avancés, vous avez besoin d’un outil dédié comme 2Captcha. 2Captcha utilise des humains pour résoudre les CAPTCHA. Il vous suffit de transmettre les informations requises à l’API 2Captcha, qui renvoie le CAPTCHA résolu.

Pour commencer à utiliser 2Captcha, vous devez créer un compte et obtenir une clé API.

Installez 2Captcha avec Composer :

composer require 2captcha/2captcha

Dans votre code, créez une instance de TwoCaptcha :

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

Utilisez ensuite 2Captcha pour résoudre les CAPTCHA :

// 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/',
]);

Vous pouvez également consulter l’outil de résolution CAPTCHA de Bright Data.

Éviter les honeypots

Les honeypots représentent une mesure anti-bot qui imite un service ou un réseau afin d’attirer les scrapers et les crawlers pour les détourner de la cible réelle. Bien que les honeypots soient utiles pour prévenir les attaques de robots, ils peuvent être problématiques pour le web scraping. Vous ne voulez pas que votre extraction reste coincée à cause d’un honeypot.

Il existe toutes sortes de mesures que vous pouvez prendre pour éviter de vous laisser entraîner dans un honeypot. Par exemple, les liens honeypot sont souvent masqués afin qu’un utilisateur réel ne les voie pas, mais qu’un robot puisse les récupérer. Pour éviter le piège, vous pouvez essayer d’éviter de cliquer sur les liens masqués (liens avec affichage: aucun  ou visibilité : aucune propriété CSS).

Une autre option consiste à alterner les proxys afin que, si l’une des adresses IP du serveur proxy est détectée dans le honeypot et bannie, vous puissiez toujours vous connecter via d’autres proxys.

Conclusion

Grâce à la bibliothèque et aux frameworks supérieurs de PHP, il est facile de créer un web scraper. Dans cet article, vous avez appris à effectuer les opérations suivantes :

  • Extraire du contenu à partir d’un site Web statique à l’aide de curl et regex
  • Extraire du contenu à partir d’un site Web statique à l’aide de file_get_contents et libxml
  • Extraire du contenu à partir d’un site statique à l’aide de Symfony BrowserKit et soumettre des formulaires
  • Extraire du contenu d’un site dynamique complexe à l’aide de Symfony Panther

Malheureusement, en utilisant ces méthodes, vous avez découvert que le scraping avec PHP comporte des complexités supplémentaires. Par exemple, vous devrez peut-être faire appel à plusieurs proxies et concevoir soigneusement votre scraper pour éviter les honeypots.

Et c’est là qu’intervient Bright Data…

À propos des proxies de Bright Data :

Proxies résidentiels : avec plus de 72 millions d’adresses IP réelles provenant de 195 pays, les proxys résidentiels de Bright Data vous permettent d’accéder au contenu de n’importe quel site Web, où que vous soyez, tout en évitant les interdictions d’adresses IP et les CAPTCHA.

Proxies ISP : avec plus de 700 000 IP ISP, tirez parti de véritables IP statiques de n’importe quelle ville du monde, attribuées par les FAI et louées à Bright Data pour votre usage exclusif, aussi longtemps que vous le souhaitez.

Proxies de centre de données : avec plus de 770 000 adresses IP de centres de données, le réseau proxy de centre de données de Bright Data est composé de plusieurs types d’adresses IP à travers le monde, dans un pool d’adresses IP partagé ou pour un achat individuel.

Proxies mobiles : avec plus de 7 millions d’adresses IP mobiles, le réseau IP mobile avancé de Bright Data offre le réseau IP 3G/4G/5G à pair réel le plus rapide et le plus étendu au monde.

Rejoignez le plus grand réseau de proxies au monde et profitez d’un essai gratuit.