Exploration des Middlewares de Scrapy : Téléchargement et Araignée

Scrapy est un framework puissant et extensible pour le web scraping, conçu autour d'une architecture asynchrone et modulaire. Au cœur de cette architecture se trouvent les composants clés qui interagissent de manière structurée pour collecter et traiter les données. Parmi ces composants, les Middlewares jouent un rôle essentiel en enterceptant et en manipulant le flux de données entre les différentes parties du système.

Dans un système Scrapy, les requêtes (Requests) et les réponses (Responses), ainsi que les éléments extraits (Items), transitent entre des entités comme le Moteur (Engine), le Planificateur (Scheduler), le Téléchargeur (Downloader) et les Araignées (Spiders). Les Middlewares se positionnent comme des "points d'interception" dans ce flux, permettant d'appliquer une logique personnalisée à ces données. Leur fonction principale est de manipuler les requêtes sortantes ou les réponses entrantes, ou d'ajuster la manière dont les araignées traitent ces informations.

Les Middlewares de Téléchargement (Downloader Middlewares)

Les Middlewares de Téléchargement opèrent entre le Moteur Scrapy et le Téléchargeur. Ils interceptent chaque Request avant qu'elle ne soit envoyée au Téléchargeur, et chaque Response après qu'elle a été reçue du Téléchargeur mais avant d'être transmise à l'Araignée. Cela en fait le lieu idéal pour des tâches telles que :

  • Modification des en-têtes de requête (par exemple, User-Agent, Referer).
  • Gestion des cookies et des sessions.
  • Ajout de proxys à la volée.
  • Détection et gestion des timeouts ou des tentatives.
  • Filtrage de requêtes basées sur leur contenu ou leur destination.
  • Cache des requêtes/réponses.

Les Middlewares d'Araignée (Spider Middlewares)

Les Middlewares d'Araignée se situent entre le Moteur Scrapy et les Araignées (Spiders). Ils interceptent les Responses une fois que le Moteur les a reçues du Téléchargeur et avant qu'elles ne soient passées à la méthode parse de l'Araignée. Ils interceptent également les Items et les Requests générés par l'Araignée avant qu'ils ne soient renvoyés au Moteur. Leurs applications incluent :

  • Filtrage des réponses en fonction de leur code d'état ou de leur contenu.
  • Enrichissement ou modification des éléments (Items) extraits.
  • Modification ou création de nouvelles requêtes à partir du résultat de l'araignée.
  • Gestion de la profondeur de crawling.
  • Traitement des erreurs spécifiques aux araignées.

Activation et Ordre des Middlewares

Pour activer un Middleware, vous devez l'ajouter à la configuration de votre projet Scrapy (généralement dans le fichier settings.py) via les dictionnaires DOWNLOADER_MIDDLEWARES ou SPIDER_MIDDLEWARES. Chaque entrée est un chemin vers la classe du Middleware et une valeur numérique (priorité) :

# Dans settings.py

# Exemple d'activation de Middlewares de Téléchargement
DOWNLOADER_MIDDLEWARES = {
    'scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware': 100,
    'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None, # Désactive le middleware User-Agent par défaut
    'mon_projet.middlewares.MiddlewareAgentUtilisateurPersonnalise': 501, # Mon middleware personnalisé
    'mon_projet.middlewares.MiddlewareGestionCookies': 700,
}

# Exemple d'activation de Middlewares d'Araignée
SPIDER_MIDDLEWARES = {
    'scrapy.spidermiddlewares.httperror.HttpErrorMiddleware': 50,
    'mon_projet.middlewares.MiddlewareValidationItems': 100,
}

La valeur numérique (entre 0 et 1000) détermine l'ordre d'exécution. Les Middlewares sont fusionnés avec les Middlewares de base (DOWNLOADER_MIDDLEWARES_BASE, SPIDER_MIDDLEWARES_BASE) de Scrapy. La logique d'ordre est la suivante :

  • Pour les méthodes qui traitent les entrées (process_request pour les téléchargements, process_spider_input pour les araignées), les Middlewares avec une valeur numérique plus petite (plus proche du moteur) sont appelés en premier.
  • Pour les méthodes qui traitent les sorties (process_response pour les téléchargements, process_spider_output pour les araignées), les Middlewares avec une valeur numérique plus grande (plus éloignés du moteur) sont appelés en premier.

Si vous souhaitez désactiver un Middleware intégré de Scrapy, il suffit de lui attribuer la valeur None.

Écrire un Middleware de Téléchargement

Un Middleware de Téléchargement peut implémenter plusieurs méthodes :

  • process_request(request, spider) : Appelée pour chaque requête sortante.
  • process_response(request, response, spider) : Appelée pour chaque réponse entrante.
  • process_exception(request, exception, spider) : Appelée si une exception est levée pendant le traitement d'une requête.
  • from_crawler(cls, crawler) : Méthode de classe pour instancier le Middleware, permettant d'accéder aux paramètres du crawler.

Exemple : Gestion de Cookies Automatisée

Bien que Scrapy intègre déjà un middleware de cookies, voici un exemple simplifié qui illustre la gestion des cookies à travers un middleware personnalisé. Une implémentation réelle utiliserait scrapy.http.cookies.CookieJar pour une gestion robuste.

import logging
from scrapy.exceptions import NotConfigured
from scrapy.http.cookies import CookieJar

logger = logging.getLogger(__name__)

class MiddlewareGestionCookies:
    """
    Middleware pour gérer les cookies de manière persistante entre les requêtes.
    Utilise scrapy.http.cookies.CookieJar pour une gestion plus robuste.
    """
    def __init__(self):
        self.jar = CookieJar() # Utilise CookieJar pour stocker les cookies

    @classmethod
    def from_crawler(cls, crawler):
        # Vérifie si la fonctionnalité de cookies est activée dans les paramètres
        if not crawler.settings.getbool('ACTIVER_MIDDLEWARE_COOKIES', False):
            raise NotConfigured("Le MiddlewareGestionCookies n'est pas activé.")
        return cls()

    def process_request(self, request, spider):
        # Ajoute les cookies sauvegardés à la requête
        self.jar.add_cookie_header(request)
        logger.debug(f"Cookies ajoutés à la requête pour {request.url}")
        return None # Laisse passer la requête aux middlewares suivants

    def process_response(self, request, response, spider):
        # Met à jour les cookies avec ceux reçus dans la réponse
        self.jar.extract_cookies(response, request)
        logger.debug(f"Cookies mis à jour depuis la réponse de {response.url}")
        return response # Renvoie la réponse modifiée ou non

    def process_exception(self, request, exception, spider):
        logger.error(f"Erreur lors du traitement des cookies pour {request.url}: {exception}")
        # Vous pouvez décider de renvoyer None, une Request, ou une Response ici
        return None

Pour l'activer, ajoutez-le à DOWNLOADER_MIDDLEWARES et configurez ACTIVER_MIDDLEWARE_COOKIES = True dans settings.py.

Exemple : Intégration de Selenium pour le Rendement JavaScript

Parfois, les pages web nécessitent l'exécution de JavaScript pour afficher leur contenu. Scrapy, étant un moteur de requêtes HTTP pur, ne peut pas le faire nativement. Un Middleware de Téléchargement peut intégrer un navigateur comme Selenium pour gérer ce cas.

import scrapy
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.common.exceptions import TimeoutException, WebDriverException
from scrapy.http import HtmlResponse
import logging

logger = logging.getLogger(__name__)

# Exemple de Spider qui pourrait utiliser ce middleware
class MonSpiderSelenium(scrapy.Spider):
    name = 'mon_spider_selenium'
    start_urls = ['https://www.example.com'] # URL d'exemple

    def parse(self, response):
        logger.info(f"Page traitée par le spider : {response.url}")
        # Le contenu de `response.body` ici proviendra de Selenium si le middleware est activé.
        titles = response.css('h1::text').getall()
        for title in titles:
            yield {'title': title.strip()}
        
        # Pour demander une autre page via Selenium (si nécessaire)
        # yield scrapy.Request("https://www.another-js-page.com", meta={'selenium': True}, callback=self.parse)


# Middleware Selenium
class MiddlewareSelenium:
    """
    Middleware qui utilise Selenium pour charger les pages JavaScript-rendues.
    Nécessite la configuration de SELENIUM_EXECUTABLE_PATH dans settings.py.
    """
    def __init__(self, executable_path, browser_options=None):
        service = Service(executable_path=executable_path)
        options = webdriver.ChromeOptions()
        if browser_options:
            for opt in browser_options:
                options.add_argument(opt)
        self.driver = webdriver.Chrome(service=service, options=options)
        logger.info("Navigateur Selenium initialisé.")

    @classmethod
    def from_crawler(cls, crawler):
        chemin_exec = crawler.settings.get('SELENIUM_EXECUTABLE_PATH')
        if not chemin_exec:
            raise NotConfigured("Le chemin de l'exécutable Selenium n'est pas configuré (SELENIUM_EXECUTABLE_PATH).")
        options_navigateur = crawler.settings.getlist('SELENIUM_BROWSER_OPTIONS', [])
        return cls(executable_path=chemin_exec, browser_options=options_navigateur)

    def process_request(self, request, spider):
        # Vérifie si la requête demande explicitement l'utilisation de Selenium
        if request.meta.get('selenium'):
            logger.debug(f"Traitement Selenium pour : {request.url}")
            try:
                self.driver.get(request.url)
                # Attente implicite ou explicite ici pour le chargement complet JS
                
                # Retourne une HtmlResponse avec le contenu de la page rendue par Selenium
                return HtmlResponse(url=self.driver.current_url,
                                    body=self.driver.page_source,
                                    request=request,
                                    encoding='utf-8',
                                    status=200)
            except TimeoutException:
                logger.warning(f"Timeout Selenium pour : {request.url}")
                return HtmlResponse(url=request.url, status=500, request=request)
            except WebDriverException as e:
                logger.error(f"Erreur WebDriver pour {request.url}: {e}")
                return HtmlResponse(url=request.url, status=500, request=request)
        return None # Laisser Scrapy gérer la requête normalement si 'selenium' n'est pas dans meta

    def close_spider(self, spider):
        # S'assure de fermer le navigateur quand le spider se termine
        logger.info("Fermeture du navigateur Selenium.")
        self.driver.quit()


Dans ce cas, le process_request renvoie directement une HtmlResponse. Lorsque cela se produit, Scrapy ne continue pas à appeler les autres méthodes process_request ou process_exception des middlewares suivants. Il saute directement à la phase de traitement des réponses en appelant les méthodes process_response des middlewares précédemment exécutés, puis la méthode parse du spider. Cela est crucial pour éviter des interactions contradictoires avec d'autres middlewares de téléchargement (comme ceux qui modifieraient les requêtes HTTP traditionnelles).

Valeurs de Retour des Méthodes de Téléchargement

  • process_request peut retourner :
    • None : La requête est transmise aux Middlewares suivants. C'est le comportement par défaut.
    • Une Request : Scrapy arrête le traitement par les Middlewares de Téléchargement actuels et renvoie la nouvelle requête au Moteur pour un nouveau cycle.
    • Une Response : Scrapy ignore les Middlewares de Téléchargement restants (process_request et process_exception) et commence à appeler les méthodes process_response des Middlewares précédents.
    • Une IgnoreRequest : L'exception est levée, et tous les process_exception des Middlewares précédents sont appelés.
  • process_response doit retourner :
    • Une Response : La réponse est transmise au Middleware de Téléchargement suivant (ou à l'Araignée).
    • Une Request : Scrapy renvoie cette nouvelle requête au Moteur pour un nouveau cycle de traitement.
    • Une IgnoreRequest : L'exception est levée, et tous les process_exception des Middlewares précédents sont appelés.
  • process_exception peut retourner :
    • None : L'exception est transmise aux process_exception des Middlewares précédents.
    • Une Response : Scrapy arrête la propagation de l'exception et commence à appeler les process_response des Middlewares précédents.
    • Une Request : Scrapy arrête la propagaiton de l'exception et renvoie la nouvelle requête au Moteur.

Écrire un Middleware d'Araignée

Les Middlewares d'Araignée implémentent des méthodes similaires pour intercepter les flux de données propres aux araignées :

  • process_spider_input(response, spider) : Appelée avant que la réponse ne soit traitée par l'araignée.
  • process_spider_output(response, result, spider) : Appelée après que l'araignée a traité la réponse et retourné des résultats (Items ou Requests).
  • process_spider_exception(response, exception, spider) : Appelée si une exception est levée dans l'araignée lors du traitement d'une réponse.
  • process_start_requests(start_requests, spider) : Appelée pour traiter les requêtes initiales (start_requests) du spider.
  • from_crawler(cls, crawler) : Méthode de classe pour l'instanciation.

Utilisations Courantes

Les Middlewares d'Araignée sont particulièrement utiles pour des tâches comme :

  • Filtrage des résultats : Examiner et potentiellement filtrer les Items ou Requests générés par l'araignée.
  • Suivi de la profondeur de crawl : Ajouter des informations de profondeur aux requêtes ou filtrer les requêtes qui dépassent une certaine profondeur.
  • Gestion des codes d'état HTTP non-200 : Bien que le HttpErrorMiddleware intégré de Scrapy gère cela, un middleware personnalisé pourrait implémenter une logique plus spécifique.

Différences Stratégiques

Bien que les deux types de Middlewares puissent sembler similaires dans leur fonction d'interception, leur positionnement dans l'architecture Scrapy leur confère des impacts différents :

  • Un Midldeware de Téléchargement agit sur les données qui vont ou viennent directement d'internet. Toute modification d'une requête ici affecte la manière dont le Téléchargeur interagit avec le serveur distant.
  • Un Middleware d'Araignée agit sur les données qui sont déjà arrivées dans le système Scrapy (la réponse) ou qui sont générées par l'araignée elle-même (éléments, nouvelles requêtes). Ses modifications impactent le flux de données interne de Scrapy, en particulier la manière dont l'araignée traite son travail et ce qu'elle renvoie au Moteur.

Par exemple, filtrer des requêtes pour un domaine non autorisé avec un Middleware d'Araignée est généralement plus efficace qu'avec un Middleware de Téléchargement, car cela évite que ces requêtes ne soient même envoyées au Planificateur puis au Téléchargeur, réduisant ainsi la charge sur ces composants et optimisant les ressources.

En somme, les Middlewares sont un pilier de la flexibilité de Scrapy, offrant des points d'extension puissants pour adapter le comportement du framework à des besoins de scraping complexes.

Étiquettes: Scrapy middleware WebScraping Python ArchitectureDistribuée

Publié le 12 juillet à 16h26