Scraping Web avec Scrapy Splash : Guide étape par étape

Maîtrisez Scrapy Splash pour le web scraping en Python. Apprenez à rendre JavaScript, à extraire des données et à gérer efficacement les pages dynamiques.
18 min de lecture
Web Scraping with Scrapy Splash blog image

Dans ce guide sur Scrapy Splash, vous apprendrez :

  • Ce qu’est Scrapy Splash
  • Comment utiliser Scrapy Splash en Python dans un tutoriel pas à pas
  • Techniques avancées de scraping avec Splash dans Scrapy
  • Limites de l’utilisation de cet outil pour le scraping de sites web

Plongeons dans l’aventure !

Qu’est-ce que Scrapy Splash ?

Scrapy Splash fait référence à l’intégration entre ces deux outils :

  • Scrapy: Une bibliothèque Python à code source ouvert permettant d’extraire les données dont vous avez besoin des sites web.
  • Splash: un navigateur léger sans tête conçu pour le rendu de pages web à forte composante JavaScript.

Vous vous demandez peut-être pourquoi un outil aussi puissant que Scrapy a besoin de Splash. Eh bien, Scrapy ne peut gérer que des sites statiques, car il s’appuie sur des capacités d’analyse HTML (en particulier, à partir de Parsel). Cependant, lorsque vous scrapez des sites web dynamiques, vous devez gérer le rendu JavaScript. Une solution courante consiste à utiliser un navigateur automatisé, ce qui est exactement ce que Splash fournit.

Avec Scrapy Splash, vous pouvez envoyer une requête spéciale – connue sous le nom de SplashRequest - àun serveur Splash. Ce serveur rend entièrement la page en exécutant du JavaScript et renvoie le code HTML traité. Il permet donc à votre Scrapy Spider de récupérer des données à partir de pages dynamiques.

En bref, vous avez besoin de Scrapy Splash si :

  • Vous travaillez avec des sites web à forte composante JavaScript que Scrapy seul ne peut pas analyser.
  • Vous préférez une solution légère par rapport à Selenium ou Playwright.
  • Vous voulez éviter les frais généraux liés à l’exécution d’un navigateur complet pour le scraping.

Si Scrapy Splash ne répond pas à vos besoins, envisagez les alternatives suivantes :

  1. Selenium: Une capacité d’automatisation complète du navigateur pour l’exploration de sites web à forte composante JavaScript, qui fournit des extensions intéressantes comme Selenium Wire.
  2. Playwright: Un outil d’automatisation de navigateur open-source offrant une automatisation cohérente entre les navigateurs et une API robuste, qui prend en charge plusieurs langages de programmation.
  3. Puppeteer: Une bibliothèque Node.js open-source développée qui fournit une API de haut niveau pour automatiser et contrôler Chrome via le protocole DevTools.

Scrapy Splash en Python : Un tutoriel pas à pas

Dans cette section, vous comprendrez comment utiliser Scrapy Splash pour extraire des données d’un site web. La page cible sera une version spéciale en rendu JavaScript du site populaire “Quotes to Scrape” :

La page cible du tutoriel

Il s’agit de la même chose que l’habituel “Quotes to Scrape”, mais elle utilise le défilement infini pour charger les données de manière dynamique via des requêtes AJAX déclenchées par JavaScript.

Exigences

Pour reproduire ce tutoriel en utilisant Scrapy Splash en Python, votre système doit répondre aux exigences suivantes :

Si ces deux outils ne sont pas installés sur votre machine, suivez les liens ci-dessus.

Conditions préalables, dépendances et intégration de Splash

Supposons que vous appeliez le dossier principal de votre projet scrapy_splash/. À la fin de cette étape, le dossier aura la structure suivante :

scrapy_splash/
    └── venv/

venv/ contient l’environnement virtuel. Vous pouvez créer le répertoire de l ‘environnement virtuel venv/ de la manière suivante :

python -m venv venv

Pour l’activer, sous Windows, exécutez :

venv\Scripts\activate

De manière équivalente, sous macOS et Linux, exécutez :

source venv/bin/activate

Dans l’environnement virtuel activé, installez les dépendances avec :

pip install scrapy scrapy-splash

Comme dernier prérequis, vous devez extraire l’image Splash via Docker:

docker pull scrapinghub/splash

Ensuite, démarrez le conteneur :

docker run -it -p 8050:8050 --rm scrapinghub/splash

Pour plus d’informations, suivez les instructions d’intégration de Docker basées sur le système d’exploitation.

Après avoir démarré le conteneur Docker, attendez que le service Splash enregistre le message ci-dessous :

Server listening on http://0.0.0.0:8050

Le message signale que Splash est désormais disponible à l’adresse http://0.0.0.0:8050. Visitez cette URL dans votre navigateur et vous devriez voir la page suivante :

Fonctionnement du service Splash

En fonction de votre configuration, il se peut que l’URL http://0.0.0.0:8050 ne permette pas au service Splash de fonctionner. Dans ce cas, essayez d’utiliser l’une des URL suivantes :

  • http://localhost:8050
  • http://127.0.0.1:8050

Note: Rappelez-vous que la connexion au serveur Splash doit rester ouverte pendant l’utilisation de Scrapy-Splash. En d’autres termes, si vous avez utilisé le CLI pour exécuter le conteneur Docker, gardez ce terminal ouvert et utilisez un autre terminal pour les étapes suivantes de cette procédure.

Merveilleux ! Vous avez maintenant ce qu’il vous faut pour récupérer des pages web avec Scrapy Splash.

Étape 1 : Démarrer un nouveau projet Scrapy

Dans le dossier principal scrapy_splash/, tapez la commande ci-dessous pour lancer un nouveau projet Scrapy :

scrapy startproject quotes

Avec cette commande, Scrapy créera un dossier quotes/. A l’intérieur, il générera automatiquement tous les fichiers dont vous avez besoin. Voici la structure du dossier qui en résulte :

scrapy_splash/
   ├──  quotes/ 
   │       ├── quotes/
   │       │      ├── spiders/ 
   │       │      ├── __init__.py  
   │       │      ├── items.py  
   │       │      ├── middlewares.py 
   │       │      ├── pipelines.py  
   │       │      └── settings.py  
   │       │     
   │       └── scrapy.cfg  
   └── venv/   

C’est parfait ! Vous avez démarré un nouveau projet Scrapy.

Étape 2 : Générer l’araignée

Pour générer un nouveau robot d’exploration du site web cible, naviguez jusqu’au dossier quotes/:

cd quotes

Ensuite, générez une nouvelle araignée avec :

scrapy genspider words https://quotes.toscrape.com/scroll

Vous obtiendrez le résultat suivant :

Created spider 'words' using template 'basic' in module:
  quotes.spiders.words

Comme vous pouvez le constater, Scrapy a automatiquement créé un fichier words.py dans le dossier spiders/. Le fichier words.py contient le code suivant :

import scrapy

class WordsSpider(scrapy.Spider):
    name = "words"
    allowed_domains = ["quotes.toscrape.com"]
    start_urls = ["https://quotes.toscrape.com/scroll"]
    def parse(self, response):
        pass

Celui-ci contiendra bientôt la logique de récupération nécessaire à partir de la page cible dynamique.

Hourra ! Vous avez généré le spider pour qu’il scrape le site web cible.

Étape 3 : Configurer Scrapy pour utiliser Splash

Vous devez maintenant configurer Scrapy pour qu’il puisse utiliser le service Splash. Pour ce faire, ajoutez les configurations suivantes au fichier settings.py:

# Set the Splash local server endpoint
SPLASH_URL = "http://localhost:8050"
# Enable the Splash downloader middleware
DOWNLOADER_MIDDLEWARES = {
    "scrapy_splash.SplashCookiesMiddleware": 723,
    "scrapy_splash.SplashMiddleware": 725,
    "scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware": 810,
}
# Enable the Splash deduplication argument filter
SPIDER_MIDDLEWARES = {
    "scrapy_splash.SplashDeduplicateArgsMiddleware": 100,
}

Dans les configurations ci-dessus :

  • SPLASH_URL définit le point de terminaison du serveur Splash local. C’est là que Scrapy enverra les demandes de rendu JavaScript.
  • DOWNLOADER_MIDDLEWARES permet à des middlewares spécifiques d’interagir avec Splash. En particulier
    :Polylang placeholder do not modify
  • SPIDER_MIDDLEWARES garantit que les requêtes ayant les mêmes arguments Splash ne sont pas dupliquées, ce qui est utile pour réduire la charge inutile et améliorer l’efficacité.

Pour plus d’informations sur ces configurations, consultez la documentation officielle de Scrapy-Splash.

C’est bien ! Maintenant Scrapy peut se connecter à Splash et l’utiliser de manière programmatique pour le rendu JavaScript.

Étape 4 : Définir le script Lua pour le rendu JavaScript

Scrapy peut désormais s’intégrer à Splash pour rendre les pages web qui reposent sur JavaScript, comme la page cible de ce guide. Pour définir un rendu personnalisé et une logique d’interaction, vous devez utiliser des scripts Lua. En effet, Splash s’appuie sur les scripts Lua pour interagir avec les pages web via JavaScript et contrôler le comportement du navigateur de manière programmatique.

Plus précisément, ajoutez le script Lua ci-dessous à words.py :

script = """
function main(splash, args)  
      splash:go(args.url)

      -- custom rendering script logic...

      return splash:html()
    end
"""

Dans l’extrait ci-dessus, la variable script contient la logique Lua que Splash exécutera sur le serveur. En particulier, ce script demande à Splash de.. :

  1. Naviguer vers l’URL définie à l’aide de la méthode splash:go().
  2. Renvoyer le contenu HTML rendu avec la méthode splash:html().

Utilisez le script Lua ci-dessus dans une fonction start_requests() à l’intérieur de la classe WordsSpider:

def start_requests(self):
    for url in self.start_urls:
        yield SplashRequest(
            url,
            self.parse,
            endpoint="execute",
            args={"lua_source": script}
        )

La méthode start_requests() ci-dessus remplace la méthode start_requests() par défaut de Scrapy. De cette manière, Scrapy Splash peut exécuter le script Lua pour récupérer le HTML de la page rendu en JavaScript. L’exécution du script Lua se fait via l’argument script "lua_source" dans la méthode SplashRequest(). Notez également l’utilisation du point de terminaison Splash "execute" (que vous découvrirez bientôt).

N’oubliez pas d’importer SplashRequest de Scrapy Splash :

from scrapy_splash import SplashRequest

Votre fichier words.py est maintenant équipé du script Lua correct pour accéder au contenu de la page rendu en JavaScript !

Étape 5 : Définir la logique d’analyse des données

Avant de commencer, inspectez un élément HTML de citation sur la page cible pour comprendre comment l’analyser :

La page inspectée

Vous pouvez y voir que les éléments du devis peuvent être sélectionnés avec .quote. A partir d’une citation, on peut alors obtenir :

  1. Le texte de la citation de .text.
  2. L’auteur de la citation provient de .author.
  3. Les balises de citation de .tags.

La logique de récupération de toutes les citations de la page cible peut être définie à l’aide de la méthode parse():

def parse(self, response):
    # Retrieve CSS selectors
    quotes = response.css(".quote")
    for quote in quotes:
        yield {
            "text": quote.css(".text::text").get(),
            "author": quote.css(".author::text").get(),
            "tags": quote.css(".tags a.tag::text").getall()
        }

parse() traite la réponse renvoyée par Splash. En détail, elle :

  1. Extrait tous les éléments div avec la classe quote en utilisant le sélecteur CSS ".quote".
  2. Itère sur chaque élément de citation pour extraire le nom, l’auteur et le tag de chaque citation.

Très bien ! La logique de scraping de Scrapy Splash est terminée.

Étape 6 : Assembler le tout et exécuter le script

Voici à quoi devrait ressembler votre fichier words.py final :

import scrapy
from scrapy_splash import SplashRequest

# Lua script for JavaScript rendering
script = """
function main(splash, args)  
      splash:go(args.url)

      return splash:html()
    end
"""

class WordsSpider(scrapy.Spider):
    name = "words"
    start_urls = ["https://quotes.toscrape.com/scroll"]
    def start_requests(self):
        for url in self.start_urls:
            yield SplashRequest(
                url,
                self.parse,
                endpoint="execute",
                args={"lua_source": script}
            )
    def parse(self, response):
        quotes = response.css(".quote")
        for quote in quotes:
            yield {
                "text": quote.css(".text::text").get(),
                "author": quote.css(".author::text").get(),
                "tags": quote.css(".tags a.tag::text").getall()
            }

Exécutez le script à l’aide de cette commande :

scrapy crawl words

C’est le résultat attendu :

Toutes les citations grattées avec Scrapy Splash

Le résultat souhaité peut être mieux visualisé de la manière suivante :

2025-03-18 12:21:55 [scrapy.core.scraper] DEBUG: Scraped from <200 https://quotes.toscrape.com/scroll>
{'text': '“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”', 'author': 'Albert Einstein', 'tags': ['change', 'deep-thoughts', 'thinking', 'world']}

2025-03-18 12:21:55 [scrapy.core.scraper] DEBUG: Scraped from <200 https://quotes.toscrape.com/scroll>
{'text': '“It is our choices, Harry, that show what we truly are, far more than our abilities.”', 'author': 'J.K. Rowling', 'tags': ['abilities', 'choices']}

# omitted for brevity...

2025-03-18 12:21:55 [scrapy.core.engine] INFO: Closing spider (finished)

Notez que la sortie contient les données qui vous intéressent.

Notez que si vous supprimez la méthode start_requests() de la classe Words``Spider, Scrapy ne renverra aucune donnée. C’est parce que, sans Splash, il ne peut pas rendre les pages qui nécessitent JavaScript.

C’est très bien ! Vous avez réalisé votre premier projet Scrapy Splash.

Une note sur les éclaboussures

Splash est un serveur qui communique via HTTP. Cela vous permet de récupérer des pages web avec Splash en utilisant n’importe quel client HTTP, en appelant ses points de terminaison. Les points d’accès qu’il fournit sont les suivants :

  • exécuter: Exécute un script de rendu Lua personnalisé et renvoie son résultat.
  • render.html: renvoie le code HTML de la page rendue en javascript.
  • render.png: renvoie une image (au format PNG) de la page rendue en javascript.
  • render.jpeg: Renvoie une image (au format JPEG) de la page rendue en javascript.
  • render.har: renvoie des informations sur l’interaction de Splash avec un site web au format HAR.
  • render.json: Renvoie un dictionnaire codé en JSON contenant des informations sur la page web rendue en javascript. Il peut inclure des informations HTML, PNG et autres, en fonction des arguments passés.

Pour mieux comprendre le fonctionnement de ces points d’accès, prenons l’exemple du point d’accès render.html. Connectez-vous au point de terminaison avec ce code Python :

# pip install requests
import requests
import json

# URL of the Splash endpoint
url = "http://localhost:8050/render.html"

# Sending a POST request to the Splash endpoint
payload = json.dumps({
  "url": "https://quotes.toscrape.com/scroll" # URL of the page to render
})
headers = {
  "content-type": "application/json"
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)

Cet extrait définit :

  • L’instance Splash sur localhost en tant qu’URL faisant un appel au point de terminaison render.html.
  • La page cible à récupérer dans la charge utile.

Exécutez le code ci-dessus et vous obtiendrez le rendu HTML de la page entière :

<!DOCTYPE html>
<html lang="en">
  <head>
      <meta charset="UTF-8">
      <title>Quotes to Scrape</title>
      <link rel="stylesheet" href="/static/bootstrap.min.css">
      <link rel="stylesheet" href="/static/main.css">
  </head>
  <body>
      <!-- omitted for brevity... -->
  </body>
</html>

Bien que Splash puisse gérer indépendamment le HTML rendu par JavaScript, l’utilisation de Scrapy Splash avec SplashRequest facilite grandement le web scraping.

Scrapy Splash : Techniques avancées de scraping

Dans le paragraphe précédent, vous avez terminé un tutoriel Scrapy de base avec l’intégration de Splash. Il est temps d’essayer des techniques de scraping avancées avec Scrapy Splash !

Gestion du défilement avancé

La page cible contient des citations qui sont chargées dynamiquement via AJAX grâce au défilement infini :

Défilement infini sur la page cible

Pour gérer l’interaction avec le défilement infini, vous devez modifier le script Lua comme suit :

script = """
function main(splash, args)
    local scroll_delay = 1.0  -- Time to wait between scrolls
    local max_scrolls = 10    -- Maximum number of scrolls to perform
    local scroll_to = 1000    -- Pixels to scroll down each time

    splash:go(args.url)
    splash:wait(scroll_delay)

    local scroll_count = 0
    while scroll_count < max_scrolls do
        scroll_count = scroll_count + 1
        splash:runjs("window.scrollBy(0, " .. scroll_to .. ");")
        splash:wait(scroll_delay)
    end

    return splash:html()
end
"""

Ce script modifié s’appuie sur ces variables :

  • max_scrolls définit le nombre maximal de défilements à effectuer. Cette valeur peut être modifiée en fonction de la quantité de contenu que vous souhaitez extraire de la page.
  • scroll_to spécifie le nombre de pixels à faire défiler vers le bas à chaque fois. Sa valeur peut devoir être ajustée en fonction du comportement de la page.
  • splash:runjs() exécute la fonction JavaScript window.scrollBy() pour faire défiler la page vers le bas du nombre de pixels spécifié.
  • splash:wait() permet au script d’attendre avant de charger un nouveau contenu. Le temps d’attente (en secondes) est défini par la variable scroll_delay.

En termes plus simples, le script Lua ci-dessus simule un nombre défini de défilements dans un scénario de page web à défilement infini.

Le code du fichier words.py ressemblera à ceci :

import scrapy
from scrapy_splash import SplashRequest

# Lua script for infinite scrolling
script = """
function main(splash, args)
    local scroll_delay = 1.0  -- Time to wait between scrolls
    local max_scrolls = 10    -- Maximum number of scrolls to perform
    local scroll_to = 1000    -- Pixels to scroll down each time

    splash:go(args.url)
    splash:wait(scroll_delay)

    local scroll_count = 0
    while scroll_count < max_scrolls do
        scroll_count = scroll_count + 1
        splash:runjs("window.scrollBy(0, " .. scroll_to .. ");")
        splash:wait(scroll_delay)
    end

    return splash:html()
end
"""

class WordsSpider(scrapy.Spider):
    name = "words"
    start_urls = ["https://quotes.toscrape.com/scroll"]
    def start_requests(self):
        for url in self.start_urls:
            yield SplashRequest(
                url,
                self.parse,
                endpoint="execute",
                args={"lua_source": script}
            )
    def parse(self, response):
        # Retrieve CSS selectors
        quotes = response.css("div.quote")
        for quote in quotes:
            yield {
                "text": quote.css("span.text::text").get(),
                "author": quote.css("span small.author::text").get(),
                "tags": quote.css("div.tags a.tag::text").getall()
            }

Exécutez le script à l’aide de la commande ci-dessous :

scrapy crawl words

Le crawler imprimera toutes les citations scrappées en fonction de la variable max_scrolls. C’est le résultat attendu :

Le résultat du nouveau script

Notez que la sortie comprend maintenant beaucoup plus de citations qu’auparavant. Cela confirme que les pages ont bien défilé et que de nouvelles données ont été chargées et extraites.

C’est parfait ! Vous avez maintenant appris à gérer le défilement infini avec Scrapy Splash.

Attendre l’élément

Les pages web peuvent récupérer des données de manière dynamique ou rendre des nœuds dans le navigateur. Cela signifie que le rendu final du DOM peut prendre du temps. Pour éviter les erreurs lors de la récupération de données sur un site web, vous devez toujours attendre qu’un élément soit chargé sur la page avant d’interagir avec lui.

Dans cet exemple, l’élément à attendre sera le texte de la première citation :

Comment récupérer le texte des citations sur la page cible ?

Pour mettre en œuvre la logique d’attente, écrivez un script Lua comme suit :

script = """
function main(splash, args)
       splash:go(args.url)

       while not splash:select(".text") do
         splash:wait(0.2)
         print("waiting...")
       end

       return { html=splash:html() }
    end
"""

Ce script crée une boucle while qui attend 0,2 seconde si l’élément texte se trouve sur la page. Pour vérifier si l’élément .text se trouve sur la page, vous pouvez utiliser la méthode splash:select().

Attendre le temps

Comme les pages web à contenu dynamique mettent du temps à se charger et à s’afficher, vous pouvez attendre quelques secondes avant d’accéder au contenu HTML. Cela peut être réalisé par la méthode splash:wait() comme suit :

script = """
function main(splash, args)
       splash:wait(args.wait)       
       splash:go(args.url)
       return { html=splash:html() }
    end
"""

Dans ce cas, les secondes que le script doit attendre sont exprimées dans la méthode SplashRequest() avec un argument de script Lua.

Par exemple, la valeur "wait" : 2.0 indique au script Lua qu’il doit attendre 2 secondes :

import scrapy
from scrapy_splash import SplashRequest

script = """
function main(splash, args)
       splash:wait(args.wait)       
       splash:go(args.url)
       return { html=splash:html() }
    end
"""

class WordsSpider(scrapy.Spider):
    name = "words"
    start_urls = ["https://quotes.toscrape.com/scroll"]
    def start_requests(self):
        for url in self.start_urls:
            yield SplashRequest(
                url,
                self.parse,
                endpoint="execute",
                args={"lua_source": script, "wait": 2.0} # Waiting for 2 seconds
            )
# ...

Remarque: une attente ferme(splash:wait()) est utile pour les tests locaux, car elle garantit le chargement de la page avant de poursuivre. Cette approche n’est pas idéale pour la production, car elle ajoute des délais inutiles, ce qui nuit aux performances et à l’évolutivité. En outre, vous ne pouvez pas connaître à l’avance le temps d’attente adéquat.

Bravo ! Tu as appris à attendre un certain temps dans Scrapy Splash.

Limites de l’utilisation de Scrapy Splash

Dans ce tutoriel, vous avez appris à utiliser Scrapy Splash pour extraire des données du Web dans différents scénarios. Bien que cette intégration soit simple, elle présente quelques inconvénients.

Par exemple, la mise en place de Splash nécessite l’exécution d’un serveur Splash séparé avec Docker, ce qui ajoute de la complexité à votre infrastructure de scraping. En outre, l’API de script Lua de Splash est quelque peu limitée par rapport à des outils plus modernes comme Puppeteer et Playwright.

Cependant, comme pour tous les navigateurs sans tête, la plus grande limitation vient du navigateur lui-même. Les technologies anti-scraping peuvent détecter si un navigateur est automatisé plutôt qu’utilisé normalement, ce qui entraîne des blocages de scripts.

Oubliez ces défis avec Scraping Browser, unnavigateur de scraping dédié basé sur le cloud et conçu pour une évolutivité infinie. Il comprend la résolution des CAPTCHA, la gestion des empreintes digitales du navigateur et le contournement des robots, de sorte que vous n’avez pas à craindre d’être bloqué.

Conclusion

Dans cet article, vous avez appris ce qu’est Scrapy Splash et comment il fonctionne. Vous avez commencé par les bases, puis vous avez exploré des scénarios de scraping plus complexes.

Vous avez également découvert les limites de l’outil, en particulier sa vulnérabilité aux systèmes anti-bots et anti-scraping. Pour surmonter ces difficultés, Scraping Browser est une excellente solution. Ce n’est qu’une des nombreuses solutions de scraping de Bright Data que vous pouvez essayer :

Inscrivez-vous à Bright Data dès maintenant et commencez votre essai gratuit pour tester nos solutions de scraping.

Aucune carte de crédit requise