Retour au blog
Guides
Raluca PenciucLast updated on Apr 27, 202621 min read

Tutoriel Scrapy Playwright : Scraper des sites lourds en JavaScript à grande échelle

En bref : Scrapy-Playwright vous permet de rendre des pages riches en JavaScript directement au sein des robots Scrapy en contrôlant de véritables navigateurs Chromium, Firefox ou WebKit via Playwright. Ce tutoriel vous guide à travers l'installation, la configuration, les interactions avec les pages, l'interception AJAX, les mesures anti-détection et la mise en place d'une structure de projet prête pour la production, afin que vous puissiez extraire des données de sites dynamiques sans quitter l'écosystème Scrapy.

Scrapy excelle dans l'exploration rapide de HTML statique, mais dès qu'un site cible charge du contenu via JavaScript, une requête Scrapy standard ne vous renvoie qu'une coquille vide. C'est exactement le problème que Scrapy Playwright résout. Il s'agit d'un gestionnaire de téléchargement Scrapy qui délègue le rendu à Playwright, la bibliothèque d'automatisation de navigateur de Microsoft, de sorte que chaque réponse reçue par votre spider contient le DOM entièrement rendu. Si vous envisagez d'intégrer Scrapy Playwright à vos propres projets mais que vous ne savez pas comment assembler tous les éléments, ce guide couvre chaque étape : de pip install à un spider prêt pour la production avec des éléments, des pipelines et des mécanismes anti-détection intégrés. Au fil de ce guide, vous apprendrez les stratégies d'attente, l'interception AJAX, la gestion du défilement infini, la configuration des proxys et les modèles de dépannage qui garantissent la stabilité des crawls de longue durée.

Qu'est-ce que Scrapy-Playwright et pourquoi l'utiliser ?

Scrapy-Playwright (le paquet PyPI scrapy-playwright) est un gestionnaire de téléchargement Scrapy qui remplace le backend HTTP par défaut par un navigateur complet alimenté par Playwright. Lorsque vous balisez une requête Scrapy avec "playwright": True dans son meta dictionnaire, le gestionnaire lance une page de navigateur, accède à l'URL, attend que JavaScript ait terminé, puis renvoie le code HTML rendu à votre parse callback comme une requête Scrapy normale Response.

Pourquoi est-ce important ? Une part croissante du Web affiche le contenu côté client : tableaux de bord React, vitrines Vue, pages protégées par des modaux de consentement et sites qui chargent les données produit via des appels API en arrière-plan. Scrapy standard ne récupère que le document HTML initial, qui contient souvent des balises de remplacement <div> et un bundle JavaScript, mais aucune des données dont vous avez réellement besoin. Avec le rendu JavaScript de Scrapy Playwright, vous obtenez le même résultat qu'un vrai navigateur afficherait, sans quitter le pipeline de requêtes/réponses familier de Scrapy.

Quand faut-il activer Playwright sur une requête ? Toutes les URL ne nécessitent pas un navigateur complet. Une règle empirique utile :

  • utilisez une requête Scrapy standard lorsque les données dont vous avez besoin sont présentes dans le code HTML brut ou disponibles via un point de terminaison API direct que vous connaissez déjà.
  • Utilisez une requête Playwright lorsque du contenu est injecté après le chargement de la page, lorsque vous devez cliquer ou faire défiler pour afficher des données, ou lorsque la page repose sur des cookies et des redirections JavaScript difficiles à reproduire avec du HTTP simple.

Il est facile (et recommandé) de combiner les deux modes dans un même spider. Vous ne payez la surcharge du navigateur que pour les requêtes qui en ont réellement besoin, ce qui permet de conserver une exploration rapide pour les pages qui n'en ont pas besoin.

Scrapy-Playwright vs Scrapy-Splash vs Scrapy-Selenium

Le choix entre les backends de rendu par navigateur pour Scrapy se résume à la charge de maintenance, à la fidélité du navigateur et aux outils existants de votre équipe. Voici une comparaison rapide :

Critères

Scrapy-Playwright

Scrapy-Splash

Scrapy-Selenium

Moteur de navigateur

Chromium, Firefox ou WebKit

Rendu personnalisé basé sur Qt

Chrome ou Firefox via WebDriver

Prise en charge asynchrone

Native (asyncio)

Nécessite un serveur Splash distinct

Synchronisation par défaut ; des wrappers asynchrones existent

Maintenance

Maintenu activement, communauté en pleine expansion

Le développement de Splash a ralenti

Stable mais dépend du protocole WebDriver

Fidélité JS

Navigateur moderne complet

Bonne, mais certains cas particuliers échouent

Navigateur moderne complet

Facilité de configuration

pip install + playwright install

Conteneur Docker requis

Gestion des binaires WebDriver

Interactions avec les pages

Riche (click, fill, evaluate)

Script Lua limité

API WebDriver complète

Si vous démarrez un nouveau projet aujourd'hui, Scrapy Playwright est généralement le choix le plus judicieux. Il offre une prise en charge asynchrone moderne, des méthodes d'interaction avec les pages de premier ordre et évite la charge opérationnelle liée à l'exécution d'un service de rendu séparé. Pour une analyse plus approfondie des avantages et inconvénients de Scrapy par rapport à Selenium, le guide comparatif Scrapy vs Selenium traite le sujet en détail.

Installation et configuration du projet

La mise en route d'un projet Scrapy Playwright nécessite quelques commandes dans le terminal. Voici la procédure étape par étape.

Prérequis : vous devez disposer de Python 3.8 ou une version ultérieure et pip. Un environnement virtuel est fortement recommandé pour isoler les dépendances.

# Create and activate a virtual environment
python -m venv venv
source venv/bin/activate  # Windows: venv\Scripts\activate

# Install Scrapy and scrapy-playwright
pip install scrapy scrapy-playwright

# Install browser binaries (Chromium is the default)
playwright install chromium

La playwright install chromium télécharge une version spécifique de Chromium gérée en interne par Playwright. Vous pouvez également installer firefox ou webkit si votre cas d'utilisation nécessite un autre moteur.

Ensuite, créez un nouveau projet Scrapy :

scrapy startproject myproject
cd myproject
scrapy genspider example example.com

Cela vous donne la structure de répertoires standard de Scrapy : settings.py, items.py, pipelines.py, middlewares.py, ainsi qu'un spiders/ dossier. La seule étape spécifique à Playwright qui reste consiste à mettre à jour settings.py, que nous aborderons ensuite.

Une chose à noter : scrapy-playwright dépend de l'API asynchrone de Playwright, qui nécessite à son tour le asyncioréacteur Twisted basé sur . Scrapy prend cela en charge, mais vous devez définir explicitement le réacteur avant que Scrapy n'essaie d'utiliser son réacteur par défaut. Oublier cette étape est l'erreur d'installation numéro un commise par les développeurs.

Configuration des paramètres de Scrapy pour Playwright

Ouvrez le fichier settings.py et ajoutez ce qui suit :

# settings.py

DOWNLOAD_HANDLERS = {
    "http": "scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler",
    "https": "scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler",
}

TWISTED_REACTOR = "twisted.internet.asyncioreactor.AsyncioSelectorReactor"

# Optional: choose browser type (chromium, firefox, webkit)
PLAYWRIGHT_BROWSER_TYPE = "chromium"

# Optional: global navigation timeout in milliseconds
PLAYWRIGHT_DEFAULT_NAVIGATION_TIMEOUT = 30000

La directive DOWNLOAD_HANDLERS dict indique à Scrapy d'acheminer toutes les requêtes HTTP et HTTPS via le gestionnaire Playwright. La TWISTED_REACTOR ligne fait passer la boucle d'événements de Scrapy en mode asyncio, ce dont Playwright a besoin.

PLAYWRIGHT_DEFAULT_NAVIGATION_TIMEOUT définit le temps maximum (en millisecondes) pendant lequel le navigateur attendra le chargement d'une page. La valeur par défaut est de 30 secondes, ce qui convient à la plupart des sites. Si vous explorez des pages particulièrement lentes, augmentez-la. Si vous souhaitez un échec rapide pour les URL rompues, réduisez-la.

Deux autres paramètres à connaître :

  • PLAYWRIGHT_LAUNCH_OPTIONS: un dictionnaire transmis directement à playwright.chromium.launch(). Utilisez-le pour basculer en mode headless, définir des chemins d'accès aux exécutables ou configurer un proxy global.
  • PLAYWRIGHT_MAX_PAGES_PER_CONTEXT: limite le nombre de pages partageant un même contexte de navigateur avant la création d'un nouveau contexte. Cela peut faciliter la gestion de la mémoire lors de crawls de grande envergure.

Une fois ces paramètres configurés, chaque requête Scrapy incluant "playwright": True dans son meta sera rendue par Playwright. Les requêtes sans ce drapeau passeront toujours par le téléchargeur standard de Scrapy, vous bénéficiez ainsi du meilleur des deux mondes.

Rendu de pages riches en JavaScript

Écrivons votre premier spider Scrapy Playwright. L'objectif : visiter une page qui charge son contenu via JavaScript et extraire des données du DOM entièrement rendu.

import scrapy

class QuotesSpider(scrapy.Spider):
    name = "quotes"
    start_urls = ["https://quotes.toscrape.com/js/"]

    def start_requests(self):
        for url in self.start_urls:
            yield scrapy.Request(
                url,
                meta={"playwright": True},
                callback=self.parse,
            )

    def parse(self, response):
        for quote in response.css("div.quote"):
            yield {
                "text": quote.css("span.text::text").get(),
                "author": quote.css("small.author::text").get(),
            }

La ligne clé est meta={"playwright": True}. Ce simple indicateur indique au gestionnaire de téléchargement de lancer une page dans le navigateur, de naviguer vers l'URL, d'attendre l' load et renvoie le HTML rendu sous forme de TextResponse. À l'intérieur de parse, vous utilisez les mêmes sélecteurs CSS (ou XPath) que ceux que vous utiliseriez avec n'importe quel spider Scrapy. Rien ne change du côté de l'analyse.

Exécutez l'araignée avec scrapy crawl quotes, et vous devriez voir les citations entièrement extraites même si la page utilise JavaScript pour les injecter dans le DOM. Si vous essayez la même URL avec une requête Scrapy standard (sans le drapeau Playwright), response.css("div.quote") cela renverrait une liste vide.

Ce modèle constitue la base de tout le reste de ce tutoriel Scrapy Playwright. Toutes les techniques suivantes s'appuient sur le même meta dictionnaire pour transmettre des instructions supplémentaires au navigateur.

Interactions avec la page : clics, défilement et soumission de formulaires

En pratique, le scraping ne se limite que rarement au simple chargement d’une page. Vous devez souvent cliquer sur des boutons, remplir des formulaires de recherche ou faire défiler la page pour déclencher le chargement différé de contenu. Les méthodes de page de Scrapy Playwright gèrent tout cela via la playwright_page_methods clé dans la requête meta.

Un PageMethod est un wrapper autour d'une action de page Playwright. Vous transmettez une liste de ces actions, et le gestionnaire les exécute dans l'ordre après la navigation initiale.

Cliquer sur un bouton :

from scrapy_playwright.page import PageMethod

yield scrapy.Request(
    url,
    meta={
        "playwright": True,
        "playwright_page_methods": [
            PageMethod("click", selector="button#load-more"),
            PageMethod("wait_for_selector", selector="div.new-content"),
        ],
    },
    callback=self.parse,
)

Remplir et soumettre un formulaire :

yield scrapy.Request(
    url,
    meta={
        "playwright": True,
        "playwright_page_methods": [
            PageMethod("fill", selector="input#search", value="python scrapy"),
            PageMethod("click", selector="button[type=submit]"),
            PageMethod("wait_for_selector", selector="div.results"),
        ],
    },
    callback=self.parse,
)

Faire défiler jusqu'au bas d'une page :

yield scrapy.Request(
    url,
    meta={
        "playwright": True,
        "playwright_page_methods": [
            PageMethod(
                "evaluate",
                "window.scrollTo(0, document.body.scrollHeight)",
            ),
            PageMethod("wait_for_timeout", 2000),
        ],
    },
    callback=self.parse,
)

Remarquez le schéma : vous enchaînez les PageMethod les appels pour simuler une véritable session utilisateur. Le gestionnaire les traite de manière séquentielle, l'ordre est donc important. Prévoyez toujours un temps d'attente après une action qui déclenche l'affichage de nouveau contenu (un clic qui lance un appel API, un défilement qui charge davantage d'éléments) afin de laisser à la page le temps de se mettre à jour avant que Scrapy ne capture le code HTML final.

Un conseil pratique : veillez à ce que votre playwright_page_methods liste aussi courte que possible. Chaque appel de méthode ajoute de la latence. Si vous pouvez obtenir le même résultat en moins d'étapes (par exemple, en naviguant directement vers une URL filtrée au lieu de remplir un formulaire), privilégiez l'approche la plus simple.

Stratégies d'attente pour le contenu dynamique

Le choix de la bonne stratégie d'attente est essentiel pour un scraping fiable du contenu dynamique avec Scrapy Playwright. Si vous attendez trop peu, vous obtiendrez des données incomplètes. Si vous attendez trop, votre crawl s'enlisera.

Voici les principales approches :

wait_for_selector est l'option la plus précise. Elle met l'exécution en pause jusqu'à ce qu'un sélecteur CSS spécifique apparaisse dans le DOM.

PageMethod("wait_for_selector", selector="div.product-list")

Utilisez-la lorsque vous savez exactement quel élément indique que les données ont été chargées. Elle est rapide car elle se déclenche dès que l'élément existe, plutôt que d'attendre une durée arbitraire.

wait_for_load_state attend un événement particulier du cycle de vie de la page :

  • "load": se déclenche lorsque le code HTML initial et toutes les ressources (images, feuilles de style) ont été chargés.
  • "domcontentloaded": se déclenche lorsque le code HTML est analysé, avant que les images ne soient chargées.
  • "networkidle": se déclenche lorsqu'il n'y a pas eu de connexion réseau depuis au moins 500 ms.
PageMethod("wait_for_load_state", "networkidle")

networkidle est tentant car il capture la plupart des appels AJAX, mais il peut s'avérer peu fiable sur les pages comportant des connexions WebSocket persistantes, des requêtes d'analyse ou des traceurs publicitaires qui maintiennent le réseau occupé. Il a également tendance à être plus lent que wait_for_selector.

wait_for_timeout est un délai d'attente fixe, spécifié en millisecondes.

PageMethod("wait_for_timeout", 3000)

C'est l'outil le plus rudimentaire. Utilisez-le uniquement en dernier recours, par exemple sur des pages où aucun sélecteur stable n'existe et où networkidle est instable. Les temps d'attente fixes font perdre du temps sur les pages rapides et peuvent ne pas être suffisamment longs sur les pages lentes.

Recommandation : privilégiez wait_for_selector dans la mesure du possible. Restez sur networkidle pour les pages où vous ne connaissez pas le sélecteur exact. Réservez wait_for_timeout pour les pages véritablement imprévisibles, et maintenez la valeur aussi basse que possible.

Gestion du défilement infini et de la pagination

De nombreux sites modernes utilisent des modèles de défilement infini ou une navigation paginée pour répartir le contenu sur plusieurs vues. La gestion de ces deux éléments dans Scrapy nécessite des stratégies légèrement différentes.

Le défilement infini fonctionne généralement en faisant défiler jusqu'au bas de la page, en attendant que de nouveaux éléments se chargent, et en répétant l'opération jusqu'à ce qu'il n'y ait plus d'éléments. Étant donné que playwright_page_methods s'exécute une seule fois avant de renvoyer la réponse, vous devez gérer la boucle de défilement à l'intérieur d'un page.evaluate appel ou en accédant directement à l'objet de page Playwright.

L'approche la plus propre consiste à utiliser la playwright_page clé meta pour récupérer la page Playwright brute et créer vous-même la boucle :

async def parse(self, response):
    page = response.meta["playwright_page"]
    previous_height = 0

    while True:
        await page.evaluate("window.scrollTo(0, document.body.scrollHeight)")
        await page.wait_for_timeout(1500)
        current_height = await page.evaluate("document.body.scrollHeight")
        if current_height == previous_height:
            break
        previous_height = current_height

    # Re-read the fully scrolled page content
    content = await page.content()
    await page.close()

    sel = scrapy.Selector(text=content)
    for item in sel.css("div.item"):
        yield {
            "title": item.css("h3::text").get(),
        }

Notez que nous fermons explicitement la page avec await page.close(). C'est essentiel pour la gestion de la mémoire ; sinon, les pages du navigateur s'accumulent et votre processus prend une place démesurée en mémoire.

La pagination (par clic sur « Suivant » ou via l'URL) est plus simple. Si le site utilise des paramètres de requête (?page=2), il suffit de générer de nouvelles requêtes Scrapy avec des URL incrémentées. S'il s'appuie sur un bouton « Suivant », utilisez un PageMethod clic :

def parse(self, response):
    # Extract data from current page
    for product in response.css("div.product"):
        yield {"name": product.css("h2::text").get()}

    # Follow next page if it exists
    next_button = response.css("a.next-page::attr(href)").get()
    if next_button:
        yield response.follow(
            next_button,
            meta={"playwright": True},
            callback=self.parse,
        )

Pour les sites qui utilisent des boutons « Charger plus » en JavaScript uniquement sans modifier l'URL, combinez le modèle de clic de la section sur les interactions avec la page avec un wait_for_selector pour vérifier que de nouveaux éléments sont apparus avant d'extraire les données.

Interception des requêtes AJAX

Parfois, la source de données la plus propre n'est pas le DOM rendu, mais l'appel API en arrière-plan que la page effectue pour le remplir. L'interception AJAX de Scrapy Playwright vous permet de capturer directement ces réponses, vous fournissant souvent du JSON structuré sans aucun analyseur HTML.

Pour intercepter les réponses, vous devez avoir accès à l'objet de page Playwright et à son response événement :

import json

class AjaxSpider(scrapy.Spider):
    name = "ajax_products"
    captured_data = []

    def start_requests(self):
        yield scrapy.Request(
            "https://example.com/products",
            meta={
                "playwright": True,
                "playwright_include_page": True,
            },
            callback=self.parse,
        )

    async def parse(self, response):
        page = response.meta["playwright_page"]

        async def handle_response(resp):
            if "/api/products" in resp.url:
                body = await resp.json()
                self.captured_data.extend(body.get("items", []))

        page.on("response", handle_response)

        # Trigger the AJAX call (e.g., scroll or click)
        await page.evaluate("window.scrollTo(0, document.body.scrollHeight)")
        await page.wait_for_timeout(3000)
        await page.close()

        for product in self.captured_data:
            yield product

L' page.on("response", ...) listener se déclenche pour chaque réponse réseau. Vous filtrez par modèle d'URL pour ne récupérer que les appels API qui vous intéressent. Le corps de la réponse est déjà analysé (.json() ou .text()), ce qui vous permet d'éviter complètement le parcours du DOM.

Cette technique est particulièrement efficace pour les applications monopages où le frontend effectue plusieurs requêtes API paginées au fur et à mesure que vous faites défiler la page. Au lieu d'analyser du code HTML complexe, vous obtenez des données propres et structurées directement à la source.

Exécution de JavaScript personnalisé et capture d'écran

Deux fonctionnalités légères mais utiles de Scrapy Playwright sont l'exécution de JavaScript personnalisé et la capture de captures d'écran. Elles servent des objectifs différents mais partagent le même mécanisme : l'accès direct à l'objet Page de Playwright.

L'exécution de JavaScript personnalisé avec page.evaluate vous permet d'extraire des données enfouies dans des variables JavaScript ou de manipuler l'état de la page avant que Scrapy ne lise le code HTML :

yield scrapy.Request(
    url,
    meta={
        "playwright": True,
        "playwright_page_methods": [
            PageMethod(
                "evaluate",
                "document.querySelectorAll('.popup-overlay')"
                ".forEach(el => el.remove())",
            ),
        ],
    },
    callback=self.parse,
)

Cela supprime les superpositions de fenêtres contextuelles avant que Scrapy n'analyse la page, ce qui est pratique pour les sites qui affichent des fenêtres modales lors de la première visite.

La capture d'écran avec Scrapy Playwright est utile pour déboguer les problèmes de rendu. Si votre spider extrait des données vides, une capture d'écran vous montre exactement ce que le navigateur a vu :

yield scrapy.Request(
    url,
    meta={
        "playwright": True,
        "playwright_page_methods": [
            PageMethod("screenshot", path="debug.png", full_page=True),
        ],
    },
    callback=self.parse,
)

L' full_page=True capture toute la zone défilable, et pas seulement la fenêtre d'affichage. Pendant le développement, vous pouvez activer les captures d'écran de manière conditionnelle (uniquement lorsqu'un callback de parsing ne trouve aucun élément, par exemple) pour éviter de saturer votre disque lors des crawls en production.

Interrompre les requêtes indésirables pour accélérer les crawls

Par défaut, chaque page de navigateur charge des images, des polices, du CSS, des scripts d'analyse et des traceurs publicitaires. Pour le scraping, la plupart de ces ressources sont un poids mort. Les bloquer peut réduire considérablement l'utilisation de la bande passante et accélérer le chargement des pages.

Scrapy-Playwright prend en charge l'interception des requêtes via le PLAYWRIGHT_ABORT_REQUEST . Vous définissez une fonction asynchrone qui inspecte chaque requête et renvoie True pour l'annuler :

# settings.py
PLAYWRIGHT_ABORT_REQUEST = "myproject.utils.should_abort"
# myproject/utils.py
from playwright.async_api import Request as PlaywrightRequest

async def should_abort(request: PlaywrightRequest) -> bool:
    blocked_types = {"image", "font", "stylesheet", "media"}
    if request.resource_type in blocked_types:
        return True
    blocked_domains = ["google-analytics.com", "doubleclick.net"]
    if any(domain in request.url for domain in blocked_domains):
        return True
    return False

Le simple fait de bloquer les images et les polices peut réduire considérablement le temps de chargement des pages, en particulier sur les sites de commerce électronique riches en contenu multimédia. Veillez simplement à ne pas bloquer les fichiers JavaScript chargés d'afficher le contenu dont vous avez besoin. Si vos données disparaissent après avoir activé le blocage des requêtes, remettez "script" à nouveau aux types autorisés et affinez plutôt votre filtre à des domaines spécifiques.

Utilisation de proxys avec Scrapy-Playwright

Lors d'un scraping à grande échelle, la rotation des proxys est essentielle pour éviter les interdictions d'IP. La configuration des proxys dans Scrapy Playwright fonctionne à deux niveaux : global et par requête.

Le proxy global s'applique à chaque requête Playwright. Définissez-le dans settings.py:

PLAYWRIGHT_LAUNCH_OPTIONS = {
    "proxy": {
        "server": "http://proxy-server:8080",
        "username": "user",
        "password": "pass",
    },
}

Cela transmet la configuration du proxy à l'appel de lancement du navigateur, de sorte que chaque page ouverte par cette instance de navigateur passe par ce proxy.

Le proxy par requête vous offre un contrôle plus fin. Utilisez playwright_context_kwargs dans la requête meta pour attribuer différents proxys à des requêtes individuelles :

yield scrapy.Request(
    url,
    meta={
        "playwright": True,
        "playwright_context_kwargs": {
            "proxy": {
                "server": "http://different-proxy:9090",
            },
        },
        "playwright_context": "proxy_context_1",
    },
    callback=self.parse,
)

Chaque playwright_context crée un contexte de navigateur distinct avec son propre proxy, ses propres cookies et son propre état de stockage. C'est ainsi que vous isolez les sessions lors de la rotation au sein d'un pool de proxys.

Pour les crawls en production, envisagez des services qui gèrent la rotation des proxys et la résolution des CAPTCHA derrière un point de terminaison unique, afin que votre logique de spider reste propre. Le point essentiel est que la prise en charge des proxys par Scrapy-Playwright est suffisamment flexible pour s'intégrer à n'importe quelle stratégie de rotation que vous choisissez.

Meilleures pratiques en matière d'anti-détection et de furtivité

Les proxys seuls ne suffisent pas. Les systèmes anti-bot modernes vérifient les empreintes de navigateur, les chaînes d'agent utilisateur et les modèles de comportement. Voici les couches anti-détection que vous devriez envisager pour vos araignées Scrapy Playwright.

Rotation des user-agents : définissez un user-agent réaliste et rotatif par contexte :

import random

USER_AGENTS = [
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ...",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 ...",
    # Add more real browser UA strings
]

yield scrapy.Request(
    url,
    meta={
        "playwright": True,
        "playwright_context_kwargs": {
            "user_agent": random.choice(USER_AGENTS),
        },
        "playwright_context": f"ctx_{random.randint(1, 100)}",
    },
    callback=self.parse,
)

Réduction de l'empreinte digitale : le Chromium de Playwright comporte des indicateurs WebDriver par défaut que les scripts anti-bot détectent. Vous pouvez réduire votre empreinte digitale en :

  • transmettre "args": ["--disable-blink-features=AutomationControlled"] en PLAYWRIGHT_LAUNCH_OPTIONS.
  • Utiliser page.evaluate pour supprimer la navigator.webdriver propriété.
  • Définir une taille de fenêtre d'affichage réaliste plutôt que les dimensions par défaut en mode sans interface graphique.

Délais aléatoires : l'ajout d'un décalage entre les requêtes empêche votre trafic de ressembler à un bot qui martèle le serveur à la vitesse de la machine. Utilisez le DOWNLOAD_DELAY en combinaison avec RANDOMIZE_DOWNLOAD_DELAY:

DOWNLOAD_DELAY = 2
RANDOMIZE_DOWNLOAD_DELAY = True

la configuration du contexte Stealth : combinez tous les éléments ci-dessus dans une configuration de contexte réutilisable. Pour un guide complet sur la manière d'éviter les blocages, la ressource « Conseils pour éviter d'être bloqué ou banni par IP lors du web scraping » couvre des stratégies supplémentaires qui s'appliquent au-delà de Scrapy-Playwright.

En résumé : considérez l'anti-détection comme un ensemble de couches plutôt que comme une solution unique. Les proxys gèrent la réputation IP. La rotation des user-agents gère les vérifications au niveau des en-têtes. La réduction des empreintes digitales gère les vérifications au niveau JavaScript. Les délais gèrent les vérifications comportementales. Vous avez besoin que tous ces éléments fonctionnent ensemble.

Contextes de navigateur, sessions et gestion des ressources

Un contexte de navigateur dans Playwright est une session de navigateur isolée disposant de ses propres cookies, de son propre stockage local et de son propre cache. Scrapy-Playwright utilise intensivement les contextes, et il est essentiel de les comprendre pour gérer les ressources lors de crawls de grande envergure.

Par défaut, chaque requête Scrapy Playwright qui ne spécifie pas de playwright_context nom partage un contexte par défaut. Cela signifie que les cookies persistent d'une requête à l'autre, ce qui convient pour les sites où vous devez rester connecté, mais pose problème si vous souhaitez des sessions propres pour chaque requête.

Les contextes nommés vous permettent d'isoler les sessions :

yield scrapy.Request(
    url,
    meta={
        "playwright": True,
        "playwright_context": "session_a",
    },
    callback=self.parse,
)

Toutes les requêtes marquées "session_a" partagent les cookies et l'état. Les requêtes marquées "session_b" bénéficient d’une session totalement distincte. Cela est utile pour les workflows de scraping en parallèle où vous devez simuler plusieurs utilisateurs indépendants.

PLAYWRIGHT_MAX_PAGES_PER_CONTEXT contrôle le nombre de pages pouvant être ouvertes simultanément au sein d'un même contexte. Lorsque la limite est atteinte, un nouveau contexte est créé. Le réglage de ce paramètre permet d'éviter la surcharge de mémoire :

PLAYWRIGHT_MAX_PAGES_PER_CONTEXT = 4

Conseils de gestion de la mémoire :

  • Fermez toujours les pages lorsque vous utilisez playwright_include_page. Si vous oubliez await page.close() dans votre parse , les pages s'accumulent et l'utilisation de la mémoire augmente linéairement avec le nombre de requêtes.
  • Utilisez CONCURRENT_REQUESTS pour limiter le parallélisme. Les navigateurs sont gourmands en ressources ; 8 à 16 requêtes Playwright simultanées constituent un point de départ raisonnable sur une machine dotée de 8 Go de RAM.
  • Surveillez la mémoire RSS de votre spider pendant les exécutions de test. Si elle augmente régulièrement, vérifiez s'il y a des pages non fermées ou une création excessive de contextes.

Pour les workflows de scraping avec navigateur headless de manière plus générale, le guide sur l'exécution d'un navigateur headless avec Python aborde des modèles de ressources qui complètent ce que nous traitons ici.

Dépannage et gestion des erreurs

Même les araignées Scrapy Playwright bien configurées peuvent échouer à grande échelle. Voici les problèmes les plus courants et les solutions concrètes.

TimeoutError : c'est l'erreur que vous rencontrerez le plus souvent. Elle signifie que le navigateur n'a pas pu terminer la navigation ou une attente dans le délai imparti.

  • Augmentez PLAYWRIGHT_DEFAULT_NAVIGATION_TIMEOUT pour les sites lents.
  • Passez de networkidle à wait_for_selector pour éviter de rester bloqué sur des connexions persistantes.
  • Vérifiez si le site cible vous bloque (une capture d'écran de la page de délai d'expiration révèle souvent un CAPTCHA ou une page de blocage).

Déconnexions du navigateur : si le processus du navigateur plante en cours d'exploration, vous verrez BrowserError ou Connection closed des exceptions.

  • Réduisez CONCURRENT_REQUESTS. Un trop grand nombre de pages parallèles peut épuiser la mémoire système et provoquer le plantage du navigateur.
  • Réglez PLAYWRIGHT_MAX_PAGES_PER_CONTEXT une valeur inférieure.
  • Ajouter "args": ["--disable-dev-shm-usage"] lors de PLAYWRIGHT_LAUNCH_OPTIONS lors de l'exécution dans Docker, où /dev/shm est souvent trop petite.

Fuites de mémoire : l'utilisation de la mémoire par votre spider augmente progressivement lors de longs crawls.

  • Vérifiez que vous fermez toutes les pages obtenues via playwright_include_page. Chaque page non fermée conserve un DOM complet en mémoire.
  • Limitez PLAYWRIGHT_MAX_PAGES_PER_CONTEXT et redémarrez périodiquement les contextes.
  • Utilisez CLOSESPIDER_PAGECOUNT ou une extension personnalisée pour redémarrer le spider après un certain seuil.

Modèles de gestion des erreurs : utilisez les errback pour gérer les échecs de manière élégante au lieu de laisser ceux-ci provoquer le plantage du spider :

yield scrapy.Request(
    url,
    meta={"playwright": True, "playwright_include_page": True},
    callback=self.parse,
    errback=self.handle_error,
)

async def handle_error(self, failure):
    page = failure.request.meta.get("playwright_page")
    if page:
        await page.close()
    self.logger.error(f"Request failed: {failure.request.url}")

Détail essentiel : si vous avez demandé playwright_include_page, vous devez fermer la page à la fois dans le callback et dans l'errback. Sinon, une requête ayant échoué provoque une fuite d'objet de page. Combinez les errbacks avec le paramètre intégré de Scrapy RETRY_TIMES pour réessayer automatiquement les échecs temporaires avant d'abandonner.

Débogage à l'aide des traces : Playwright prend en charge l'enregistrement des traces, qui capture une chronologie complète des requêtes réseau, des instantanés DOM et des actions. Activez-le via PLAYWRIGHT_LAUNCH_OPTIONS pendant le développement pour rejouer exactement ce que le navigateur a fait sur une page problématique.

Construire un spider prêt pour la production

Les tutoriels s'arrêtent souvent après vous avoir montré comment extraire des données. En production, vous avez besoin d'une structure de projet complète avec des éléments, des pipelines, des middlewares et des paramètres bien réglés. Voici comment tout relier pour un projet Scrapy Playwright.

Définissez vos éléments :

# items.py
import scrapy

class ProductItem(scrapy.Item):
    name = scrapy.Field()
    price = scrapy.Field()
    url = scrapy.Field()

En utilisant Item des classes (ou dataclass items dans les versions plus récentes de Scrapy) vous permet de valider le schéma et rend votre code de pipeline plus propre que si vous passiez des dictionnaires bruts.

Écrivez un pipeline d'éléments pour la validation et le stockage :

# pipelines.py
class ValidateProductPipeline:
    def process_item(self, item, spider):
        if not item.get("name"):
            raise scrapy.exceptions.DropItem("Missing name")
        item["price"] = float(item["price"].replace("$", "").strip())
        return item

class JsonWriterPipeline:
    def open_spider(self, spider):
        import json
        self.file = open("products.jsonl", "w")

    def close_spider(self, spider):
        self.file.close()

    def process_item(self, item, spider):
        import json
        self.file.write(json.dumps(dict(item)) + "\n")
        return item

Liste de contrôle des paramètres de production :

# settings.py (additions for production)
ITEM_PIPELINES = {
    "myproject.pipelines.ValidateProductPipeline": 100,
    "myproject.pipelines.JsonWriterPipeline": 200,
}

CONCURRENT_REQUESTS = 8
DOWNLOAD_DELAY = 1.5
RANDOMIZE_DOWNLOAD_DELAY = True
RETRY_TIMES = 3
LOG_LEVEL = "INFO"

PLAYWRIGHT_MAX_PAGES_PER_CONTEXT = 4
PLAYWRIGHT_DEFAULT_NAVIGATION_TIMEOUT = 30000

Le modèle prêt pour la production est le suivant : les éléments structurés transitent par des pipelines de validation, les paramètres limitent la concurrence à un niveau que votre machine et le site cible peuvent gérer, et la logique de réessai ainsi que les errbacks interceptent les échecs transitoires. Le collecteur de statistiques intégré de Scrapy vous fournit des métriques par crawl (éléments extraits, erreurs, réessais) sans code supplémentaire.

Pour les équipes qui souhaitent acquérir les bases du web scraping avec Scrapy avant d'intégrer Playwright, le guide sur le web scraping avec Scrapy fournit une base solide.

Points clés

  • Activez Playwright de manière sélective. Ne marquez les requêtes avec "playwright": True lorsque la page nécessite véritablement un rendu JavaScript ; combinez des requêtes Scrapy standard pour tout le reste afin de maintenir la rapidité des explorations.
  • Utilisez wait_for_selector plutôt que networkidle ou les pauses forcées. L'attente basée sur des sélecteurs est plus rapide et plus fiable pour la plupart des scénarios de contenu dynamique.
  • Interceptez les appels AJAX lorsque cela est possible. La capture des réponses API en arrière-plan vous fournit du JSON propre et évite les sélecteurs DOM instables.
  • Superposez les mesures anti-détection : les proxys, la rotation des user-agents, la réduction des empreintes digitales et les délais aléatoires doivent fonctionner ensemble, et non se remplacer mutuellement.
  • Fermez toutes les pages que vous ouvrez. Les fuites de mémoire provenant de pages Playwright non fermées sont la cause la plus courante d'instabilité dans les crawls Scrapy Playwright de longue durée.

FAQ

Scrapy-Playwright prend-il en charge Firefox et WebKit, ou uniquement Chromium ?

Oui, les trois moteurs sont pris en charge. Définissez PLAYWRIGHT_BROWSER_TYPE sur "firefox" ou "webkit" dans vos paramètres Scrapy et exécutez playwright install firefox (ou webkit) pour télécharger le binaire du navigateur correspondant. Chromium est le navigateur par défaut et celui qui a été le plus testé, mais Firefox peut s’avérer utile pour les sites qui identifient spécifiquement Chromium.

Comment résoudre les exceptions TimeoutError dans Scrapy-Playwright ?

Commencez par augmenter PLAYWRIGHT_DEFAULT_NAVIGATION_TIMEOUT au-delà de la valeur par défaut de 30 secondes. Si le délai d'attente persiste, remplacez votre stratégie d'attente networkidle à wait_for_selector en ciblant un élément spécifique. Faites également une capture d'écran de la page qui plante pour vérifier si le site affiche un CAPTCHA ou une page de blocage plutôt que le contenu attendu.

Puis-je exécuter Scrapy-Playwright en mode headful (navigateur visible) pour le débogage ?

Oui. Ajoutez "headless": False à PLAYWRIGHT_LAUNCH_OPTIONS dans settings.py. La fenêtre du navigateur s'ouvrira de manière visible, vous permettant d'observer chaque navigation et interaction en temps réel. Cela est très utile pour le débogage des séquences de méthodes de page. N'oubliez pas de repasser en mode sans interface graphique avant d'exécuter des crawls en production.

Quelle quantité de mémoire Scrapy-Playwright utilise-t-il, et comment puis-je réduire cette consommation ?

Chaque page Chromium consomme environ 50 à 150 Mo de RAM, selon la complexité de la page. Pour réduire la consommation de mémoire, diminuez CONCURRENT_REQUESTS, définissez PLAYWRIGHT_MAX_PAGES_PER_CONTEXT sur une petite valeur (3 à 5), supprimez les types de ressources inutiles (images, polices, feuilles de style) et fermez toujours les pages explicitement dans vos méthodes callback et errback.

Quelle est la différence entre Scrapy-Playwright, Scrapy-Splash et Scrapy-Selenium ?

Scrapy-Playwright utilise l'API asynchrone moderne de Playwright avec Chromium, Firefox ou WebKit. Scrapy-Splash s'appuie sur un service de rendu distinct basé sur Docker, avec une interactivité limitée. Scrapy-Selenium encapsule l'ancien protocole WebDriver. Pour les nouveaux projets, Scrapy-Playwright offre généralement la meilleure combinaison entre fidélité du navigateur, performances asynchrones et maintenance active.

Conclusion

Scrapy Playwright comble le fossé entre le puissant moteur de crawling de Scrapy et la réalité du Web actuel, piloté par JavaScript. En ajoutant un simple indicateur méta à vos requêtes, vous bénéficiez d'un rendu complet du navigateur sans renoncer aux pipelines, au middleware et au modèle de concurrence de Scrapy. Ce tutoriel a couvert l'ensemble du spectre : de l'installation et la configuration initiales aux interactions avec les pages, en passant par l'interception AJAX, l'anti-détection et la sécurisation en production.

Les techniques présentées ici devraient permettre de gérer la grande majorité des scénarios de scraping dynamique. Pour les projets où la gestion de l'infrastructure de navigation, la rotation des proxys et l'anti-détection à grande échelle deviennent le goulot d'étranglement plutôt que la logique de scraping elle-même, notre API Scraper gère ces couches derrière un point de terminaison unique afin que vous puissiez vous concentrer sur les données plutôt que sur l'infrastructure.

Quelle que soit l'approche que vous choisissez, le principe de base reste le même : n'utilisez le rendu du navigateur que lorsque cela est nécessaire, veillez à ce que vos robots soient bien structurés et fermez chaque page que vous ouvrez.

À propos de l'auteur
Raluca Penciuc, Développeur full-stack @ WebScrapingAPI
Raluca PenciucDéveloppeur full-stack

Raluca Penciuc est développeuse Full Stack chez WebScrapingAPI ; elle conçoit des robots de collecte de données, améliore les techniques de contournement et recherche des moyens fiables de réduire le risque de détection sur les sites cibles.

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.