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 :
pip install playwright
playwright installSi tu ne veux qu'un seul navigateur pour gagner de la place, précise-le. Pour ce tutoriel, Chromium suffit :
playwright install chromiumIl 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 :
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 :
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 :
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 :
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 :
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 :
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 :
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 :
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 :
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 :
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 :
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 :
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 :
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 :
- Respecte les conditions d'utilisation et le fichier robots.txt du site cible.
- Espaces tes requêtes : un délai aléatoire entre deux pages est plus humain qu'une rafale.
- Utilise un user-agent réaliste et un viewport cohérent via new_context.
- Mets en cache ce que tu as déjà récupéré pour ne pas re-scraper inutilement.
- 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.






