Retour au blog
Guides
Mihai MaximLast updated on May 13, 202618 min read

Scraping Web avec Scrapy : 2026 Playbook

Scraping Web avec Scrapy : 2026 Playbook
En bref : voici un guide complet et subjectif sur le web scraping avec Scrapy en 2026. Vous apprendrez à installer Scrapy, à créer des prototypes de sélecteurs dans le terminal, à développer un robot d'exploration pour un site e-commerce multipages, à nettoyer les éléments avec des chargements d'éléments (Item Loaders), à enregistrer les données dans une base de données, à renforcer les paramètres pour éviter les bannissements, et à intégrer Scrapy-Playwright pour les pages rendues en JavaScript.

Scrapy est la colonne vertébrale du crawling Python sérieux depuis plus d’une décennie, et malgré l’arrivée d’une vague de nouvelles bibliothèques asynchrones, il reste toujours d’actualité. Si vous pratiquez le web scraping avec Scrapy aujourd’hui, vous disposez d’un framework bien pensé qui résout les aspects fastidieux (planification des requêtes, déduplication, tentatives de reconnexion, pipelines d’éléments) afin que vous puissiez vous concentrer sur les éléments qui posent réellement problème : les sélecteurs, l’anti-bot et le stockage.

Ce guide s’articule autour du cycle de vie des requêtes et des réponses plutôt que d’une progression chronologique. Chaque section correspond à un composant Scrapy que vous utiliserez en production, depuis le moteur et les middlewares de téléchargement jusqu’aux chargeurs d’éléments et aux exportations de flux. Nous utilisons une seule cible tout au long du guide, le site d’entraînement public books.toscrape.com, de sorte que chaque bloc de code s'intègre dans un modèle mental unique.

À la fin, vous disposerez d’un spider fonctionnel capable de paginer un catalogue, de valider et de nettoyer les éléments, d’écrire à la fois dans JSON Lines et SQLite, de réessayer en cas de 429 en cas de pannes, et se rabat sur un véritable navigateur lorsqu’une page nécessite du JavaScript. Nous signalerons également les parties du framework que les débutants utilisent systématiquement de manière erronée, avec des corrections à copier.

Pourquoi Scrapy domine toujours le scraping en production en 2026

Il est tentant de se tourner vers httpx plus selectolax et en rester là. Pour un script ponctuel, c'est la bonne décision. Pour un crawler qui doit s'exécuter chaque nuit, dédupliquer les URL, survivre à une panne partielle et écrire vers deux destinations, il vous faut un framework. Au moment où nous écrivons ces lignes, Scrapy reste la norme dans le secteur pour l'extraction de données à grande échelle, et la raison est simple : il est livré avec le planificateur, le filtre anti-duplication, le middleware de réessai, la limitation de débit, les signaux et les exportations de flux déjà intégrés.

Comparé à l'assemblage de requests et d'BeautifulSoups, Scrapy impose ses choix de manière utile. Il s'exécute sur la boucle d'événements de Twisted, ce qui permet à un seul processus de distribuer des centaines de requêtes simultanées sans la charge cognitive de async/await. Vous n'écrivez pas la boucle de crawl. Vous déclarez les URL d'entrée et la logique de parsing, et le moteur gère la file d'attente. C'est ce contrat qui justifie la courbe d'apprentissage plus raide de Scrapy.

Comment fonctionne le web scraping avec Scrapy : le cycle de vie des requêtes et des réponses

Avant d'écrire un spider, familiarisez-vous avec le cycle de vie. Une exécution de Scrapy se déroule comme suit :

  1. Le moteur extrait une Request du planificateur.
  2. La requête passe par les middlewares du téléchargeur (par ordre de priorité). C'est là que les en-têtes sont définis, que les cookies sont ajoutés, que les proxys sont alternés et que les tentatives de reconnexion sont déclenchées.
  3. Le téléchargeur émet l'appel HTTP et renvoie une Response.
  4. La réponse repasse par les middlewares du téléchargeur à l'aller, puis par ceux de l'araignée, avant d'arriver dans la fonction de rappel de votre araignée (généralement parse).
  5. Votre callback yieldgénère soit d'autres Request objets (qui retournent vers le planificateur) ou Items (qui sont acheminés vers les pipelines d'éléments).
  6. Les pipelines valident, transforment, suppriment ou persistent chaque élément.
  7. Tout ce qui survit est transmis à l'exportateur de flux, qui écrit sur le disque, sur S3 ou vers stdout.

Deux termes que vous verrez dans les callbacks : callback est la fonction que Scrapy exécute lorsqu'une requête aboutit, et errback est la fonction qu'il exécute lorsqu'une requête échoue. Les spiders sont généralement écrits sous forme de générateurs Python, produisant des requêtes et des éléments de manière paresseuse afin que le moteur puisse intercaler le travail.

Connaître cette boucle fait la différence entre « mon spider fonctionne » et « mon spider est évolutif ». Lorsque les pages reviennent vides, la réponse se trouve presque toujours dans la couche middleware du téléchargeur. Lorsque des éléments disparaissent, la réponse se trouve dans un pipeline. Lorsque la pagination ne fonctionne plus, c'est votre callback. Associez le symptôme à l'étape, puis corrigez le composant concerné.

Un guide plus détaillé est disponible dans la documentation officielle sur l'architecture de Scrapy, qu'il vaut la peine d'ajouter à vos favoris.

Installation de Scrapy et démarrage d'un projet

Scrapy cible Python 3 moderne (consultez le guide d'installation officiel pour connaître la version minimale au moment de l'installation). La documentation recommande vivement un environnement virtuel dédié afin que les dépendances fixées de Scrapy n'entrent pas en conflit avec les paquets système.

python -m venv .venv
source .venv/bin/activate     # Windows: .venv\Scripts\activate
pip install --upgrade pip
pip install scrapy
scrapy version

Une fois que scrapy version affiche une chaîne de version, créez un projet :

scrapy startproject bookstore
cd bookstore

Vous disposez désormais d'une arborescence de projet identique à celle de toutes les bases de code Scrapy existantes, ce qui est précisément le but recherché. Chaque fois que vous intégrez un nouveau dépôt Scrapy, vous savez déjà où se trouvent les robots d'indexation, où sont stockés les paramètres et quel fichier contient les pipelines. Cette reproductibilité représente la moitié de l'intérêt d'utiliser un framework. Résistez à la tentation d'aplatir la structure : les outils en aval tels que scrapyd et scrapy crawl en dépendent.

Au cœur d'un projet Scrapy : le rôle de chaque fichier

scrapy startproject génère cinq fichiers et un dossier que vous utiliserez quotidiennement.

  • scrapy.cfg est la configuration de projet de niveau supérieur. Elle nomme le projet et indique scrapyd où se trouve le module de paramètres.
  • items.py est la couche de schéma. Vous y définissez Product, Article, ou toutes les classes que vous souhaitez, chacune héritant de scrapy.Item. Considérez-la comme une classe de données pour les résultats du scraping.
  • pipelines.py C'est là que les éléments extraits sont nettoyés, validés, supprimés ou écrits dans une base de données. Chaque pipeline est une classe simple dotée d'une process_item méthode.
  • middlewares.py contient les middlewares de téléchargement et de spider. C'est le fichier où vous faites tourner les user agents, ajoutez des proxys ou acheminez les requêtes via une API de scraping gérée.
  • settings.py est l'objet de configuration central : la concurrence, la limitation, les tentatives de reconnexion, les pipelines, les middlewares et les exportations de flux se trouvent tous ici.
  • spiders/ est le dossier où se trouvent les fichiers spider individuels. Un spider par site cible est une bonne valeur par défaut.

Prototypage de sélecteurs dans le shell Scrapy

Le shell Scrapy est l'arme secrète dont personne ne parle assez. Avant d'écrire la moindre ligne de code de spider, ouvrez le shell sur une URL réelle et testez les sélecteurs de manière interactive. Cela vous fera gagner des heures.

scrapy shell "https://books.toscrape.com/catalogue/page-1.html"

Dans le shell, vous disposez d'un response préchargé avec la page. Trois commandes sont essentielles :

  • fetch("https://example.com") permet de remplacer la réponse par une nouvelle sans quitter le shell.
  • view(response) ouvre le code HTML téléchargé dans votre navigateur par défaut, ce qui vous permet de vérifier que vous travaillez avec le même DOM que celui vu par le spider, et non celui rendu que votre navigateur afficherait normalement.
  • response.css(...) et response.xpath(...) vous permet de tester des sélecteurs sur la réponse en direct.

Essayez ceci sur le site d'entraînement :

>>> response.css("article.product_pod h3 a::attr(title)").getall()[:3]
['A Light in the Attic', 'Tipping the Velvet', 'Soumission']
>>> response.xpath("//article[@class='product_pod']//p[@class='price_color']/text()").get()
'£51.77'

Répétez l'opération jusqu'à ce que les deux sélecteurs renvoient des données valides. Ce n'est qu'alors que vous devez intégrer l'expression dans votre spider. Le coût du débogage d'un XPath défectueux au cours d'un crawl de 5 minutes est bien plus élevé que celui d'une session de shell.

Écrire votre premier spider pour le web scraping avec Scrapy

Générez un squelette de spider pour votre domaine cible :

scrapy genspider books books.toscrape.com

Cela crée spiders/books.py. Remplacez son contenu par le spider ci-dessous. Il explore la page d'accueil du catalogue, extrait le titre, le prix et la note de chaque livre, puis génère un dictionnaire Python par livre. Nous le mettrons à niveau vers de véritables objets Items dans une section ultérieure.

import scrapy

class BooksSpider(scrapy.Spider):
    name = "books"
    allowed_domains = ["books.toscrape.com"]
    start_urls = ["https://books.toscrape.com/catalogue/page-1.html"]

    def parse(self, response):
        for card in response.css("article.product_pod"):
            yield {
                "title": card.css("h3 a::attr(title)").get(),
                "price": card.css("p.price_color::text").get(),
                "rating": card.css("p.star-rating::attr(class)").get(),
                "url": response.urljoin(card.css("h3 a::attr(href)").get()),
            }

Exécutez-le depuis la racine du projet :

scrapy crawl books -o books.jsonl

Vous devriez voir Scrapy enregistrer une requête vers la page 1, vingt éléments extraits, puis un arrêt propre. Ouvrez books.jsonl et vérifiez qu'il y a un objet JSON par ligne.

Quelques points à noter. start_urls est le point d'entrée, le moteur planifie automatiquement chaque URL. parse est le callback par défaut. response.urljoin résout une href par rapport à la page actuelle afin d'éviter les liens rompus. Le rating champ contient encore des éléments parasites tels que "star-rating Three", ce qui correspond exactement au type de nettoyage que les chargeurs d’éléments géreront ultérieurement.

Remarque concernant la production : l'exécution avec -o convient pour un test rapide, mais ne vous en fiez jamais dans une tâche planifiée. Configurez le FEEDS paramètre dans settings.py afin que la destination de sortie, le format et le comportement de remplacement soient gérés par le contrôle de version. Nous allons intégrer cela à un pipeline de base de données dans la section sur la persistance. Considérez le drapeau CLI comme un raccourci de développement, et non comme un artefact de déploiement.

CSS vs XPath : choisir des sélecteurs qui ne cassent pas

Les deux moteurs de sélection sont fournis avec Scrapy et fonctionnent tous deux sur le même arbre parsemé. Utilisez celui qui est le plus court et le plus clair pour la tâche à accomplir. En règle générale, le CSS est préférable pour les requêtes basées sur les classes et structurelles, tandis que l'XPath est préférable lorsque vous devez parcourir l'arbre par contenu textuel, par élément frère ou par ancêtre.

# CSS: short, idiomatic, fast to write
response.css("article.product_pod p.price_color::text").get()

# XPath equivalent
response.xpath("//article[@class='product_pod']//p[@class='price_color']/text()").get()

XPath prend tout son sens lorsque le CSS ne permet pas d'exprimer ce dont vous avez besoin :

# "Find the <td> that follows the <th> whose text is 'Stock'"
response.xpath("//th[normalize-space()='Stock']/following-sibling::td/text()").get()

# "Find all links whose visible text contains 'Next'"
response.xpath("//a[contains(., 'Next')]/@href").getall()

Quelques habitudes pour garantir la stabilité des sélecteurs : préférez les sélecteurs d'attributs aux sélecteurs de position fragiles (nth-child(3) finiront par ne plus fonctionner), normalisez les espaces lorsque vous comparez du texte (normalize-space()), et combinez .get() pour une correspondance unique avec .getall() pour une liste, n'indexez jamais le résultat de .getall() à l'aveuglette. Pour une comparaison plus approfondie permettant de déterminer quand chaque moteur est le bon choix, notre guide « Sélecteurs XPath vs CSS » est un bon complément de lecture.

Remarque de production : lorsqu’un sélecteur renvoie None en production mais fonctionne dans le shell, la page a probablement été rendue par JavaScript. Vérifiez avec view(response) avant de blâmer le sélecteur.

Éléments et chargeurs d'éléments : modèles de nettoyage réutilisables

Renvoyer de simples dictionnaires convient pour dix lignes de code. À grande échelle, vous avez besoin d’un schéma typé afin qu’une faute de frappe dans un nom de champ soit détectée rapidement au lieu de générer silencieusement des lignes erronées. Définissez un élément dans items.py:

import scrapy
from itemloaders.processors import MapCompose, TakeFirst, Join

def to_float(value):
    return float(value.replace("£", "").replace("$", "").strip())

def normalize_rating(value):
    # "star-rating Three" -> "Three"
    parts = value.split()
    return parts[1] if len(parts) > 1 else value

class ProductItem(scrapy.Item):
    title = scrapy.Field(input_processor=MapCompose(str.strip), output_processor=TakeFirst())
    price = scrapy.Field(input_processor=MapCompose(str.strip, to_float), output_processor=TakeFirst())
    rating = scrapy.Field(input_processor=MapCompose(normalize_rating), output_processor=TakeFirst())
    description = scrapy.Field(input_processor=MapCompose(str.strip), output_processor=Join(" "))

MapCompose transformateurs de chaînes, TakeFirst réduit une liste de correspondances à une seule valeur, et Join fusionne plusieurs paragraphes en un seul. Utilisez un chargeur dans le spider pour que celui-ci reste lisible :

from scrapy.loader import ItemLoader
from bookstore.items import ProductItem

def parse(self, response):
    for card in response.css("article.product_pod"):
        loader = ItemLoader(item=ProductItem(), selector=card)
        loader.add_css("title", "h3 a::attr(title)")
        loader.add_css("price", "p.price_color::text")
        loader.add_css("rating", "p.star-rating::attr(class)")
        yield loader.load_item()

L'avantage, c'est la réutilisation. Une fois to_float est implémenté dans items.py, chaque élément comportant un prix sur chaque spider peut l'appeler. La logique de nettoyage cesse d'être copiée-collée à travers les callbacks.

Suivi des liens : pagination manuelle vs CrawlSpider

Il existe deux méthodes courantes pour explorer plusieurs pages dans Scrapy. Choisissez en fonction du degré de prévisibilité de la structure des liens.

La pagination manuelle est le bon choix lorsqu’il n’y a qu’un seul lien « suivant » à suivre. Ajoutez ceci à la fin de parse:

next_page = response.css("li.next a::attr(href)").get()
if next_page:
    yield response.follow(next_page, callback=self.parse)

response.follow gère les URL relatives et réutilise le même callback, ce qui correspond exactement aux besoins de la pagination de type catalogue. L'exploration s'arrête naturellement lorsque le lien « suivant » disparaît sur la dernière page.

CrawlSpider est le bon choix lorsque vous souhaitez parcourir l'intégralité d'un site en faisant correspondre des modèles d'URL. Il utilise Rule et LinkExtractor pour découvrir et suivre les liens automatiquement :

from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor

class BooksCrawl(CrawlSpider):
    name = "books_crawl"
    allowed_domains = ["books.toscrape.com"]
    start_urls = ["https://books.toscrape.com/"]
    rules = (
        Rule(LinkExtractor(restrict_css=".pager a")),  # follow pagination
        Rule(LinkExtractor(restrict_css="h3 a"), callback="parse_book"),
    )

    def parse_book(self, response):
        yield {
            "title": response.css("h1::text").get(),
            "price": response.css("p.price_color::text").get(),
        }

La fonctionnalité intégrée de Scrapy RFPDupeFilter garantit qu’une même URL n’est pas mise en file d’attente deux fois, vous n’avez donc pas besoin de suivre vous-même les liens visités. Définissez DEPTH_LIMIT sur settings.py lorsque vous explorez un site profond et que vous souhaitez un arrêt forcé.

Remarque de production : pour les sites compatibles avec les sitemaps, SitemapSpider est encore plus simple. Il lit /sitemap.xml directement et vous permet de filtrer les modèles d’URL avec sitemap_rules.

Persistance des résultats : FEEDS et pipeline de base de données

Le web scraping avec Scrapy vous offre deux couches de persistance, et vous souhaitez généralement les deux. Le paramètre FEEDS gère gratuitement les exportations structurées, tandis qu’un pipeline prend en charge les destinations personnalisées telles qu’une base de données relationnelle.

Configurez les flux dans settings.py. Consultez la documentation sur les exportations de flux Scrapy pour connaître la syntaxe actuelle, mais une configuration moderne ressemble à peu près à ceci :

FEEDS = {
    "data/books.jsonl": {
        "format": "jsonlines",
        "encoding": "utf-8",
        "overwrite": True,
    },
    "data/books.csv.gz": {
        "format": "csv",
        "postprocessing": ["scrapy.extensions.postprocessing.GzipPlugin"],
    },
}

JSON Lines est le bon choix par défaut : streamable, compatible avec l'ajout de données et facile à charger dans Pandas ou un entrepôt de données. Le CSV avec gzip convient pour la transmission aux analystes. Les deux échouent pour les requêtes relationnelles, c'est là que les pipelines entrent en jeu.

Un pipeline SQLite qui s'exécute après un validateur :

# pipelines.py
import sqlite3
from itemadapter import ItemAdapter

class SqlitePipeline:
    def open_spider(self, spider):
        self.conn = sqlite3.connect("data/books.db")
        self.conn.execute(
            "CREATE TABLE IF NOT EXISTS products (title TEXT, price REAL, rating TEXT)"
        )

    def close_spider(self, spider):
        self.conn.commit()
        self.conn.close()

    def process_item(self, item, spider):
        a = ItemAdapter(item)
        self.conn.execute(
            "INSERT INTO products(title, price, rating) VALUES (?, ?, ?)",
            (a["title"], a["price"], a["rating"]),
        )
        return item

Enregistrez-le avec une priorité. Les nombres les plus bas s'exécutent en premier, donc un validateur à 100 se déclenche avant l'enregistreur de base de données à 200 :

ITEM_PIPELINES = {
    "bookstore.pipelines.PriceRangeValidator": 100,
    "bookstore.pipelines.SqlitePipeline": 200,
}

Désormais, les prix invalides sont rejetés avant même d'atteindre la base de données.

Renforcement de settings.py : AutoThrottle, Retries et Caching

Les paramètres par défaut fonctionnent en développement mais vous feront bannir en production. Les quelques paramètres ci-dessous sont les plus importants. Vérifiez les valeurs par défaut exactes par rapport à la version de Scrapy que vous avez installée.

# settings.py
ROBOTSTXT_OBEY = True            # respect the site's policy unless you have a contract
CONCURRENT_REQUESTS = 8          # global cap; lower for fragile sites
CONCURRENT_REQUESTS_PER_DOMAIN = 4
DOWNLOAD_DELAY = 0.5             # base delay; AutoThrottle adjusts dynamically

AUTOTHROTTLE_ENABLED = True
AUTOTHROTTLE_TARGET_CONCURRENCY = 2.0
AUTOTHROTTLE_START_DELAY = 1.0
AUTOTHROTTLE_MAX_DELAY = 30.0

RETRY_ENABLED = True
RETRY_TIMES = 5
RETRY_HTTP_CODES = [429, 500, 502, 503, 504, 408, 522, 524]

HTTPCACHE_ENABLED = True         # huge time-saver during development
HTTPCACHE_EXPIRATION_SECS = 3600
HTTPCACHE_IGNORE_HTTP_CODES = [429, 500, 502, 503, 504]

AutoThrottle est la fonctionnalité phare ici. Au lieu de deviner une DOWNLOAD_DELAY, vous lui donnez une concurrence cible et Scrapy ralentit lorsque la latence augmente. Cela suffit à prévenir la plupart des situations de DDoS accidentelles sur les sites lents.

HTTPCACHE_ENABLED est un paramètre qui facilite le développement : pendant que vous itérez sur les sélecteurs, des requêtes identiques sont renvoyées depuis le disque, ce qui vous évite de saturer la cible. Désactivez-le en production.

Pour lutter efficacement contre les bots, les paramètres seuls ne suffisent pas, et notre guide expliquant pourquoi les scrapers sont bloqués aborde les mécanismes plus complexes. Quoi qu'il en soit, la couche suivante concerne les middlewares.

Middlewares de téléchargement : en-têtes, proxys et API gérées

Lorsqu'un site commence à renvoyer 403, la solution se trouve presque toujours dans un middleware de téléchargement. Le squelette est simple :

# middlewares.py
import random

class RandomUserAgentMiddleware:
    UAS = [
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ...",
        "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_4) AppleWebKit/605.1.15 ...",
    ]
    def process_request(self, request, spider):
        request.headers["User-Agent"] = random.choice(self.UAS)

Enregistrez-le dans settings.py. Scrapy est livré avec une pile de middlewares par défaut préconfigurée (plus de dix activés d'emblée), et les numéros de priorité des middlewares sont généralement exprimés dans une plage d'entiers documentée. Les recommandations de la communauté placent les middlewares anti-bot personnalisés avant le RetryMiddleware, dont la priorité par défaut est 550, afin que les tentatives de reconnexion utilisent votre identité en rotation.

DOWNLOADER_MIDDLEWARES = {
    "bookstore.middlewares.RandomUserAgentMiddleware": 400,
    "scrapy.downloadermiddlewares.useragent.UserAgentMiddleware": None,  # disable default
}

Pour la rotation de proxy, définissez request.meta["proxy"] dans process_request. Il existe des plugins communautaires pour la rotation des proxys et la randomisation des user agents (ainsi que pour l'exploration distribuée, la mise en cache persistante et la surveillance), mais vérifiez l'état de maintenance actuel de chaque projet avant de vous en servir en production pour le web scraping avec Scrapy à une échelle significative.

Le compromis honnête : à un certain moment, gérer vos propres en-têtes, adresses IP résidentielles et résolution de CAPTCHA devient un projet parallèle. C'est là qu'une API Scraper gérée s'intègre parfaitement. Implémentez un middleware qui réécrit request.url pour pointer vers le point de terminaison de l'API et ajoute votre clé API en tant qu'en-tête, et le reste de votre spider reste inchangé.

Scrapy-Playwright : la porte de secours JavaScript

Scrapy n'exécute pas JavaScript de lui-même ; par conséquent, les sites construits avec Angular, React ou tout autre framework côté client renvoient le code HTML brut et non les données que vous voyez dans le navigateur. La solution la plus propre en 2026 pour le web scraping avec Scrapy sur des pages dynamiques est scrapy-playwright, qui remplace le téléchargeur par défaut par un véritable Chromium headless lorsque vous l'activez à chaque requête.

Installez-le et vérifiez la syntaxe actuelle d'enregistrement du gestionnaire par rapport au README de scrapy-playwright au moment de l'installation :

# settings.py
DOWNLOAD_HANDLERS = {
    "http": "scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler",
    "https": "scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler",
}
TWISTED_REACTOR = "twisted.internet.asyncioreactor.AsyncioSelectorReactor"

Activez les requêtes en définissant meta:

def start_requests(self):
    yield scrapy.Request(
        "https://example-spa.com/products",
        meta={
            "playwright": True,
            "playwright_page_methods": [
                ("wait_for_selector", "article.product"),
            ],
        },
    )

Ne marquez que les URL qui nécessitent réellement un navigateur. Chaque requête Playwright est nettement plus coûteuse qu’une simple récupération Scrapy, tant en termes de CPU que de latence ; un spider hybride (HTML pour les listes, Playwright pour les détails des produits) est donc généralement la solution la plus appropriée. Si vous souhaitez un guide plus approfondi ou une comparaison avec l’ancien backend Splash, notre tutoriel Scrapy-Playwright couvre ces modèles en détail.

Journalisation, contrats et déploiement

Le web scraping de niveau production avec Scrapy nécessite trois éléments que les tutoriels omettent généralement.

Journalisation. Configurez LOG_LEVEL = "INFO" lors settings.py pour les exécutions normales et "DEBUG" uniquement en cas de problème. Redirigez les journaux vers un fichier à l'aide de LOG_FILE ou les transmettez en continu vers un backend structuré.

Contrats de spider. Ajoutez des contrats docstring aux callbacks et exécutez-les scrapy check en CI. Un contrat type fixe l'URL, les champs attendus et le nombre minimum d'éléments, de sorte qu'une modification silencieuse du site interrompt la compilation plutôt que le jeu de données.

def parse(self, response):
    """
    @url https://books.toscrape.com/
    @returns items 20 20
    @scrapes title price rating
    """

Planification et déploiement. scrapyd exécute votre projet sous la forme d'un démon de longue durée que vous pouvez déployer via scrapyd-client. Pour les piles basées sur des conteneurs, créez une image Docker allégée avec votre projet et exécutez-la scrapy crawl selon un calendrier cron (ou un CronJob Kubernetes). Dans tous les cas, enregistrez les résultats sur un stockage durable, et non dans le système de fichiers du conteneur.

Pièges courants et comment les déboguer

  • Sélecteurs vides. Le sélecteur fonctionnait dans le shell, mais renvoie None dans le spider. Presque toujours rendu en JavaScript. Vérifiez avec view(response) et passez à scrapy-playwright pour cette URL.
  • 403 et 429 tempêtes. Votre empreinte est évidente. Ajoutez un middleware User-Agent aléatoire, réduisez CONCURRENT_REQUESTS_PER_DOMAIN, augmentez AUTOTHROTTLE_START_DELAY, et vérifiez RETRY_HTTP_CODES inclut 429.
  • Boucles de pagination infinies. Le sélecteur « next » correspond également à la dernière page. Ancrez-le sur une classe CSS qui disparaît à la fin, ou définissez DEPTH_LIMIT.
  • Éléments supprimés silencieusement. Une erreur s'est produite dans le pipeline DropItem et vous ne l'avez jamais remarqué. Bump LOG_LEVEL à DEBUG, recherchez Dropped:, et vérifiez vos contrôles de plage.
  • Des URL en double passent entre les mailles du filet. RFPDupeFilter correspondances par empreinte, donc des URL ne différant que par l'ordre des chaînes de requête peuvent s'infiltrer. Normalisez les URL avant de transmettre les requêtes.

Points clés

  • Le web scraping avec Scrapy est rentable lorsque vous avez besoin d’une planification, d’une déduplication, de tentatives de reconnexion, d’une limitation du débit et de pipelines intégrés prêts à l’emploi, et non lorsqu’un script de 20 lignes suffit.
  • Associez chaque symptôme à une étape du cycle de vie : les blocages se produisent dans les middlewares du téléchargeur, les éléments manquants se trouvent dans les pipelines, et les échecs de sélection indiquent généralement un problème de rendu JavaScript.
  • Chargeurs d'éléments avec MapCompose, TakeFirst, et Join permettent de réutiliser la logique de nettoyage entre les robots d'indexation au lieu de la copier-coller dans les callbacks.
  • Persistez avec FEEDS pour les formats portables et un pipeline personnalisé pour le stockage relationnel. Utilisez les deux, en définissant les priorités du pipeline de manière à ce que la validation précède l'écriture dans la base de données.
  • Traitez AutoThrottle, les codes de nouvelle tentative et une API de scraping gérée comme une défense à plusieurs niveaux contre les interdictions. N'utilisez scrapy-playwright uniquement lorsque le code HTML est véritablement vide.

FAQ

Vaut-il encore la peine d'apprendre Scrapy en 2026 par rapport aux bibliothèques asynchrones plus récentes ?

Oui, pour les explorations dépassant quelques centaines de pages. Les nouvelles piles asynchrones comme httpx plus selectolax sont idéales pour des scripts ponctuels, mais Scrapy intègre le planificateur, le filtre anti-duplicatas, le middleware de réessai, les signaux et les exportations de flux que vous devriez autrement écrire vous-même. Pour un crawler de production récurrent, cette conception « tout-en-un » reste avantageuse en termes de coûts de maintenance.

Scrapy peut-il extraire seul des pages rendues en JavaScript, ou ai-je besoin de Playwright ou de Splash ?

Pas tout seul. Scrapy récupère le HTML brut et n'exécute pas JavaScript, donc les applications monopages renvoient du balisage shell. La meilleure option actuelle est scrapy-playwright, qui remplace le téléchargeur par un véritable Chromium sans interface graphique pour chaque requête. scrapy-splash fonctionne toujours pour certaines équipes, mais Playwright offre une prise en charge plus large des navigateurs et une maintenance active.

Comment Scrapy se compare-t-il à Beautiful Soup et Selenium pour des projets de tailles différentes ?

Beautiful Soup est un analyseur syntaxique, pas un robot d'indexation, et s'associe bien à requests pour les petits scrapes statiques. Selenium pilote un navigateur complet et est idéal pour les flux interactifs avec état, comme les tableaux de bord nécessitant une connexion. Scrapy se situe entre les deux : un framework de crawling à haut débit pour des centaines à des millions de pages, avec un rendu par navigateur intégré via scrapy-playwright si nécessaire.

Comment déployer un spider Scrapy pour qu'il s'exécute selon un calendrier en production ?

Trois modèles courants. Exécutez scrapyd en tant que démon et déclenchez des tâches via son API HTTP. Créez une image Docker avec votre projet et planifiez scrapy crawl <name> via cron ou un CronJob Kubernetes. Ou utilisez une plateforme de scraping gérée qui héberge les araignées pour vous. Dans tous les cas, enregistrez les résultats sur un stockage durable comme S3 ou une base de données, jamais sur le système de fichiers d'un conteneur.

Comment éviter que mon spider Scrapy ne soit bloqué ou que son adresse IP ne soit bannie ?

Mettez en place plusieurs niveaux de défense. Activez AutoThrottleet randomisez User-Agent les en-têtes via un middleware de téléchargement, incluez 429 dans RETRY_HTTP_CODESet réduisez CONCURRENT_REQUESTS_PER_DOMAIN. Pour les sites plus résistants, acheminez les requêtes via des proxys résidentiels ou une API de scraping gérée qui gère la rotation et la résolution des CAPTCHA derrière un seul point de terminaison. Respectez robots.txt et les limites de débit lorsque c'est possible.

Conclusion

L'intérêt du web scraping avec Scrapy n'est pas d'écrire moins de code qu'avec requests plus BeautifulSoup. En général, vous en écrivez davantage le premier jour. L'intérêt réside dans le fait que le code que vous écrivez le premier jour fonctionne toujours au bout de quatre-vingt-dix jours, car le moteur, le planificateur, le filtre anti-duplication, la limitation de débit, la couche de réessai et le contrat de pipeline ne changent pas en arrière-plan. Vous vous offrez une base stable, puis vous adaptez les robots, les éléments et les intergiciels à chaque site cible.

Si vous ne devez retenir qu’une seule chose de ce guide, que ce soit le cycle de vie des requêtes et des réponses. Chaque bug Scrapy que vous rencontrerez se situe à une étape spécifique de cette boucle, et identifier cette étape, c’est déjà la moitié du chemin vers la solution. Les sélecteurs échouent dans le callback. Les éléments disparaissent dans le pipeline. Les bannissements se produisent dans le téléchargeur. La pagination boucle indéfiniment dans votre logique de callback. Associez le symptôme à l’étape, et la solution deviendra évidente.

Lorsque la pression anti-bot dépasse ce que vous pouvez intégrer middlewares.py, c'est le moment idéal pour décharger la couche de requêtes. Chez WebScrapingAPI, nous avons développé Scraper API précisément pour ce transfert : conservez vos araignées Scrapy, vos éléments et vos pipelines, et laissez un point de terminaison géré s'occuper des proxys, de la résolution des CAPTCHA et du rendu JavaScript. Votre araignée reste Scrapy. Les obstacles deviennent le problème de quelqu'un d'autre.

À propos de l'auteur
Mihai Maxim, Développeur Full Stack @ WebScrapingAPI
Mihai MaximDéveloppeur Full Stack

Mihai Maxim est développeur Full Stack chez WebScrapingAPI ; il participe à l'ensemble du produit et contribue à la création d'outils et de fonctionnalités fiables pour la plateforme.

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.