📍 Guide principal : Web scraping en Python : le guide complet.
Cinquième étape de la série. Elle traite le cas que requests et Scrapy ne savent pas gérer seuls : les pages dont le contenu n’arrive qu’après l’exécution du JavaScript.
Le fournisseur d’Aminata lance un nouveau portail. Aminata relance fièrement son script… qui ne ramène rien. En regardant de près, le HTML reçu est presque vide : un squelette, et un gros bloc de JavaScript. Les articles ne sont pas dans la page livrée par le serveur — ils sont construits dans le navigateur, après coup, par du code qui interroge une API et insère les éléments. requests, lui, ne voit que le squelette. Pour ces pages-là, il faut un outil qui se comporte comme un vrai navigateur : Playwright.
Pour s’entraîner à la technique, on utilise quotes.toscrape.com/js, une page publique conçue exprès pour charger son contenu en JavaScript — exactement le comportement du nouveau portail. À la fin, vous saurez piloter un navigateur depuis Python pour récupérer ce que requests ne peut pas atteindre.
🎯 Ce que vous allez apprendre
- Reconnaître une page « dynamique » (rendue en JavaScript) et savoir pourquoi requests échoue dessus.
- Lancer un navigateur sans interface (headless) avec Playwright et ouvrir une page.
- Attendre que le contenu apparaisse avant de l’extraire — le point décisif.
- Extraire les données avec les locators, et gérer le défilement infini.
🛠️ Ce que vous allez construire
Un script portail.py qui ouvre un vrai navigateur en arrière-plan, charge la page dynamique, attend l’apparition des éléments, puis extrait chaque entrée (texte et auteur). On ajoutera la gestion du défilement infini, fréquent sur les portails modernes, et un pont vers BeautifulSoup pour réutiliser ce que vous savez déjà.
Prérequis
- Python 3.13 (Playwright prend en charge Python jusqu’à 3.13 au moment d’écrire ; restez sur cette version pour toute la série).
- Installer la bibliothèque puis le navigateur :
pip install playwrightsuivi deplaywright install chromium. - Avoir vu requests + BeautifulSoup aide, mais n’est pas indispensable.
- ⏱️ Temps estimé : ~45 minutes.
Étape 1 — Constater le problème
Avant de sortir l’artillerie, prouvons que requests ne suffit pas. C’est une étape de diagnostic à ne jamais sauter : Playwright est plus lourd et plus lent, on ne l’emploie que lorsque c’est nécessaire. Le test tient en quelques lignes : on télécharge la page dynamique avec requests et on cherche un élément de contenu.
import requests
html = requests.get("https://quotes.toscrape.com/js/", timeout=10).text
print("div.quote présent :", "quote" in html)
print("Taille du HTML :", len(html))
Le mot « quote » apparaît bien (dans le JavaScript), mais si vous parsez ce HTML avec BeautifulSoup, select("div.quote") renvoie une liste vide : les éléments n’existent pas encore. Comparez avec la version statique quotes.toscrape.com (sans /js), où le même sélecteur trouve dix citations. La différence est là, nette : le contenu est généré côté navigateur. requests a fait son travail ; ce n’est simplement pas le bon outil ici.
Étape 2 — Lancer un navigateur avec Playwright
Playwright pilote un vrai moteur de navigateur (Chromium, par défaut). Il l’ouvre, charge la page comme le ferait un humain — JavaScript compris — puis vous donne accès au résultat. On travaille en mode headless : le navigateur tourne sans fenêtre visible, ce qui le rend rapide et utilisable sur un serveur sans écran.
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
navigateur = p.chromium.launch(headless=True)
page = navigateur.new_page()
page.goto("https://quotes.toscrape.com/js/", timeout=15000)
print("Titre de la page :", page.title())
navigateur.close()
Le bloc with sync_playwright() as p démarre et arrête proprement Playwright. launch ouvre le navigateur, new_page crée un onglet, goto charge l’URL. On ferme toujours le navigateur à la fin pour libérer la mémoire. Si page.title() affiche le titre du site, le navigateur fonctionne — mais attention, charger la page ne veut pas dire que son contenu JavaScript est déjà prêt. C’est tout l’objet de l’étape suivante.
Étape 3 — Attendre que le contenu apparaisse
Voici l’erreur numéro un du scraping dynamique : extraire trop tôt. Entre le moment où la page se charge et celui où le JavaScript a fini d’insérer les éléments, il s’écoule un délai variable. Si vous lisez juste après goto, vous trouvez souvent… rien. La solution n’est jamais un sleep arbitraire (trop court il rate, trop long il gaspille) mais une attente conditionnelle : on dit à Playwright d’attendre qu’un élément précis existe.
page.goto("https://quotes.toscrape.com/js/", timeout=15000)
page.wait_for_selector("div.quote") # attend l'apparition réelle
nombre = page.locator("div.quote").count()
print("Citations chargées :", nombre)
wait_for_selector bloque jusqu’à ce qu’au moins un div.quote soit présent dans la page, ou jusqu’au délai limite. C’est robuste : votre script s’adapte à la vitesse réelle du réseau et du serveur, au lieu de deviner une durée. Le compteur doit afficher 10. Retenez ce réflexe : sur une page dynamique, on attend toujours un élément, jamais une durée fixe.
✅ Point d’étape —
count()renvoie 10. S’il renvoie 0, c’est que le sélecteur est faux ou que le contenu vient d’ailleurs : ouvrez le navigateur enheadless=Falsepour voir la page se construire sous vos yeux et repérer le bon élément.
Étape 4 — Extraire avec les locators
Playwright manipule les éléments via des locators : des références vivantes vers des éléments de la page, qu’on interroge par leur texte ou leurs attributs. La logique ressemble à BeautifulSoup, avec une syntaxe propre à Playwright. On parcourt les citations et, dans chacune, on lit le texte et l’auteur.
citations = page.locator("div.quote")
resultats = []
for i in range(citations.count()):
bloc = citations.nth(i)
resultats.append({
"texte": bloc.locator("span.text").inner_text(),
"auteur": bloc.locator("small.author").inner_text(),
})
navigateur.close()
print(f"{len(resultats)} citations extraites")
print(resultats[0])
citations.nth(i) cible la i-ème citation ; à l’intérieur, un nouveau locator descend vers le texte et l’auteur, et inner_text() en récupère le contenu affiché. On range le tout dans une liste de dictionnaires — le même format de travail que dans les tutoriels précédents, ce qui veut dire que tout ce que vous avez appris sur le nettoyage et l’export (CSV, SQLite) s’applique tel quel ici.
Étape 5 — Gérer le défilement infini
Beaucoup de portails ne paginent pas : ils chargent de nouveaux éléments à mesure qu’on descend, indéfiniment. Pour tout récupérer, il faut reproduire ce geste : faire défiler la page, attendre que de nouveaux éléments arrivent, et recommencer jusqu’à ce que plus rien n’apparaisse. On détecte la fin en comparant le nombre d’éléments avant et après un défilement.
page.goto("https://quotes.toscrape.com/scroll", timeout=15000)
page.wait_for_selector("div.quote")
precedent = 0
while True:
page.mouse.wheel(0, 10000) # défile vers le bas
page.wait_for_timeout(1000) # laisse le temps de charger
actuel = page.locator("div.quote").count()
if actuel == precedent: # plus rien de nouveau
break
precedent = actuel
print("Total après défilement :", actuel)
La boucle défile, patiente une seconde, puis compte. Tant que le compteur augmente, c’est qu’il reste du contenu à charger ; quand il stagne, on s’arrête. Ici, wait_for_timeout est légitime — c’est une petite pause de courtoisie après une action, pas une attente du contenu (qu’on mesure, elle, par le compteur). Sur cette page d’entraînement, vous passez de 10 à 100 citations.
Accélérer et économiser : bloquer les ressources inutiles
Un navigateur télécharge tout : images, polices, feuilles de style, traceurs publicitaires. Pour du scraping, ces ressources sont du poids mort — on ne veut que le texte et le JavaScript qui le construit. Playwright permet d’intercepter chaque requête sortante et d’annuler celles dont on n’a pas besoin. Le chargement accélère, et surtout la consommation de données chute.
page = navigateur.new_page()
# Annuler le chargement des images, polices et médias
page.route(
"**/*",
lambda route: route.abort()
if route.request.resource_type in {"image", "font", "media"}
else route.continue_(),
)
page.goto("https://quotes.toscrape.com/js/", timeout=15000)
page.route intercepte toutes les requêtes (le motif **/* les vise toutes). Pour chacune, on regarde son resource_type : si c’est une image, une police ou un média, on l’annule avec route.abort() ; sinon on la laisse passer avec route.continue_(). Le HTML et le JavaScript, seuls indispensables, continuent d’arriver. Sur une page riche en visuels, l’économie de données dépasse souvent la moitié du poids total — un argument décisif quand chaque mégaoctet compte, et un crawl notablement plus rapide en prime.
Étape 6 — Passer le relais à BeautifulSoup
Vous n’êtes pas obligé d’extraire avec les locators de Playwright. Une approche très pratique consiste à laisser Playwright faire ce qu’il fait de mieux — exécuter le JavaScript — puis à récupérer le HTML final et à le confier à BeautifulSoup, dont vous maîtrisez déjà les sélecteurs. Le meilleur des deux mondes.
from bs4 import BeautifulSoup
html_rendu = page.content() # le HTML APRÈS exécution du JavaScript
navigateur.close()
soup = BeautifulSoup(html_rendu, "lxml")
citations = soup.select("div.quote")
print("Via BeautifulSoup :", len(citations))
page.content() renvoie le HTML tel qu’il est après le rendu — cette fois, select("div.quote") trouve bien les éléments, contrairement à l’étape 1. C’est le pont idéal : Playwright pour franchir l’obstacle JavaScript, BeautifulSoup pour l’extraction confortable, et tout votre code de nettoyage et d’export réutilisé sans changement.
✅ Point d’étape — Les deux méthodes (locators et BeautifulSoup) donnent le même nombre d’éléments. Vous savez désormais récupérer du contenu dynamique et le ramener dans votre chaîne de traitement habituelle.
🐞 Pièges fréquents
| Symptôme / erreur | Cause probable | Correctif |
|---|---|---|
Executable doesn't exist au lancement |
Le navigateur n’a pas été téléchargé | Lancer playwright install chromium une fois après l’installation du paquet |
count() renvoie 0 |
Extraction avant la fin du rendu | Ajouter wait_for_selector sur un élément du contenu |
TimeoutError sur wait_for_selector |
Mauvais sélecteur ou contenu jamais chargé | Relancer en headless=False pour observer ; vérifier le sélecteur dans l’inspecteur |
| Le script est très lent | Un navigateur complet est lourd par nature | N’utiliser Playwright que si requests échoue ; sinon, rester sur requests/Scrapy |
🌍 Adaptation au contexte ouest-africain
Deux points d’attention. D’abord la taille : playwright install chromium télécharge un navigateur de plusieurs centaines de mégaoctets. Sur une connexion lente ou facturée au volume, lancez l’installation une fois, dans un endroit stable, et évitez de recréer l’environnement sans raison. Ensuite la légèreté à l’exécution : le mode headless est indispensable sur un petit VPS ou un poste modeste, car il évite tout rendu graphique. Et n’oubliez pas la règle d’or : un navigateur piloté consomme bien plus de mémoire et de processeur que requests. Avant de dégainer Playwright, vérifiez toujours qu’une simple requête ne suffit pas — c’est souvent le cas, même sur des sites d’apparence moderne.
Dernier réflexe utile en zone à faible débit : combinez le mode headless, le blocage des images vu plus haut et un timeout généreux sur goto — ce trio rend un scraper dynamique viable même derrière une connexion mobile capricieuse, là où un navigateur complet et gourmand échouerait.
✅ Récapitulatif
Vous savez maintenant traiter les pages que requests ne voit pas. Vous avez d’abord diagnostiqué le problème (contenu généré en JavaScript), puis piloté un vrai navigateur avec Playwright : ouverture en mode headless, attente conditionnelle du contenu — le geste qui fait toute la différence —, extraction par locators, gestion du défilement infini, et pont vers BeautifulSoup pour réutiliser votre chaîne de traitement. Vous disposez désormais des trois outils du parcours : requests/BeautifulSoup pour le statique, Scrapy pour crawler à l’échelle, Playwright pour le dynamique. Il ne manque qu’une chose pour scraper en professionnel : savoir le faire de façon responsable et légale.
🧾 Aide-mémoire
| Élément | Rôle |
|---|---|
playwright install chromium |
Télécharger le navigateur (une fois) |
sync_playwright() |
Démarrer/arrêter Playwright |
p.chromium.launch(headless=True) |
Ouvrir un navigateur sans fenêtre |
page.goto(url) |
Charger une page |
page.wait_for_selector(sel) |
Attendre l’apparition d’un élément |
page.locator(sel).nth(i).inner_text() |
Extraire le texte d’un élément |
page.content() |
Récupérer le HTML rendu (pour BeautifulSoup) |
💪 À vous de jouer
Sur quotes.toscrape.com/js, chaque citation porte des étiquettes (div.tags a.tag). Ajoutez à chaque dictionnaire une liste tags contenant ces mots-clés.
Voir une solution
etiquettes = bloc.locator("div.tags a.tag")
bloc_tags = [etiquettes.nth(j).inner_text()
for j in range(etiquettes.count())]
resultats.append({
"texte": bloc.locator("span.text").inner_text(),
"auteur": bloc.locator("small.author").inner_text(),
"tags": bloc_tags,
})
On compte les étiquettes du bloc, puis on lit chacune par son index. Le même motif count() + nth() sert partout avec Playwright.
Tutoriels frères
- Scrapy avancé : pagination, throttling et pipelines — pour les sites statiques à grande échelle.
- Scraping responsable : robots.txt, éthique et limites — la dernière brique, indispensable.
Pour aller plus loin
- 🔝 Retour au guide complet du web scraping en Python.
- Documentation officielle : Playwright pour Python.
- À noter : Playwright sert aussi aux tests end-to-end ; c’est le même outil, un autre usage.
FAQ
Playwright ou Selenium ?
Les deux pilotent un navigateur. Playwright est plus récent, plus rapide à configurer (il installe ses propres navigateurs) et son attente automatique des éléments évite beaucoup de code fragile. Pour démarrer aujourd’hui, c’est un excellent choix.
Comment savoir si une page a besoin de Playwright ?
Testez d’abord avec requests : si le contenu recherché est absent du HTML reçu (ou si « Afficher le code source » dans le navigateur ne le montre pas), c’est qu’il est généré en JavaScript. Dans ce cas seulement, passez à Playwright.
headless=True ou False ?True en production (rapide, sans écran). False pendant la mise au point, pour voir ce que fait le navigateur et déboguer un sélecteur ou une attente récalcitrante.
Peut-on réutiliser une session déjà connectée ?
Oui : Playwright sait enregistrer l’état d’un navigateur — cookies compris — dans un fichier via storage_state, puis le recharger au lancement suivant. On évite ainsi de refaire une authentification à chaque exécution. À réserver, bien sûr, aux comptes et aux sites où vous êtes autorisé à le faire.