Laravel : utiliser le navigateur Chrome avec PHP

Je suis sur une bonne lancée en ce moment donc je vais continuer ma petite série d’articles et cette fois, je vais vous parler de Laravel, le célèbre framework PHP. Ça fait longtemps que j’avais écrit un article à son sujet et comme vous le savez sûrement, du moins si vous l’habitude de passer ici, c’est un outil que j’apprécie beaucoup.

Au-delà de Laravel, car c’est ce qui va nous servir de support et finalement, on pourrait s’en passer, aujourd’hui, on va parler de récupération de contenu avec un script. En effet, la mission de ce tutoriel est de vous apprendre à récupérer le contenu d’une page web, mais attention, pas n’importe comment !

Il existe plusieurs solutions pour récupérer le contenu d’une page web via son URL en PHP. Vous pouvez commencer par utiliser la fonction file_get_contents() mais le problème, c’est que c’est assez limité. Les possibilités pour la paramétrer ne sont pas énormes et surtout il existe beaucoup mieux qui est supporté partout ou presque. Je parle bien sur de la légendaire librairie cURL qui existe depuis maintenant de très nombreuses années et qui continue d’être mise à jour de façon régulière par ses mainteneurs. Dans la plupart des cas, c’est un excellent choix et les tutoriels pour utiliser cURL avec PHP ne manquent pas.

Sauf que tout n’est pas parfait et il n’est pas toujours possible de récupérer le contenu d’une page web pour diverses raisons. Cela peut être à cause d’user-agent invalide, des redirections en chaîne derrière l’URL, un blocage à cause des cookies obligatoire. Tout ceci est bien évidemment paramétrable avec cURL, mais dans certains cas, on peut avoir besoin d’un véritable navigateur web.

Pourquoi un navigateur web pour crawl une page ?

Le cas le plus courant est la sécurité présente sur les sites web pour éviter ce que l’on appelle le scraping. Il s’agit d’une technique qui consiste à récupérer massivement le contenu des pages web pour réutiliser ultérieurement les informations obtenues sur les pages. En général, ce sera le service de Cloudflare qui bloquera l’exploration des sites et si vous tentez d’obtenir le contenu HTML avec un appel cURL basique tel que celui-ci :

<?php
// URL cible
$url = "https://exemple.com/";

// Initialiser une session cURL
$ch = curl_init();

// Définir les options cURL
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, true); // Inclure les en-têtes HTTP dans la sortie
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false); // Ne pas suivre les redirections

// Exécuter cURL et récupérer la réponse
$response = curl_exec($ch);

// Récupérer les informations de la requête cURL
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$is_redirect = curl_getinfo($ch, CURLINFO_REDIRECT_COUNT) > 0 ? 'Yes' : 'No';
$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);

// Extraire les en-têtes et le contenu
$headers = substr($response, 0, $header_size);
$content = substr($response, $header_size);

// Fermer la session cURL
curl_close($ch);

// Fonction pour analyser les en-têtes HTTP
function get_header_value($headers, $header_name) {
    $pattern = '/^' . preg_quote($header_name, '/') . ':\s*(.*)$/mi';
    if (preg_match($pattern, $headers, $matches)) {
        return trim($matches[1]);
    }
    return null;
}

// Extraire les en-têtes demandés
$date = get_header_value($headers, 'date');
$content_type = get_header_value($headers, 'content-type');
$content_length = get_header_value($headers, 'content-length');
$cf_ray = get_header_value($headers, 'cf-ray');
$server = get_header_value($headers, 'server');

// Afficher les informations
echo "HTTP Code: $http_code\n";
echo "Is Redirect: $is_redirect\n";
echo "Headers:\n";
echo "  date: " . ($date ?: 'N/A') . "\n";
echo "  content-type: " . ($content_type ?: 'N/A') . "\n";
echo "  content-length: " . ($content_length ?: 'N/A') . "\n";
echo "  cf-ray: " . ($cf_ray ?: 'N/A') . "\n";
echo "  server: " . ($server ?: 'N/A') . "\n";

// Afficher le contenu de la page
echo "\nContent:\n";
echo $content;

Sur un site qu’il n’a pas de protection particulière, pas de problème, vous allez bien récupérer le contenu de la page. Vous devriez obtenir cette réponse :

HTTP Code: 200
Is Redirect: No
Headers:
  date: Fri, 23 Aug 2024 09:42:43 GMT
  content-type: text/html; charset=UTF-8
  content-length: N/A
  cf-ray: N/A
  server: nginx/1.22.1

Content:
<html><head><title>le contenu de la page web ...

Sur un site protégé par Cloudflare, il est fort possible que vous ayez la réponse suivante, car leur protection ne vous fait pas assez confiance :

HTTP Code: 403
Is Redirect: No
Headers:
  date: Fri, 23 Aug 2024 09:47:09 GMT
  content-type: text/html; charset=UTF-8
  content-length: 16118
  cf-ray: 8a7c2dfa5c0fd4f6-CDG
  server: cloudflare

Content:
<!DOCTYPE html><html lang="en-US"><head><title>Just a moment...</title><meta http-equiv="Content-Type" 

C’est à ce moment-là que le navigateur headless entre en jeu. Attention, ce n’est pas une solution miracle et pour certains sites ça passe, pour d’autres non. Cela dépendra des options choisies par le propriétaire du site dans Cloudflare et le niveau de vérifications qu’il souhaite appliquer. Vous pouvez trouver plus d’informations sur cette page chez Cloudflare.

Pour terminer sur ce point, c’est surtout les Security Level et Browser Integrity Check qui ne seront pas configurés pareil en fonction des sites que vous cherchez à crawl. Donc les résultats ne seront pas les mêmes partout. Néanmoins, dans certains cas, un headless browser sera suffisant pour obtenir le contenu de la page alors qu’avec cURL tout seul ce n’est pas possible.

C’est quoi un navigateur headless ?

Un navigateur headless est un navigateur web qui fonctionne sans interface graphique utilisateur (GUI). En d’autres termes, il s’agit d’un navigateur capable de charger et d’exécuter des pages web comme n’importe quel autre navigateur, mais sans afficher l’interface visuelle typique avec des fenêtres, des boutons ou des menus. Les navigateurs headless sont principalement utilisés pour l’automatisation des tâches liées au web, les tests de sites web et le scraping de données.

Avant de passer à la suite de ce tutoriel, il va donc falloir installer proprement un navigateur headless sur mon serveur Ubuntu et cette fois, il ne s’agit pas du navigateur Edge qui lui est destiné à la version classique de bureau, mais de Chromium Browser dans sa version headless. Je ferai sûrement aussi un autre tutoriel pour piloter un navigateur web Chrome à partir d’un script Python, mais cette fois de manière graphique, rendez-vous dans quelques jours !

Installer Chromium Browser sur un serveur Linux Ubuntu

Pour que la suite de ce tutoriel fonctionne, il va donc falloir installer quelques paquets sur notre serveur Linux Ubuntu. Je commence par faire le ménage, car je ne veux pas la version snap du navigateur et je préfère m’assurer qu’il n’y a rien qui traîne pour éviter toute incompatibilité. J’ai eu le cas sur un serveur qui venait d’être fraîchement installé donc je ne prend plus de risque. Pour supprimer ce qui ne m’intéresse pas je fais la commande :

sudo apt purge chromium-browser chromium-chromedriver -y
sudo apt autoremove -y

Si ce n’était pas installé, il ne se passe rien. Ensuite et c’est ce qui m’a causé des soucis, j’enlève la version snap de Chromium déjà présente sur mon serveur Ubuntu :

sudo snap remove chromium

Après quelques secondes, le terminal m’indique que cette version snap de Chromium à bien été supprimée avec le message :

chromium removed

Je peux donc réinstaller le navigateur headless avec un paquet en plus pour être sur que tout va bien fonctionner :

sudo apt install -y chromium-browser
sudo apt install -y xdg-utils

Une fois que c’est terminé, je vérifie la version du navigateur headless installée avec la commande suivante :

chromium-browser --version

Elle me répond alors que tout est bon avec cette version de Chromium au moment où j’écris ces lignes :

Chromium 127.0.6533.119

Manipuler Chromium avec Laravel en PHP pour scraper le contenu d’une page web

On va maintenant pouvoir passer à la partie script en PHP pour exploiter ce navigateur headless Chromium. Comme je vous le disais en introduction, vous n’êtes pas obligé d’utiliser Laravel et cela fonctionnera dans un autre framework PHP ainsi que dans un simple script PHP. C’est juste que pour ce genre de chose, j’ai pris l’habitude d’utiliser les commandes de Laravel pour ce genre de script.

Dans un premier temps, je vais importer Chrome PHP qui vous permet de faire tout un tas d’autres choses que simplement récupérer le HTML d’une page web. Vous pouvez prendre des captures d’écran de la page, créer des PDF, simuler un clavier et une souris, et encore pleins d’autres choses. Ce n’est pas l’objet de cet article, mais si vous voulez l’utiliser pour vos projets, c’est très pratique et relativement léger coté PHP.

J’importe donc cette librairie dans mon projet Laravel avec un simple composer :

composer require chrome-php/chrome

Après l’installation, je crée donc une petite commande dans Laravel :

php artisan make:command GetChromeHtml

Dans cette nouvelle commande, je place alors le contenu suivant :

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use HeadlessChromium\BrowserFactory;

class GetChromeHtml extends Command
{
    protected $signature = 'get:chrome-html {url}';
    protected $description = "Récupérer le HTML d'une page avec Headless Chromium";

    public function handle()
    {
        // Récupère l'URL depuis les arguments de la commande
        $url = $this->argument('url');

        // Spécifie le chemin vers Chromium (ajuste ce chemin si nécessaire)
        $browserFactory = new BrowserFactory('/usr/bin/chromium-browser');

        // Crée une instance du navigateur headless
        $browser = $browserFactory->createBrowser([
            'headless' => true,  // Mode headless
            'noSandbox' => true, // Désactiver le sandboxing
        ]);

        try {
            // Crée une nouvelle page
            $page = $browser->createPage();

            // Charge l'URL et attend la fin du chargement
            $this->info("Chargement de la page : $url");
            $page->navigate($url)->waitForNavigation();

            // Récupère le contenu HTML de la page
            $content = $page->getHtml();

            // Affiche le HTML
            $this->info("HTML de la page : \n");
            $this->line($content);

            // Ferme le navigateur proprement
            $browser->close();
        } catch (\Exception $e) {
            $this->error("Une erreur s'est produite : " . $e->getMessage());

            // Ferme le navigateur en cas d'erreur
            $browser->close();
        }
    }
}

Pour utiliser cette commande dans Laravel, il ne vous reste plus qu’à lancer ceci dans le terminal :

php artisan get:chrome-html https://example.com

Cela va utiliser Headless Chromium pour récupérer le contenu HTML de la page spécifiée et l’afficher dans le terminal. Il ne vous reste plus qu’a faire ce que vous souhaitez avec cette variable.

J’ai toutefois quelques remarques. Assurez-vous que Chromium est bien installé sur le système et que le chemin vers l’exécutable est correct, par défaut cela devrait être /usr/bin/chromium-browser. La commande est conçue pour être exécutée dans un environnement où chromium-browser est disponible, je n’ai utilisé qu’un serveur Linux Ubuntu pour ce tutoriel. Je n’ai pas testé sur Windows et macOS. Si vous utilisez un autre système d’exploitation, distribution Linux ou configuration, il pourrait être nécessaire d’ajuster le chemin vers le binaire de Chromium.

Pensez à gérer correctement les erreurs pour éviter que le navigateur reste ouvert en cas de problème (ce qui est partiellement fait ici avec le bloc try-catch). Amusez-vous bien !

Laisser un commentaire