Retour au blog
Guides
Robert SfichiLast updated on Apr 22, 20269 min read

Le guide complet pour se lancer dans le web scraping avec Elixir

Le guide complet pour se lancer dans le web scraping avec Elixir

Introduction

Le web scraping est le processus qui consiste à extraire des données de sites accessibles au public, tels que les forums, les réseaux sociaux, les sites d'actualités, les plateformes de commerce électronique, etc. Pour vous donner un aperçu de ce que vous allez créer aujourd'hui, cet article décrit la création d'un outil de web scraping en Elixir.

Si la définition du web scraping n'est pas encore claire pour vous, considérons le fait d'enregistrer une image depuis un site comme du web scraping manuel. Si vous souhaitez enregistrer toutes les images d'un site à la main, cela peut prendre des heures, voire des jours, selon la complexité du site. 

Vous pouvez automatiser ce processus en créant un scraper web.

Vous vous demandez peut-être quels sont les cas d'utilisation d'un scraper web. Voici les plus courants :

Suivi de l'actualité

Vous pouvez extraire les dernières actualités de votre site d'actualités financières préféré, exécuter un algorithme d'analyse de sentiment et savoir dans quoi investir quelques minutes avant l'ouverture et l'évolution du marché

Analyses des réseaux sociaux

Vous pouvez extraire les commentaires de vos pages sur les réseaux sociaux et analyser ce dont parlent vos abonnés et ce qu'ils pensent de votre produit ou service.

Suivi des prix

Si vous êtes passionné par les consoles et les jeux vidéo mais que vous ne souhaitez pas dépenser une fortune pour la dernière PS5, vous pouvez créer un scraper web qui récupère les annonces eBay et vous envoie une notification lorsqu'une console à prix réduit est mise en vente.

Formation en apprentissage automatique

Si vous souhaitez créer une application mobile capable d'identifier la race d'un chat sur n'importe quelle photo, vous aurez besoin d'une grande quantité de données d'entraînement ; au lieu d'enregistrer manuellement des centaines de milliers de photos de chats pour entraîner le modèle, vous pouvez utiliser un scraper web pour le faire automatiquement.

Nous allons développer notre scraper web en Elixir, un langage de programmation basé sur Erlang, créé par José Valim, membre de l'équipe principale de Ruby on Rails. Ce langage de programmation emprunte la simplicité syntaxique de Ruby et la combine avec la capacité d'Erlang à construire des systèmes à faible latence, distribués et tolérants aux pannes.

Prérequis

Avant d'écrire la première ligne de code, assurez-vous qu'Elixir est installé sur votre ordinateur. Téléchargez le programme d'installation adapté à votre système d'exploitation et suivez les instructions de la page d'installation

Au cours de l'installation, vous remarquerez que le langage de programmation Erlang est également requis. N'oubliez pas qu'Elixir s'exécute dans la machine virtuelle Erlang ; vous avez donc besoin des deux.

Pour commencer

Dans cet article, vous apprendrez à créer un scraper web en Elixir, à extraire les produits d'eBay pour les annonces PS5 et à stocker localement les données extraites (nom, URL, prix).

Inspection de la cible

Il est temps d'inspecter les résultats de recherche sur la page eBay et de collecter quelques sélecteurs.

Rendez-vous sur ebay.com, saisissez le terme « PS5 » dans le champ de recherche et cliquez sur le bouton « Rechercher ». Une fois la page de résultats chargée, ouvrez l'outil d'inspection de votre navigateur (cliquez avec le bouton droit n'importe où sur la page et sélectionnez « Inspecter »).

Vous devez collecter les sélecteurs suivants :

  • Élément de la liste de produits
  • URL du produit
  • Nom du produit
  • Prix du produit

Utilisez l'outil de sélection d'éléments et recherchez la liste des éléments de produit (ul) et l'élément de produit (li) :

À l'aide de ces deux éléments, vous pouvez extraire les classes nécessaires au robot d'indexation pour l'extraction des données :

  • .srp-results .s-item les éléments enfants de l'élément de liste des produits (ul)
  • .s-item__title span : le titre du produit
  • .s-item__link : le lien du produit
  • .s-item__price : le prix du produit

Création du projet

Créons un projet Elixir à l'aide de la commande mix :

mix new elixir_spider --sup

L'option --sup génère un squelette d'application OTP comprenant une arborescence de supervision, une fonctionnalité indispensable pour une application qui gère plusieurs processus simultanés, comme un robot d'indexation.

Création du dossier temporaire

Accédez au répertoire racine du projet :

cd elixir_spider

Créez le répertoire temp :

mkdir temp

Nous utilisons ce répertoire pour stocker les éléments extraits.

Ajout des dépendances

Une fois le projet créé, vous devez ajouter les deux dépendances :

  • Crawly est un framework d'application permettant d'explorer des sites et d'extraire des données structurées
  • Floki est un analyseur HTML qui permet de rechercher des nœuds à l'aide de sélecteurs CSS

Ouvrez le fichier mix.exs et ajoutez les dépendances dans le bloc deps :

defp deps do
    [
        {:crawly, "~> 0.14.0"},
        {:floki, "~> 0.33.1"}
    ]
end

Récupérez les dépendances en exécutant cette commande :

mix deps.get

Créez le fichier de configuration

Créez le fichier config/config.exs et collez-y cette configuration :

import Config

config :crawly,
    closespider_timeout: 10,
    concurrent_requests_per_domain: 8,
    closespider_itemcount: 100,

    middlewares: [
            Crawly.Middlewares.DomainFilter,
            Crawly.Middlewares.UniqueRequest,
            {Crawly.Middlewares.UserAgent, user_agents: ["Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:105.0) Gecko/20100101 Firefox/105.0"]}
    ],
    pipelines: [
            {Crawly.Pipelines.Validate, fields: [:url, :title, :price]},
            {Crawly.Pipelines.DuplicatesFilter, item_id: :title},
            Crawly.Pipelines.JSONEncoder,
            {Crawly.Pipelines.WriteToFile, extension: "jl", folder: "./temp"}
    ]

Options générales

Passons en revue chaque propriété pour en comprendre le sens :

  • closespider_timeout : nombre entier, nombre maximal de secondes pendant lesquelles le spider restera ouvert
  • concurrent_requests_per_domain : le nombre maximal de requêtes qui seront effectuées pour chaque domaine exploré
  • closespider_itemcount : nombre maximal d'éléments transmis par le pipeline d'éléments

Agent utilisateur

En configurant l'agent utilisateur, vous améliorez les résultats du scraping en imitant un vrai navigateur. Les sites n'apprécient pas les scrapers et tentent de bloquer tout agent utilisateur qui ne semble pas réel. Vous pouvez utiliser un outil comme celui-ci pour obtenir l'agent utilisateur de votre navigateur.

WebScrapingAPI fait tourner l'agent utilisateur et l'adresse IP à chaque requête et met également en œuvre d'innombrables techniques d'évasion pour éviter ce genre de situation. Vos requêtes ne seront pas bloquées et la mise en place d'un mécanisme de réessai vous donnera des résultats exceptionnels.

Pipelines

Les pipelines sont des commandes traitées de haut en bas et permettent la manipulation des éléments traités. Nous utilisons les pipelines suivants :

  • Valider les champs (titre, prix, URL) : vérifie si l'élément contient les champs extraits
  • Filtre de doublons : vérifie s'il existe des éléments en double par titre
  • Encodeur JSON : encode les structures en objet JSON
  • Écriture dans un fichier : écrit les éléments dans le dossier ./temp

Création du spider

Un crawler web, ou spider, est un type de bot qui parcourt un site et extrait des données à l'aide de champs définis par l'utilisateur via des sélecteurs CSS. Un crawler peut extraire tous les liens d'une page et utiliser certains d'entre eux (comme les liens de pagination) pour explorer davantage de données. 

Il est temps de poser les bases du crawler : créez le fichier ebay_scraper.ex dans le dossier lib/elixir_spider et collez-y le code suivant :

# lib/elixir_spider/ebay.ex
defmodule EbayScraper do
    use Crawly.Spider

    @impl Crawly.Spider
    def base_url(), do: ""

    @impl Crawly.Spider
    def init() do

    end

    @impl Crawly.Spider
    def parse_item(response) do

    end
 end

Il s'agit simplement du squelette du fichier ; il ne s'exécutera pas et ne renverra aucun résultat. Commençons par passer en revue chaque fonction, puis nous les compléterons une par une.

La fonction base_url() est appelée une seule fois et renvoie l'URL de base du site web cible que le crawler va scraper ; elle sert également à filtrer les liens externes et à empêcher le crawler de les suivre. Vous ne voulez pas scraper l'intégralité d'Internet.

@impl Crawly.Spider
def base_url(), do: "https://www.ebay.com/"

La fonction init() est appelée une seule fois et sert à initialiser l'état par défaut du crawler ; dans ce cas, la fonction renvoie l'URL de départ (start_url) à partir de laquelle l'exploration commencera.

Remplacez votre fonction vide par celle-ci :

@impl Crawly.Spider
def init() do
[start_urls: ["https://www.ebay.com/sch/i.html?_nkw=ps5"]]
end

Toute la magie de l'extraction des données se passe dans la fonction parse_item(). Cette fonction est appelée pour chaque URL explorée. Au sein de cette fonction, nous utilisons l'analyseur HTML Floki pour extraire les champs dont nous avons besoin : title, url et price.

La fonction ressemblera à ceci :

@impl Crawly.Spider
def parse_item(response) do
    # Parse response body to document
    {:ok, document} = Floki.parse_document(response.body)

    # Create item (for pages where items exists)
    items =
        document
        |> Floki.find(".srp-results .s-item")
        |> Enum.map(fn x ->
         %{
           title: Floki.find(x, ".s-item__title span") |> Floki.text(),
           price: Floki.find(x, ".s-item__price") |> Floki.text(),
           url: Floki.find(x, ".s-item__link") |> Floki.attribute("href") |> Floki.text(),
         }
    end)

    %{items: items}
end

Comme vous l'avez peut-être remarqué, nous utilisons les classes que nous avons trouvées dans la section « Pour commencer - Inspection de la cible » pour extraire les données dont nous avons besoin à partir des éléments DOM.

Exécution du spider

Il est temps de tester le code et de vérifier qu'il fonctionne. Depuis le répertoire racine du projet, exécutez cette commande :

iex -S mix run -e "Crawly.Engine.start_spider(EbayScraper)"

Si vous utilisez PowerShell, veillez à remplacer iex par iex.bat, sinon vous obtiendrez une erreur concernant le paramètre -S inexistant. Utilisez cette commande pour PowerShell :

iex.bat -S mix run -e "Crawly.Engine.start_spider(EbayScraper)"

Vérification des résultats

Ouvrez le dossier ./temp et vérifiez le fichier .jl. Vous devriez voir un fichier texte contenant une liste d'objets JSON, un par ligne. Chaque objet contient les informations dont nous avions besoin à partir de la liste des produits eBay : titre, prix et URL.

Voici à quoi devrait ressembler l'objet produit :

{"url":"https://www.ebay.com/itm/204096893295?epid=19040936896&hash=item2f851f716f:g:3G8AAOSwNslhoSZW&amdata=enc%3AAQAHAAAA0Nq2ODU0vEdnTBtnKgiVKIcOMvqJDPem%2BrNHrG4nsY9c3Ny1bzsybI0zClPHX1w4URLWSfXWX%2FeKXpdgpOe%2BF8IO%2FCh77%2FycTnMxDQNr5JfvTQZTF4%2Fu450uJ3RC7c%2B9ze0JHQ%2BWrbWP4yvDJnsTTWmjSONi2Cw71QMP6BnpfHBkn2mNzJ7j3Y1%2FSTIqcZ%2F8akkVNhUT0SQN7%2FBD38ue9kiUNDw9YDTUI1PhY14VbXB6ZMWZkN4hCt6gCDCl5mM7ZRpfYiDaVjaWVCbxUIm3rIg%3D%7Ctkp%3ABFBMwpvFwvRg","title":"PS5 Sony PlayStation 5 Console Disc Version! US VERSION!","price":"$669.99"}

Améliorer le spider

Nous avons récupéré tous les produits de la première page de la liste, mais cela ne suffit pas. Il est temps d'implémenter la pagination et de laisser le crawler extraire tous les produits disponibles.

Modifions la fonction parse_item() et ajoutons un nouveau bloc qui crée une structure requests avec le lien de pagination suivant. Ajoutez ce code après le code des éléments :

# Extract the next page link and convert it to a request
requests =
  document
  |> Floki.find(".s-pagination a.pagination__next")
  |> Floki.attribute("href")
  |> Crawly.Utils.build_absolute_urls(response.request_url)
  |> Crawly.Utils.requests_from_urls()

Mettez à jour l'instruction return de la fonction parse_item() pour inclure également les requêtes suivantes. La structure ressemblera à ceci :

%{
  :requests => requests,
  :items => items
}

Relancez le crawler, mais cette fois-ci, préparez-vous un café. Le scraping de toutes les pages contenant les annonces PS5 prendra quelques minutes.

Une fois que le crawler a terminé sa tâche, consultez le dossier ./temp pour voir les résultats du scraping. Vous avez réussi à extraire les consoles PS5 sur eBay et disposez d'une liste avec leurs prix. Vous pouvez étendre ce crawler pour extraire n'importe quel autre produit.

Conclusion

Dans cet article, vous avez appris ce qu’est un scraper web, quels sont les cas d’utilisation de ces crawlers, comment utiliser des bibliothèques prêtes à l’emploi pour configurer un scraper avec Elixir en quelques minutes, et comment l’exécuter et extraire les données réelles.

Si vous avez trouvé que cela représentait beaucoup de travail, j'ai une mauvaise nouvelle à vous annoncer : nous n'avons fait qu'effleurer le sujet. L'exécution prolongée de ce scraper va vous causer plus de problèmes que vous ne pouvez l'imaginer.

eBay détectera votre activité et la jugera suspecte ; le crawler commencera à recevoir des captchas ; vous devrez étendre les fonctionnalités du crawler pour résoudre ces captchas

Les systèmes de détection d'eBay pourraient signaler votre adresse IP et vous empêcher d'accéder au site web ; vous devrez vous procurer un pool de proxys et faire tourner les adresses IP à chaque requête. 

Vous avez déjà la tête qui tourne ? Abordons un autre problème : l'agent utilisateur. Vous devez constituer une vaste base de données d'agents utilisateurs et faire tourner cette valeur à chaque requête. Les systèmes de détection bloquent les scrapers en fonction de l'adresse IP et de l'agent utilisateur.

Si vous souhaitez vous concentrer davantage sur l'aspect commercial et investir votre temps dans l'extraction de données plutôt que dans la résolution des problèmes de détection, il est préférable d'utiliser un scraper en tant que service. Une solution telle que WebScrapingAPI résout tous les problèmes présentés ci-dessus, ainsi que bien d'autres encore. 

À propos de l'auteur
Robert Sfichi, Développeur full-stack @ WebScrapingAPI
Robert SfichiDéveloppeur full-stack

Robert Sfichi fait partie de l'équipe de WebScrapingAPI ; il contribue au développement du produit et aide à mettre en place des solutions fiables au service de la plateforme et de ses utilisateurs.

Commencez à créer

Prêt à faire évoluer votre système de collecte de données ?

Rejoignez plus de 2 000 entreprises qui utilisent WebScrapingAPI pour extraire des données Web à l'échelle de l'entreprise, sans aucun coût d'infrastructure.