Playwright : pilote un vrai navigateur avec Python et automatise le web

Playwright : pilote un vrai navigateur avec Python et automatise le web

De quoi avez-vous besoin

Version de Python

3.x

Packages

  • {"nom":"playwright","version":"1.62.0"}

Difficulté

Intermédiaire

Tu as essayé de scraper un site, et tu t'es heurté au mur : le contenu n'apparaît qu'après un clic, un formulaire se remplit en JavaScript, ou le serveur renvoie une page de connexion parce que ta requête ne ressemble pas à un vrai navigateur. Les outils classiques, comme requests, récupèrent le HTML brut sans exécuter le JavaScript. Il te faut un vrai navigateur, piloté automatiquement.

C'est exactement ce que fait Playwright, la bibliothèque d'automatisation de navigateur créée par Microsoft. Elle pilote Chromium, Firefox et WebKit avec une seule API, exécute le JavaScript comme un utilisateur réel, et embarque une synchronisation automatique qui élimine la plupart des sleep() approximatifs des scripts de scraping. Elle sert aussi bien au scraping qu'aux tests d'interface de bout en bout.

Dans ce tutoriel, tu vas lancer un navigateur depuis Python, naviguer, cliquer, remplir des formulaires, attendre le chargement, extraire des données d'une page réelle, prendre des captures d'écran, et écrire un premier test automatisé. À la fin, tu auras une base solide pour scraper proprement ou tester tes applications.

Prérequis et installation

Playwright s'installe en deux temps : le paquet Python, puis les navigateurs. La seconde commande télécharge les binaires de Chromium, Firefox et WebKit, qui ne sont pas inclus dans le paquet :

bash
pip install playwright
playwright install

Si tu ne veux qu'un seul navigateur pour gagner de la place, précise-le. Pour ce tutoriel, Chromium suffit :

bash
playwright install chromium

Il existe deux API : une synchrone (sync_playwright) et une asynchrone (async_playwright). On utilisera la synchrone, plus simple à lire, parfaite pour des scripts ; l'asynchrone brille dans les applications web à haute concurrence.

Pourquoi Playwright plutôt que Selenium

Si tu viens du scraping, tu connais peut-être Selenium, l'ancêtre de l'automatisation. Playwright en reprend l'idée mais corrige ses défauts historiques. Les différences clés :

  • Synchronisation automatique : Playwright attend qu'un élément soit prêt avant d'agir, là où Selenium impose des attentes explicites ou des sleep() fragiles.
  • Un seul outil pour trois navigateurs : Chromium, Firefox et WebKit, sans driver externe à télécharger séparément.
  • Des sélecteurs modernes : tu cibles un élément par son rôle, son texte ou son libellé, pas seulement par des XPath obscurs.
  • Des contextes isolés : chaque contexte a ses propres cookies et son propre stockage, idéal pour tester plusieurs sessions ou gérer le multi-comptes.

Le résultat est un code plus lisible, plus stable dans le temps, et moins sujet aux erreurs de timing qui empoisonnent les scripts de scraping.

Lancer le navigateur et ouvrir une page

Le point d'entrée est sync_playwright, utilisé comme gestionnaire de contexte. On lance un navigateur, on ouvre une page, on navigue, puis on referme proprement :

python
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    # headless=True : le navigateur tourne sans fenetre visible
    browser = p.chromium.launch(headless=True)
    page = browser.new_page()
    page.goto("https://example.com")
    print("Titre de la page :", page.title())
    print("URL finale :", page.url)
    browser.close()

Le bloc with garantit la fermeture du navigateur même si une erreur survient. En mode headless (par défaut dans cet exemple), rien ne s'affiche à l'écran, ce qui est parfait pour un serveur ou un script automatisé. Pour voir le navigateur agir, passe headless=False : indispensable pour déboguer.

Naviguer, cliquer et remplir des champs

La force de Playwright, c'est l'interaction. Les méthodes click, fill et press reproduisent le comportement d'un utilisateur, et la bibliothèque attend que l'élément soit visible et stable avant d'agir. Voici un enchaînement classique :

python
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=True)
    page = browser.new_page()
    page.goto("https://example.com")

    # Cliquer sur un lien identifie par son texte accessible
    page.get_by_role("link", name="More information").click()

    # Remplir un champ de formulaire et appuyer sur Entree
    page.get_by_label("Search").fill("playwright")
    page.get_by_label("Search").press("Enter")

    print("On est maintenant sur :", page.url)
    browser.close()

Les méthodes get_by_role et get_by_label sont la façon moderne de cibler un élément : elles s'appuient sur la sémantique accessible de la page plutôt que sur des classes CSS fragiles. C'est plus lisible et plus robuste aux refontes du site.

Les sélecteurs et locators

Un locator est une référence à un élément, résolue au moment où tu interagis. Playwright propose plusieurs familles, par ordre de préférence :

  • get_by_role : cible par rôle accessible (bouton, lien, titre, case à cocher).
  • get_by_label : cible un champ par son libellé de formulaire.
  • get_by_text : cible par contenu textuel, utile pour un bouton ou un paragraphe précis.
  • get_by_placeholder : cible par texte indicatif d'un champ.
  • locator('css') ou locator('xpath') : sélecteurs bruts, à réserver aux cas sans alternative sémantique.

Un locator peut être affiné ou chaîné, et surtout il se résout paresseusement : si l'élément n'existe pas encore, Playwright attend. Voici quelques exemples :

python
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    page = p.chromium.launch(headless=True).new_page()
    page.goto("https://example.com")

    # Par role + nom accessible
    titre = page.get_by_role("heading", name="Example Domain")
    print("Le titre est visible :", titre.is_visible())

    # Par texte exact
    lien = page.get_by_text("More information...")

    # Selecteur CSS brut (dernier recours)
    premier_paragraphe = page.locator("p").first
    print(premier_paragraphe.inner_text())
    page.context.browser.close()

La synchronisation automatique

C'est LA fonctionnalité qui change tout. Avant de cliquer, Playwright attend que l'élément soit visible, activé et stable. Après un clic, il attend que la navigation ou les événements associés se terminent. Fini les time.sleep(3) au petit bonheur :

python
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    page = p.chromium.launch(headless=True).new_page()
    page.goto("https://example.com")

    # Le clic attend que le lien soit actionnable, puis attend la navigation
    page.get_by_role("link", name="More information").click()

    # Attente explicite : attendre qu'un element remplisse une condition
    page.locator("body").wait_for(state="visible", timeout=10_000)

    print("Navigation terminee :", page.url)
    page.context.browser.close()

Les attentes explicites restent utiles quand l'état que tu attends n'est pas lié à une action précise : attendre un élément visible, détaché du DOM, ou une URL donnée. Le timeout (ici 10 secondes) évite qu'un script ne reste bloqué éternellement sur un site défaillant.

Extraire des données (scraping)

Passons au scraping concret. Le site d'entraînement quotes.toscrape.com affiche des citations avec auteur et tags, sans piège anti-bot : parfait pour apprendre. On récupère un locator pour chaque citation, puis on lit le texte de ses enfants :

python
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    page = p.chromium.launch(headless=True).new_page()
    page.goto("https://quotes.toscrape.com")

    citations = page.locator(".quote")
    print(f"{citations.count()} citations trouvees")

    for quote in citations.all():
        texte = quote.locator(".text").inner_text()
        auteur = quote.locator(".author").inner_text()
        print(f"{auteur} : {texte}")

    page.context.browser.close()

La méthode inner_text récupère le texte visible, contrairement à text_content qui inclut le texte masqué. Pour des données structurées, tu peux aussi récupérer un attribut avec get_attribute, ou extraire toutes les lignes d'un tableau en itérant sur ses cellules.

Prendre des captures d'écran

Pour documenter, déboguer ou archiver, la capture d'écran est une arme précieuse. Playwright capture la zone visible, un élément précis, ou la page entière en une ligne :

python
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    page = p.chromium.launch(headless=True).new_page()
    page.goto("https://example.com")

    # Page entiere, y compris les parties hors ecran
    page.screenshot(path="page_complete.png", full_page=True)

    # Un element precis
    page.get_by_role("heading", name="Example Domain").screenshot(path="titre.png")

    page.context.browser.close()

Le format est inféré de l'extension du fichier (png, jpeg, webp). Dans un test, la capture automatique au moment d'une erreur est un réflexe qui te fera gagner des heures de diagnostic : tu vois exactement l'état de la page au moment du crash.

Remplir un formulaire de bout en bout

Assemblons les briques pour remplir un vrai formulaire : cibler les champs par libellé, sélectionner une option dans un menu, cocher une case, puis soumettre. La méthode select_option gère les menus déroulants :

python
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    page = p.chromium.launch(headless=True).new_page()
    page.goto("https://example.com")

    # Champs texte
    page.get_by_label("Email").fill("contact@exemple.fr")
    page.get_by_label("Mot de passe").fill("secret123")

    # Menu deroulant : on selectionne par valeur
    page.get_by_label("Pays").select_option("fr")

    # Case a cocher
    page.get_by_role("checkbox", name="Accepter les conditions").check()

    # Soumission
    page.get_by_role("button", name="S'inscrire").click()

    page.context.browser.close()

Chaque méthode attend que le champ soit prêt, ce qui rend le script robuste même sur des formulaires dynamiques. Pour les champs de date, de fichier ou les contenus riches, Playwright propose des méthodes dédiées (set_input_files, fill sur les champs date, etc.).

Scraper plusieurs pages (pagination)

Un vrai scraping dépasse rarement la première page. Ajoutons la pagination : on boucle sur les pages en cliquant sur le bouton Suivant tant qu'il existe. C'est le même schéma que ci-dessus, enveloppé dans une boucle :

python
from playwright.sync_api import sync_playwright

resultats = []

with sync_playwright() as p:
    page = p.chromium.launch(headless=True).new_page()
    page.goto("https://quotes.toscrape.com")

    while True:
        for quote in page.locator(".quote").all():
            resultats.append({
                "auteur": quote.locator(".author").inner_text(),
                "texte": quote.locator(".text").inner_text(),
            })

        suivant = page.locator("li.next a")
        # Si le bouton Suivant n'existe plus, on a fini
        if suivant.count() == 0:
            break
        suivant.click()

    print(f"{len(resultats)} citations collectees")
    page.context.browser.close()

Le test suivant.count() == 0 est une façon élégante de détecter la fin de la pagination sans gérer d'exception. Pour des volumes importants, pense à espacer tes requêtes et à respecter les conditions d'utilisation du site cible.

Exécuter du JavaScript dans la page

Parfois, la donnée que tu cherches n'est pas dans le HTML, mais dans une variable JavaScript de la page. La méthode evaluate exécute une expression dans le contexte du navigateur et renvoie le résultat en Python :

python
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    page = p.chromium.launch(headless=True).new_page()
    page.goto("https://example.com")

    # Lire une propriete du document, ou calculer une valeur
    hauteur = page.evaluate("document.body.scrollHeight")
    print("Hauteur du document :", hauteur)

    # Faire defiler la page jusqu'en bas
    page.evaluate("window.scrollTo(0, document.body.scrollHeight)")

    page.context.browser.close()

evaluate est aussi pratique pour déclencher un défilement infini, cocher une option qui n'est pas exposée en HTML, ou lire une valeur stockée dans window. C'est la passerelle vers tout ce que seul le JavaScript de la page connaît.

Intercepter et modifier les requêtes réseau

Les données d'une application web moderne transitent souvent par des appels à une API interne. Plutôt que de parser le DOM, tu peux intercepter ces réponses réseau et lire le JSON directement. La méthode route te laisse observer ou réécrire les requêtes :

python
from playwright.sync_api import sync_playwright

reponses = []

with sync_playwright() as p:
    page = p.chromium.launch(headless=True).new_page()

    # Intercepter les reponses qui contiennent du JSON
    def capturer(reponse):
        if "application/json" in (reponse.headers.get("content-type", "")):
            reponses.append(reponse.json())

    page.on("response", capturer)
    page.goto("https://example.com")

    print(f"{len(reponses)} reponses JSON interceptees")
    page.context.browser.close()

Pour aller plus loin, page.route permet de bloquer des ressources (publicités, images) afin d'accélérer le scraping, ou de simuler une réponse factice dans des tests. C'est une technique avancée, mais qui débloque les cas les plus tordus.

Télécharger des fichiers

Le scraping ne se limite pas au texte : documents, images, exports CSV. Playwright intercepte les téléchargements déclenchés par la page et les écrit sur ton disque. Pour récupérer un fichier au passage, écoute l'événement download :

python
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    page = p.chromium.launch(headless=True).new_page()
    page.goto("https://example.com")

    # S'abonner au prochain telechargement
    with page.expect_download() as info_download:
        page.get_by_role("link", name="Télécharger le rapport").click()

    telechargement = info_download.value
    # Enregistre sous un nom explicite
    telechargement.save_as("rapport.csv")
    print("Fichier enregistre :", telechargement.suggested_filename)

    page.context.browser.close()

expect_download attend qu'un téléchargement soit déclenché par le clic, puis te donne accès au flux. C'est la méthode propre pour récupérer des exports générés à la volée, sans devoir deviner l'URL du fichier.

Écrire des tests avec expect

Playwright est aussi un framework de test de bout en bout. Le module expect fournit des assertions qui attendent automatiquement la condition, sans sleep :

python
from playwright.sync_api import sync_playwright, expect

with sync_playwright() as p:
    page = p.chromium.launch(headless=True).new_page()
    page.goto("https://example.com")

    # L'assertion attend que la condition soit vraie (avec un timeout implicite)
    expect(page).to_have_title("Example Domain")

    titre = page.get_by_role("heading", name="Example Domain")
    expect(titre).to_be_visible()

    print("Tous les tests passent.")
    page.context.browser.close()

Pour de vrais projets, Playwright fournit un outil de test dédié avec rapport, parallélisation et reprise des traces. Mais même en simple script, expect apporte une vraie valeur : il transforme des vérifications fragiles en assertions fiables et lisibles.

Contextes, cookies et sessions isolées

Un contexte de navigateur est un environnement isolé : ses propres cookies, son stockage local, sa géolocalisation. C'est la brique qui permet de tester plusieurs comptes en parallèle, ou de simuler un utilisateur connecté sans refaire le login à chaque run :

python
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=True)

    # Deux contextes isoles, chacun avec ses propres cookies
    contexte_a = browser.new_context()
    contexte_b = browser.new_context()

    page_a = contexte_a.new_page()
    page_b = contexte_b.new_page()

    page_a.goto("https://example.com")
    page_b.goto("https://example.com")

    # Les cookies de A ne sont pas visibles dans B
    print("Cookies A :", contexte_a.cookies())
    print("Cookies B :", contexte_b.cookies())

    browser.close()

Tu peux aussi sauvegarder l'état d'un contexte (storage_state) pour le recharger plus tard, et ainsi conserver une session connectée entre deux exécutions. C'est le moyen le plus propre de gérer l'authentification dans des scripts de scraping.

Mode headless, lenteur et débogage

Le mode headless exécute le navigateur sans interface, ce qui est rapide et adapté aux serveurs. Mais pour comprendre ce qui se passe, rien ne vaut un navigateur visible. Quelques réglages utiles :

  • headless=False pour voir le navigateur agir, idéal en développement.
  • slow_mo=200 pour ralentir chaque action de 200 ms et suivre le déroulé à l'œil.
  • Le Playwright Inspector, lancé avec PWDEBUG=1, permet de passer le script pas à pas.
  • page.pause() fige l'exécution à un point précis pour inspecter la page.

Ces options ne servent pas qu'au confort : certains sites se comportent différemment face à un navigateur headless, et un passage en headful peut lever une ambiguïté en quelques secondes.

Bonnes pratiques et anti-blocage

Le scraping automatisé peut se heurter à des mécanismes anti-bot. Pour rester dans les clous et limiter les blocages, applique ces principes :

  1. Respecte les conditions d'utilisation et le fichier robots.txt du site cible.
  2. Espaces tes requêtes : un délai aléatoire entre deux pages est plus humain qu'une rafale.
  3. Utilise un user-agent réaliste et un viewport cohérent via new_context.
  4. Mets en cache ce que tu as déjà récupéré pour ne pas re-scraper inutilement.
  5. Préfère une API officielle quand elle existe : c'est plus stable et plus respectueux.

Un scraping raisonnable, espacé et limité au nécessaire pose rarement problème. L'automatisation de navigateur est un outil : elle reste ton alliée tant que tu l'utilises avec discernement.

Conclusion

Tu sais désormais piloter un vrai navigateur depuis Python : lancer Chromium, naviguer, interagir par rôle et libellé, laisser la synchronisation automatique gérer le timing, extraire des données, capturer des pages, gérer la pagination, écrire des assertions, et isoler des sessions dans des contextes.

Les pistes d'approfondissement sont nombreuses : l'API asynchrone pour du scraping à grande échelle, le framework de test intégré avec rapport et traces, ou l'interception réseau (page.route) pour modifier les requêtes à la volée. Avec ces bases, tu peux attaquer à peu près n'importe quel site du web.

Un dernier mot : la valeur de Playwright ne se mesure pas seulement au temps gagné sur un script, mais à la confiance qu'il apporte. Quand tes tests tournent sur un vrai navigateur, tu sais que ce que tu livres correspond à ce qu'un utilisateur verra réellement. C'est cette garantie qui vaut de l'or, en scraping comme en développement.

Pour aller plus loin

La documentation officielle : playwright.dev/python, avec la référence complète de l'API.

Le dépôt microsoft/playwright-python pour suivre les versions et les notes de sortie.