Screaming Frog est le crawler de référence pour la plupart des SEOs, mais vous avez probablement atteint ses limites : le plafond de 500 URLs sur la version gratuite, la RAM saturée sur les grands sites, ou le désir d’automatiser les crawls sans surveiller une interface graphique. Scrapy est le framework Python open source qui supprime ces limites.

Si vous pouvez exécuter npm install ou git clone, vous pouvez exécuter Scrapy. La courbe d’apprentissage est réelle mais gérable, surtout si vous vous familiarisez déjà avec les outils CLI via les workflows de codage agentique.

Pourquoi Scrapy ?

Avantages Clés

Screaming Frog fonctionne bien pour les audits rapides. Mais il a des limites :

Limitation Impact
Limite gratuite de 500 URLs Nécessite une licence à 259$/an pour les sites plus grands
Gourmand en mémoire Les grands crawls peuvent consommer 8Go+ de RAM
Dépendant du GUI Difficile à automatiser ou planifier
Personnalisation limitée Les options de configuration sont fixes

Scrapy résout ces problèmes :

Scrapy Ce que vous obtenez
Gratuit et open source Pas de limites d’URLs, pas de frais de licence
Empreinte mémoire réduite Files d’attente sur disque gardent la RAM sous contrôle
Natif CLI Scriptable, planifiable avec cron, prêt pour CI/CD
Personnalisation Python complète Extrayez ce dont vous avez besoin, filtrez comme vous voulez
Pause/Reprise Arrêtez et continuez les grands crawls à tout moment
Scrapy ne remplacera pas Screaming Frog pour tout. Les audits rapides restent plus rapides dans un GUI. Mais pour les crawls à grande échelle, l'automatisation et l'extraction personnalisée, il vaut la peine de l'avoir dans votre boîte à outils.

Installation

Configuration

Scrapy fonctionne sur Python. Utilisez un environnement virtuel pour garder les choses propres :

Debian/Ubuntu :

sudo apt install python3.11-venv
python3 -m venv venv
source venv/bin/activate
pip install scrapy

macOS :

python3 -m venv venv
source venv/bin/activate
pip install scrapy

Windows :

python -m venv venv
venv\Scripts\activate
pip install scrapy
Les Environnements Virtuels Comptent
Utilisez toujours un venv. L'installation globale cause des conflits de dépendances et compromet la reproductibilité.

Créer un Projet

Avec Scrapy installé :

scrapy startproject myproject
cd myproject
scrapy genspider sitename example.com

Cela crée :

myproject/
    scrapy.cfg
    myproject/
        __init__.py
        items.py
        middlewares.py
        pipelines.py
        settings.py
        spiders/
            __init__.py
            sitename.py

Le code du spider va dans spiders/sitename.py. La configuration est dans settings.py.

Paramètres pour un Crawling Poli

Critique

Configurez settings.py avant d’exécuter quoi que ce soit. Être bloqué gaspille plus de temps que crawler lentement.

# Crawling poli
CONCURRENT_REQUESTS_PER_DOMAIN = 5
DOWNLOAD_DELAY = 1
ROBOTSTXT_OBEY = True

# AutoThrottle - ajuste la vitesse selon la réponse du serveur
AUTOTHROTTLE_ENABLED = True
AUTOTHROTTLE_START_DELAY = 5
AUTOTHROTTLE_MAX_DELAY = 60
AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0
AUTOTHROTTLE_DEBUG = True

# Limites de sécurité
CLOSESPIDER_PAGECOUNT = 10000

# Sortie
FEED_EXPORT_ENCODING = "utf-8"
Avec AutoThrottle activé, 5 requêtes simultanées est un point de départ raisonnable. AutoThrottle réduira automatiquement si le serveur a des difficultés. Sans AutoThrottle, commencez plus bas à 1-3.

AutoThrottle

AutoThrottle surveille les temps de réponse du serveur et ajuste automatiquement la vitesse de crawl :

  • Réponses rapides → accélère
  • Réponses lentes → ralentit
  • Erreurs/timeouts → ralentit significativement

Contrairement aux délais fixes de Screaming Frog, il s’adapte aux conditions réelles du serveur.

Gestion des Codes de Statut

Par défaut, le HttpErrorMiddleware de Scrapy supprime silencieusement les réponses non-2xx. Cela signifie que les 404, 301, 500 sont éliminés avant d’atteindre votre callback. Votre crawl pourrait afficher 100% de codes de statut 200, non pas parce que le site est parfait, mais parce que les erreurs sont filtrées.

Ajoutez ceci à votre classe spider pour capturer tous les codes de statut :

handle_httpstatus_list = [200, 301, 302, 403, 404, 500, 502, 503]

Screaming Frog capture tous les codes de statut par défaut. Ce paramètre aligne Scrapy sur ce comportement.

Performance en Conditions Réelles

Benchmarks

Chiffres réels d’un crawl de test avec 5 requêtes simultanées et AutoThrottle activé :

Progression du Crawl Pages/Minute Notes
0-200 pages 14-22 Montée en charge
200-500 pages 10-12 Stabilisation
500-1 000 pages 7-10 AutoThrottle s’ajuste
1 000+ pages 5-7 État stable
Vitesse vs. Fiabilité
Ces vitesses semblent lentes. C'est le but. AutoThrottle priorise la santé du serveur plutôt que la vitesse brute. Être bloqué et redémarrer gaspille plus de temps qu'un crawl méthodique.

Comparaison des Fonctionnalités

Fonctionnalité Screaming Frog Scrapy
Coût Gratuit <500 URLs, ~259$/an Gratuit, open source
Taille max de crawl Limitée par la mémoire Files d’attente sur disque
Personnalisation Options de config limitées Code Python complet
Planification Manuel ou tiers CLI natif, planifiable avec cron
Pause/Reprise Oui Oui (avec JOBDIR)
Courbe d’apprentissage Faible (GUI) Moyenne (code)
Limitation de débit Délais fixes basiques AutoThrottle (adaptatif)
Rendu JavaScript Optionnel (Chrome) Optionnel (playwright/splash)
Codes de statut Tous par défaut Nécessite configuration
Filtrage de sous-domaines Cases à cocher GUI Code (regex flexible)
Formats d’export CSV, Excel, etc. JSON, CSV, XML, personnalisé
Intégration CI/CD Difficile Native

Filtrage d’URLs

Contrôle Précis

Screaming Frog utilise des cases à cocher. Scrapy utilise du code. Le compromis est la courbe d’apprentissage contre la précision.

Exclure les chemins internationaux :

import re
from urllib.parse import urlparse

class MySiteSpider(scrapy.Spider):
    name = "mysite"
    allowed_domains = ["example.com", "www.example.com"]
    start_urls = ["https://www.example.com/"]

    # Ignorer les chemins internationaux comme /uk/, /fr/, /de/
    EXCLUDED_PATTERNS = re.compile(
        r"/(in|au|th|es|hk|sg|ph|my|ca|cn|uk|kr|id|fr|vn|de|jp|nl|it|tw)/"
    )

    def filter_links(self, links):
        filtered = []
        for link in links:
            hostname = urlparse(link.url).hostname or ""
            if hostname not in ("example.com", "www.example.com"):
                continue
            if self.EXCLUDED_PATTERNS.search(link.url):
                continue
            filtered.append(link)
        return filtered

Vous pouvez filtrer par motifs d’URL, paramètres de requête, en-têtes de réponse, contenu de page ou toute combinaison.

Pause et Reprise

Essentiel

Pour les crawls de plus de 1 000 pages, activez pause/reprise avec JOBDIR :

scrapy crawl myspider -o output.json -s JOBDIR=crawl_state

Scrapy sauvegarde l’état dans crawl_state/. Appuyez sur Ctrl+C pour mettre en pause. Exécutez la même commande pour reprendre.

Utilisez toujours JOBDIR pour les crawls de production. Protège contre les problèmes réseau, les redémarrages système, ou simplement le besoin de s'arrêter pour la journée.

L’état comprend les URLs en attente, les URLs vues et la file d’attente des requêtes. C’est plus robuste que la fonctionnalité sauvegarder/charger de Screaming Frog car c’est basé sur des fichiers et survit aux redémarrages système.

Rendu JavaScript

Scrapy récupère uniquement le HTML brut. Il ne rend pas le JavaScript. C’est la même chose que ce que retourne curl.

Pour la plupart des crawls SEO, c’est suffisant :

  • Les balises meta, canonicals et h1 sont généralement dans le HTML initial
  • Les moteurs de recherche indexent principalement le contenu rendu côté serveur
  • La plupart des sites e-commerce et de contenu sont rendus côté serveur

Si votre site cible rend le contenu côté client, vous avez des options :

Package Notes
scrapy-playwright Utilise Chromium/Firefox/WebKit. Recommandé pour les sites JS modernes
scrapy-splash Léger, moteur de rendu basé sur Docker
scrapy-selenium Approche plus ancienne, fonctionne toujours

Le rendu JS est significativement plus lent et consomme plus de ressources. Ne l’ajoutez que si le site le nécessite.

Screaming Frog a un compromis similaire. Activer le rendu JavaScript utilise Chrome en arrière-plan et ralentit considérablement les crawls.

Gestion de la Mémoire

À ~1 300 pages avec extraction complète des champs :

  • Mémoire : ~265 Mo
  • CPU : ~4%

Utiliser JOBDIR déplace les files d’attente de requêtes sur le disque, gardant la mémoire basse. Pour les très grands crawls (100k+ URLs), ajoutez ces paramètres :

MEMUSAGE_LIMIT_MB = 1024
MEMUSAGE_WARNING_MB = 800
SCHEDULER_DISK_QUEUE = 'scrapy.squeues.PickleFifoDiskQueue'
SCHEDULER_MEMORY_QUEUE = 'scrapy.squeues.FifoMemoryQueue'

Cela limite l’utilisation de la mémoire et force les files d’attente sur disque pour le scheduler.

Données de Sortie

Personnalisable

Sortie basique du spider :

{
    "url": "https://www.example.com/page/",
    "title": "Titre de la Page Ici",
    "status": 200
}

Pour les crawls SEO, vous voudrez des champs similaires à ce qu’exporte Screaming Frog :

def parse_page(self, response):
    yield {
        "url": response.url,
        "status": response.status,
        "title": response.css("title::text").get(),
        "meta_description": response.css("meta[name='description']::attr(content)").get(),
        "meta_robots": response.css("meta[name='robots']::attr(content)").get(),
        "h1": response.css("h1::text").get(),
        "canonical": response.css("link[rel='canonical']::attr(href)").get(),
        "og_title": response.css("meta[property='og:title']::attr(content)").get(),
        "og_description": response.css("meta[property='og:description']::attr(content)").get(),
        "word_count": len(response.text.split()) if response.status == 200 else None,
        "content_type": response.headers.get("Content-Type", b"").decode("utf-8", errors="ignore"),
    }

Ajoutez ou supprimez des champs selon vos besoins. Les sélecteurs CSS fonctionnent pour n’importe quel élément de la page.

Formats d’export : JSON (-o output.json), JSON Lines (-o output.jsonl), CSV (-o output.csv), XML (-o output.xml).

JSON Lines est meilleur pour les grands crawls. Les fichiers sont valides ligne par ligne pendant le crawl, donc vous pouvez surveiller avec tail -f. Le JSON standard n’est pas valide tant que le crawl n’est pas terminé.

Screaming Frog → Scrapy

Guide de Traduction

Mapper les workflows SF vers Scrapy :

Action Screaming Frog Équivalent Scrapy
Démarrer un nouveau crawl scrapy crawl spidername
Définir le délai de crawl DOWNLOAD_DELAY dans settings
Limiter les threads simultanés CONCURRENT_REQUESTS_PER_DOMAIN
Respecter robots.txt ROBOTSTXT_OBEY = True
Exporter en CSV -o output.csv
Sauvegarder/Charger le crawl -s JOBDIR=crawl_state
Filtrer les sous-domaines Code dans le spider (regex)
Extraction personnalisée Sélecteurs CSS/XPath dans parse()

Changements de mentalité :

  1. La configuration est du code. Éditez settings.py au lieu de cocher des cases.
  2. L’extraction est explicite. Vous écrivez quelles données capturer.
  3. La planification est native. Ajoutez des commandes à cron ou CI/CD.
  4. Le débogage c’est les logs. Activez AUTOTHROTTLE_DEBUG pour voir ce qui se passe.

Workflow Complet

Avec les paramètres standard ci-dessus, vous pouvez avoir Scrapy installé et en train de crawler en moins de 15 minutes :

python3 -m venv venv
source venv/bin/activate  # venv\Scripts\activate sur Windows
pip install scrapy
scrapy startproject urlcrawler
cd urlcrawler
scrapy genspider mysite example.com
# Éditez settings.py avec la config de crawl poli
# Éditez spiders/mysite.py avec votre logique de parse
scrapy crawl mysite -o urls.jsonl -s JOBDIR=crawl_state

Scrapy Shell

Pendant que vous construisez des configurations personnalisées, utilisez Scrapy Shell pour tester vos sélecteurs et paramètres de manière interactive :

scrapy shell "https://example.com"

Cela ouvre une console Python interactive avec la réponse déjà chargée. Testez les sélecteurs CSS et XPath en temps réel avant de les ajouter à votre spider :

>>> response.css('title::text').get()
'Example Domain'
>>> response.xpath('//h1/text()').get()
'Example Domain'

Scrapy Shell réduit considérablement le temps d’itération. Validez la logique d’extraction sans exécuter des crawls complets.

Modèle de Spider Complet

Un spider prêt pour la production avec filtrage d’URLs, gestion des codes de statut et extraction complète des champs SEO :

import re
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from urllib.parse import urlparse


class SEOSpider(CrawlSpider):
    name = "seospider"
    allowed_domains = ["example.com"]
    start_urls = ["https://www.example.com"]

    # Capturer tous les codes de statut HTTP, pas seulement 2xx
    handle_httpstatus_list = [200, 301, 302, 403, 404, 500, 502, 503]

    # Motifs d'URLs à exclure
    EXCLUDED_PATTERNS = re.compile(
        r"/(in|au|th|es|hk|sg|ph|my|ca|cn|uk|kr|id|fr|vn|de|jp|nl|it|tw)/"
    )

    rules = (
        Rule(
            LinkExtractor(allow=()),
            callback="parse_page",
            follow=True,
            process_links="filter_links",
        ),
    )

    def filter_links(self, links):
        filtered = []
        for link in links:
            parsed = urlparse(link.url)
            hostname = parsed.hostname or ""

            if hostname not in ("example.com", "www.example.com"):
                continue

            if self.EXCLUDED_PATTERNS.search(link.url):
                continue

            filtered.append(link)
        return filtered

    def parse_page(self, response):
        yield {
            "url": response.url,
            "status": response.status,
            "title": response.css("title::text").get(),
            "meta_description": response.css("meta[name='description']::attr(content)").get(),
            "meta_robots": response.css("meta[name='robots']::attr(content)").get(),
            "h1": response.css("h1::text").get(),
            "canonical": response.css("link[rel='canonical']::attr(href)").get(),
            "og_title": response.css("meta[property='og:title']::attr(content)").get(),
            "og_description": response.css("meta[property='og:description']::attr(content)").get(),
            "word_count": len(response.text.split()) if response.status == 200 else None,
            "content_type": response.headers.get("Content-Type", b"").decode("utf-8", errors="ignore"),
        }

Remplacez example.com par votre domaine cible. Ajustez EXCLUDED_PATTERNS pour la structure d’URLs de votre site.

Quand Utiliser Lequel

Screaming Frog :

Scrapy :

  • Sites de plus de 10 000 URLs
  • Crawls automatisés et planifiés
  • Besoins d’extraction personnalisée
  • Intégration CI/CD
  • Contraintes de mémoire
  • Configurations versionnées

La Conclusion

Scrapy a une courbe de configuration plus raide que Screaming Frog, mais il supprime les limites pratiques qu’imposent les crawlers GUI. Pas de limites d’URLs, pas de frais de licence, utilisation mémoire réduite et automatisation native.

Commencez petit. Crawlez un site que vous connaissez. Utilisez des paramètres conservateurs. Comparez la sortie à Screaming Frog. Les données correspondront, mais vous aurez un outil qui passe à l’échelle.