Scrape les sites de manière efficace
le 8 septembre 2024
Le scraping web est une technique essentielle pour extraire des données de sites internet, que ce soit pour des recherches, des analyses de marché ou des développements d’applications. En PHP, il est possible de tirer parti des modèles asynchrones, de la gestion des IP et du pattern Y-combinator pour réaliser des tâches de scraping de manière efficace et évolutive. Cet article vous guide à travers trois étapes pour construire un scraper puissant : en commençant par un scraper basique d’URLs, en ajoutant une gestion avancée des IPs, et en finissant par l’utilisation du pattern Y-Combinator pour des tâches de traitement de données plus complexes.
Scraper Basique avec ScrapUrlsJob
Au cœur de notre scraper se trouve la classe ScrapUrlsJob
, un outil simple mais efficace qui permet de récupérer des données à partir de plusieurs URLs en utilisant la fonctionnalité multi-handle de cURL. Cette technique permet d'envoyer plusieurs requêtes simultanément, ce qui accélère considérablement le processus.
Comment cela fonctionne :
- cURL Multi-Handle : En créant plusieurs sessions cURL et en les gérant ensemble, on peut exécuter plusieurs requêtes HTTP simultanément.
- Suspension des Fibers : Cette méthode utilise les fibers de PHP pour une gestion asynchrone, suspendant l'exécution en attendant les réponses des URLs.
Voici l’implémentation de ScrapUrlsJob
:
class ScrapUrlsJob implements JobInterface
{
public function __invoke($urlContents): array
{
// Initialisation de la multi-handle cURL
$mh = curl_multi_init();
$curl_handles = [];
// Création et ajout des handles cURL à la multi-handle
foreach ($urlContents as $urlContent) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $urlContent->url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_multi_add_handle($mh, $ch);
$curl_handles[] = [$ch, $urlContent];
}
// Exécution des requêtes de manière asynchrone
do {
curl_multi_exec($mh, $running);
Fiber::suspend();
} while ($running > 0);
// Récupération des réponses
$urlContents = [];
foreach ($curl_handles as [$ch, $urlContent]) {
$urlContent->content = curl_multi_getcontent($ch);
$urlContents[] = $urlContent;
curl_multi_remove_handle($mh, $ch);
curl_close($ch);
}
// Fermeture de la multi-handle
curl_multi_close($mh);
return $urlContents;
}
}
Dans ce job :
- Le scraper envoie plusieurs requêtes en parallèle et suspend l'exécution (
Fiber::suspend()
) en attendant les réponses. - Une fois les réponses reçues, le contenu est collecté et traité.
Ce job est idéal pour scraper des données à partir d’une liste statique d'URLs, là où la limitation de débit ou le blocage des IPs ne pose pas de problème.
Scraping Avancé avec ScrapUrlJob et FlattenIpStrategy
Lorsqu’il s’agit de sites web sensibles ou limitant le débit des requêtes, la rotation des adresses IP devient essentielle. À cette étape, nous introduisons la classe ScrapUrlJob
combinée à la stratégie de gestion d'IP FlattenIpStrategy
, qui permet de répartir les requêtes sur plusieurs IPs pour éviter le blocage ou le ralentissement.
ScrapUrlJob est responsable du scraping d’une URL unique et de la gestion des sessions cURL. Lorsqu’elle est combinée avec FlattenIpStrategy, elle permet de distribuer les requêtes sur différentes adresses IP afin d’éviter les blocages par le serveur cible.
Voici l'implémentation de ScrapUrlJob
:
class ScrapUrlJob implements JobInterface
{
private CurlMultiHandle $mh;
public function __construct()
{
// Initialize a cURL multi handle
$this->mh = curl_multi_init();
}
public function __destruct()
{
curl_multi_close($this->mh);
}
public function __invoke($urlContent): mixed
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $urlContent->url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_multi_add_handle($this->mh, $ch);
do {
$status = curl_multi_exec($this->mh, $active);
curl_multi_exec($this->mh, $active);
Fiber::suspend();
$info = curl_multi_info_read($this->mh);
} while (
$active && $status === CURLM_OK // check curl_multi is active
&& !($info !== false && $info['handle'] === $ch && $info['result'] === CURLE_OK) // check $ch is done
);
$content = curl_multi_getcontent($ch);
curl_multi_remove_handle($this->mh, $ch);
curl_close($ch);
return new UrlContent($urlContent->url, $content);
}
}
Et la stratégie FlattenIpStrategy
:
class FlattenIpStrategy implements IpStrategyInterface
{
private IpPool $ipPool;
public function __construct()
{
$this->ipPool = new IpPool();
}
public function push(PushEvent $event): void
{
$ip = $event->getIp();
if (!is_iterable($ip->data)) {
throw new LogicException('Les données IP doivent être itérables');
}
foreach ($ip->data as $data) {
$this->ipPool->addIp(new Ip($data));
}
}
public function pull(PullEvent $event): void
{
$ip = $this->ipPool->shiftIp();
if ($ip !== null) {
$event->addIp($ip);
}
}
public function pool(PoolEvent $event): void
{
$event->addIps($this->ipPool->getIps());
}
}
Cette stratégie garantit que plusieurs adresses IP sont utilisées lors du scraping, réduisant ainsi les risques de blocage par le serveur cible. En combinant ScrapUrlJob
avec FlattenIpStrategy
, nous assurons que le scraper est résilient face aux limitations basées sur les IPs.
Pattern Y-Combinator
Le pattern Y-combinator est une technique de récursion avancée utilisée pour effectuer des opérations complexes. Dans notre cas, nous l’utilisons pour collecter des données utilisateurs à travers plusieurs pages et les agréger de manière asynchrone.
L’exemple suivant montre l’utilisation du pattern Y-combinator avec YJob
pour gérer des tâches de scraping récursives, comme la récupération d'une liste d’utilisateurs, de leurs publications et de leurs tâches.
yield static fn () => [null, []];
yield [new YJob(function ($rec) {
return function ($args) use ($rec) {
[$data, $defer] = $args;
return $defer(function ($complete, $async) use ($data, $defer, $rec) {
[$i, $users] = $data;
if ($i === null) {
$response = $this->httpClient->request('GET', 'https://jsonplaceholder.typicode.com/users');
Fiber::suspend();
$users = $response->toArray();
$async($rec([[0, $users], $defer]), static function ($result) use ($complete) {
$complete($result);
});
} elseif ($i >= 0 && $i < count($users)) {
$users[$i] = $this->getUserData($users[$i], $this->httpClient);
$async($rec([[$i + 1, $users], $defer]), static function ($result) use ($complete) {
$complete($result);
});
} else {
$complete([$users, $defer]);
}
});
};
}), null, null, null, new DeferAsyncHandler()];
yield static function ($users) use ($io) {
$io->writeln(sprintf('ScrapYDeferJob : Terminé le scraping de %d utilisateurs', count($users)));
};
Dans ce pattern :
- Traitement Récursif : La fonction continue à traiter les utilisateurs jusqu’à ce que toutes les données (comme les tâches et publications) aient été récupérées.
- Gestion de la Suspension : La nature asynchrone du processus est gérée via le
DeferAsyncHandler
, permettant de suspendre le processus (Fiber::suspend()
) et de le reprendre lorsque les données sont disponibles.
Cette approche est extrêmement flexible et utile lorsqu’on travaille avec des APIs qui renvoient des données paginées ou imbriquées, permettant de chaîner les requêtes et de construire des structures de données complexes en temps réel.
Se former à Flow
Si vous souhaitez approfondir vos connaissances sur le framework Flow et découvrir d'autres moyens d'améliorer votre productivité en tant que développeur, je propose une formation complète sur Flow disponible sur
https://www.bonzai.pro/matyo91/shop
Conclusion
Le scraping web peut varier d’une tâche simple, comme la récupération de pages statiques, à des opérations plus complexes impliquant des contenus dynamiques et de grands volumes de données. En progressant du scraper basique d’URL (ScrapUrlsJob
) aux techniques avancées de gestion des IPs (ScrapUrlJob
avec FlattenIpStrategy
) et à l'utilisation de la récursivité asynchrone (Y-Combinator avec YJob
), vous pouvez construire un scraper en PHP à la fois performant et évolutif.
Ces méthodes utilisent la puissance des opérations asynchrones, des fibers et de la gestion multi-handle de cURL pour optimiser le processus de scraping et réduire la consommation des ressources. Que vous grattiez de petits ou de grands ensembles de données, ces patterns vous aideront à collecter les informations de manière efficace tout en respectant les serveurs cibles.