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_requestpour les téléchargements,process_spider_inputpour 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_responsepour les téléchargements,process_spider_outputpour 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_requestpeut 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_requestetprocess_exception) et commence à appeler les méthodesprocess_responsedes Middlewares précédents. - Une
IgnoreRequest: L'exception est levée, et tous lesprocess_exceptiondes Middlewares précédents sont appelés.
process_responsedoit 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 lesprocess_exceptiondes Middlewares précédents sont appelés.
- Une
process_exceptionpeut retourner :None: L'exception est transmise auxprocess_exceptiondes Middlewares précédents.- Une
Response: Scrapy arrête la propagation de l'exception et commence à appeler lesprocess_responsedes 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 (ItemsouRequests).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
ItemsouRequestsgé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
HttpErrorMiddlewareinté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.