Une grande partie des données qui valent la peine d'être collectées n'apparaît jamais dans le HTML brut. Les grilles de produits, les fils de commentaires, les flux à défilement infini et les widgets de tableau de bord arrivent tous après l'exécution de JavaScript dans le navigateur ; une requête HTTP simple vous remet donc une coquille avec les parties intéressantes manquantes. La réponse classique est de rendre la page dans un vrai navigateur, puis d'analyser le balisage terminé, et le duo le plus courant pour cela en Python est Selenium avec BeautifulSoup.

Ce guide vous montre comment scraper du contenu dynamique avec Selenium et BeautifulSoup : Selenium pilote une instance Chrome headless pour exécuter le JavaScript, attendre les éléments, faire défiler pour déclencher le chargement paresseux et cliquer sur les interactions, tandis que BeautifulSoup analyse la page_source rendue en données structurées propres. Nous construirons un exemple petit mais exécutable, puis nous examinerons honnêtement où ce stack devient coûteux et où une API de rendu côté serveur est le choix le plus léger.

Pourquoi une requête simple rate le contenu dynamique

Quand une page est rendue côté serveur, le HTML que vous téléchargez contient déjà les données. Quand elle est rendue côté client, le serveur envoie une coquille presque vide ainsi qu'un bundle JavaScript ; le navigateur exécute ce JavaScript, rappelle une API, et injecte le contenu réel dans le DOM par la suite. Une bibliothèque comme requests ne voit que la première réponse, elle ne voit donc jamais le contenu que JavaScript ajoute ensuite.

C'est là tout le problème que le scraping dynamique résout : vous avez besoin de quelque chose qui exécute réellement le JavaScript de la page avant de lire le DOM. Selenium fait exactement cela en automatisant un vrai navigateur. Une fois que le navigateur a tout rendu, le HTML résultant n'est que du HTML, et BeautifulSoup est le moyen rapide et ergonomique d'en extraire des champs. Les deux outils se partagent le travail clairement : Selenium gère l'interaction et le rendu, BeautifulSoup gère l'extraction.

Render first, parse second

BeautifulSoup n'exécute pas JavaScript. Seul, il analyse le HTML que vous lui donnez, donc lui fournir la réponse brute d'une page rendue côté client vous donne la même coquille vide qu'un simple fetch. L'étape de rendu doit se produire en premier, que ce soit via un navigateur local via Selenium ou une API de rendu qui renvoie du HTML terminé.

Configurer l'environnement

Vous avez besoin de Python 3.8 ou supérieur et de pip. Créez un environnement virtuel pour que les dépendances restent isolées, puis installez Selenium et BeautifulSoup.

bash
python -m venv scraper_env
source scraper_env/bin/activate

pip install selenium beautifulsoup4

Sur Windows, activez avec scraper_env\Scripts\activate au lieu de la ligne source. Vous n'avez pas besoin de télécharger manuellement un binaire de driver : depuis Selenium 4.10, Selenium Manager résout et télécharge automatiquement le ChromeDriver correspondant la première fois que vous lancez Chrome, donc une installation Chrome à jour plus le package selenium suffisent pour commencer.

Étape 1 : Lancer un WebDriver Chrome headless

Commencez par configurer Chrome pour qu'il fonctionne en mode headless, c'est-à-dire sans fenêtre visible. Le mode headless est plus rapide et est ce que vous souhaitez sur un serveur, bien que l'exécution avec affichage pendant le développement facilite beaucoup le débogage des sélecteurs. Quelques flags supplémentaires maintiennent le navigateur stable dans les conteneurs et réduisent la surface sur laquelle les vérifications anti-bot simples s'appuient.

python
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

def build_driver():
    options = Options()
    options.add_argument("--headless=new")
    options.add_argument("--no-sandbox")
    options.add_argument("--disable-dev-shm-usage")
    options.add_argument("--window-size=1920,1080")
    return webdriver.Chrome(options=options)

Une taille de fenêtre fixe est plus importante qu'il n'y paraît : beaucoup de sites affichent des mises en page différentes selon la largeur du viewport, donc fixer la taille maintient vos sélecteurs stables d'une exécution à l'autre. Pour déboguer ce que le navigateur voit réellement, supprimez la ligne --headless=new et regardez la page se charger en direct.

Étape 2 : Naviguer et attendre les éléments explicitement

La plus grosse erreur dans le scraping dynamique est de lire le DOM avant que le contenu ne soit arrivé. Un time.sleep() fixe est la mauvaise solution : trop court et vous ratez des données, trop long et chaque exécution est lente. Le bon outil est une attente explicite, qui interroge la page jusqu'à ce qu'une condition spécifique soit vraie (ou qu'un délai d'attente se déclenche), puis revient immédiatement une fois que c'est le cas. Selenium fournit cela sous la forme de WebDriverWait associé à expected_conditions.

python
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

def load_page(driver, url, wait_for):
    driver.get(url)
    WebDriverWait(driver, 15).until(
        EC.presence_of_element_located((By.CSS_SELECTOR, wait_for))
    )

Ici, load_page navigue vers l'URL et se bloque jusqu'à ce qu'au moins un élément correspondant à wait_for apparaisse, pendant 15 secondes maximum. Utilisez visibility_of_element_located quand vous avez également besoin que l'élément soit peint (pas seulement présent dans le DOM), et element_to_be_clickable avant de cliquer sur quelque chose. Lier votre attente à l'élément qui vous intéresse réellement est ce qui rend une exécution Selenium à la fois rapide et fiable.

Étape 3 : Faire défiler pour déclencher le chargement paresseux

Beaucoup de flux et de grilles de produits chargent davantage d'éléments seulement lorsque vous faites défiler. Pour les capturer tous, vous devez piloter le défilement vous-même, puis attendre que le nouveau lot soit rendu avant de défiler à nouveau. Le schéma est une boucle : faites défiler jusqu'en bas, attendez, mesurez la hauteur de la page, et arrêtez quand elle cesse de croître.

python
import time

def scroll_to_bottom(driver, pause=2.0, max_rounds=10):
    last_height = driver.execute_script("return document.body.scrollHeight")
    for _ in range(max_rounds):
        driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
        time.sleep(pause)
        new_height = driver.execute_script("return document.body.scrollHeight")
        if new_height == last_height:
            break
        last_height = new_height

Le court sleep ici est une pause pragmatique pour que le prochain lot soit récupéré et rendu ; c'est le seul endroit où un délai fixe est difficile à éviter car le déclencheur est le réseau, pas un élément connu. Limitez la boucle avec max_rounds pour qu'une page qui ne cesse jamais de croître ne puisse pas tourner indéfiniment. Si le site utilise un bouton "Charger plus" au lieu du défilement infini, l'équivalent est de trouver le bouton, de cliquer dessus, d'attendre les nouvelles lignes, et de répéter jusqu'à ce que le bouton disparaisse.

Étape 4 : Passer le HTML rendu à BeautifulSoup

Une fois la page rendue et entièrement déroulée, le reste est une analyse ordinaire. Lisez driver.page_source, qui est le DOM en direct sérialisé en HTML, et chargez-le dans BeautifulSoup. À partir de là, vous sélectionnez les éléments par sélecteur CSS exactement comme vous le feriez avec n'importe quelle page statique.

python
from bs4 import BeautifulSoup

def parse_items(html):
    soup = BeautifulSoup(html, "html.parser")
    items = []
    for card in soup.select("div.product-card"):
        title = card.select_one("h2.title")
        price = card.select_one("span.price")
        items.append({
            "title": title.get_text(strip=True) if title else None,
            "price": price.get_text(strip=True) if price else None,
        })
    return items

Les gardes autour de chaque champ (title.get_text(...) if title else None) évitent qu'un élément manquant ne fasse planter toute l'exécution, ce qui vaut la peine d'être fait dès le départ car les vraies annonces sont incohérentes. Vous pourriez interroger les éléments via le propre find_elements de Selenium à la place, mais BeautifulSoup est plus rapide pour l'extraction en masse et son API de sélecteur et de navigation est plus conviviale une fois que le DOM est stabilisé.

Étape 5 : Assembler les pièces

Reliez les quatre étapes en un script : construire le driver, charger et attendre, faire défiler, analyser, puis toujours quitter le driver pour ne pas laisser fuir les processus du navigateur.

python
import json

def main():
    url = "https://example.com/products"
    driver = build_driver()
    try:
        load_page(driver, url, "div.product-card")
        scroll_to_bottom(driver)
        data = parse_items(driver.page_source)
    finally:
        driver.quit()
    print(json.dumps(data, indent=2))

if __name__ == "__main__":
    main()

Le try/finally n'est pas optionnel en production. Une boucle de défilement ou une attente peut lever une exception, et si driver.quit() ne s'exécute jamais, vous laissez un processus Chrome zombie derrière vous à chaque échec. Sur un long travail, cela épuise rapidement la mémoire. Pour une couverture plus approfondie de l'exécution des navigateurs de cette façon, voir navigateur headless pour le web scraping, et pour le panorama plus large du rendu JavaScript avec Python, comment scraper des pages JavaScript avec Python.

Les coûts réels de l'approche Selenium

Ce stack fonctionne, et pour un scraping ponctuel de quelques pages, il est difficile de faire mieux. À grande échelle, les coûts s'accumulent, et il vaut la peine de les nommer avant de s'engager à faire tourner une flotte de navigateurs.

  • Surcharge des navigateurs. Chaque page lance une instance Chrome complète avec son empreinte mémoire et CPU. Une douzaine de drivers concurrents peuvent saturer un petit serveur, donc le débit est limité par le matériel, pas seulement par le réseau.
  • Attentes instables. Les attentes explicites sont bien meilleures que sleep, mais elles se cassent quand un site modifie son balisage ou son timing, et une attente qui passe localement peut expirer sur une machine plus lente. La logique d'attente devient une maintenance permanente.
  • Détection anti-bot. Un navigateur headless laisse encore fuiter des signaux (empreintes de driver, en-têtes manquants, adresses IP de centres de données) que les défenses modernes détectent. En volume, vous rencontrerez des CAPTCHAs et des blocages d'IP qu'aucune quantité d'attente ne résout, ce qui implique d'ajouter une rotation de proxy et des correctifs d'empreinte par-dessus tout ce qui précède.

Rien de tout cela ne fait de Selenium le mauvais outil ; cela en fait un outil lourd. Quand le travail est "rendre beaucoup de pages de manière fiable depuis un serveur sans surveiller une flotte de navigateurs", le rendu et le déblocage sont les parties difficiles, et ce sont exactement elles qu'une API gérée peut prendre en charge.

Crawlbase Crawling API

Si vous voulez du HTML rendu sans faire tourner une flotte de navigateurs, l'API Crawling rend la page dans un vrai navigateur côté serveur et fait tourner des IP résidentielles pour vous, puis renvoie du HTML terminé que vous analysez avec le même code BeautifulSoup. Vous passez un token JS et des options d'attente au lieu de gérer des drivers, des boucles de défilement et un pool de proxies. Commencez avec le niveau gratuit et pointez-le d'abord sur une seule page dynamique.

L'alternative plus légère : rendre côté serveur, analyser localement

L'API Crawling conserve la moitié de ce flux de travail que vous aimez réellement (l'extraction BeautifulSoup) et supprime la moitié qui pose problème (faire tourner et débloquer les navigateurs). Vous envoyez l'URL avec un token JavaScript, l'API la rend derrière une IP de confiance, et vous analysez le HTML renvoyé exactement comme avant. La même fonction parse_items de l'étape 4 fonctionne sans modification.

python
from crawlbase import CrawlingAPI
from bs4 import BeautifulSoup
import json

api = CrawlingAPI({"token": "YOUR_CRAWLBASE_JS_TOKEN"})

def fetch_rendered(url):
    options = {"ajax_wait": "true", "page_wait": 5000}
    response = api.get(url, options)
    if response["status_code"] == 200:
        return response["body"].decode("utf-8")
    print(f"Request failed: {response['status_code']}")
    return None

html = fetch_rendered("https://example.com/products")
if html:
    print(json.dumps(parse_items(html), indent=2))

L'option ajax_wait indique à l'API d'attendre que le contenu asynchrone se stabilise, et page_wait maintient un nombre fixe de millisecondes après le chargement pour que les éléments à rendu tardif apparaissent avant la capture. Augmentez page_wait si des champs reviennent vides. Notez ce qui a disparu : pas de cycle de vie de driver, pas de boucle de défilement, pas de nettoyage try/finally, et pas de gestion de proxy. Le rendu et la rotation d'IP se produisent côté serveur, donc mille pages représentent mille appels HTTP plutôt que mille lancements de navigateur.

When to keep Selenium

Si votre tâche nécessite une véritable interaction multi-étapes (connexion, remplissage et soumission d'un formulaire, navigation dans un assistant, puis lecture d'un état qui dépend de ces actions), la session de navigateur avec état de Selenium est le bon outil. L'API Crawling brille quand l'objectif est "rendre cette URL et me donner le HTML terminé" en volume. De nombreux projets utilisent les deux : un navigateur pour les quelques flux interactifs, l'API pour la récupération en masse.

Pour d'autres stacks d'automatisation de navigateur dignes de comparaison, Playwright pour le web scraping couvre une alternative moderne à Selenium avec des compromis similaires. Si vous préférez router votre propre trafic de navigateur via des adresses IP rotatives plutôt que d'utiliser l'API gérée, le Smart AI Proxy vous donne une rotation résidentielle comme endpoint proxy plug-and-play, et pour les tâches "lancer et oublier", le Crawler asynchrone pousse les résultats rendus vers un callback au lieu de bloquer sur chaque requête.

Récapitulatif

Points clés

  • Le contenu dynamique nécessite un rendu. JavaScript injecte les données après la première réponse, vous devez donc exécuter la page avant de l'analyser ; un simple fetch renvoie une coquille.
  • Selenium rend, BeautifulSoup extrait. Pilotez un WebDriver Chrome headless pour rendre et interagir, puis passez driver.page_source à BeautifulSoup pour une extraction rapide basée sur des sélecteurs.
  • Utilisez des attentes explicites, pas des sleeps. WebDriverWait avec expected_conditions lié à l'élément dont vous avez besoin est à la fois plus rapide et plus fiable qu'un délai fixe.
  • Faites défiler pour déclencher le chargement paresseux. Bouclez défilement-attente-mesure jusqu'à ce que la hauteur de page cesse de croître, avec une limite de tours pour qu'elle ne tourne pas indéfiniment.
  • Selenium est lourd à grande échelle. La surcharge des navigateurs, les attentes instables et le blocage anti-bot s'accumulent ; une API de rendu comme l'API Crawling renvoie du HTML terminé avec la rotation d'IP gérée, et votre code BeautifulSoup reste identique.

Foire aux questions

Pourquoi BeautifulSoup ne peut-il pas scraper du contenu dynamique seul ?

BeautifulSoup est un parser, pas un navigateur : il lit le HTML que vous lui donnez mais n'exécute jamais JavaScript. Sur une page rendue côté client, le HTML brut est une coquille vide, donc BeautifulSoup n'a rien à extraire tant que quelque chose n'a pas rendu la page en premier. Cette étape de rendu est ce que Selenium fournit localement, ou ce qu'une API de rendu avec token JS fournit côté serveur, avant que BeautifulSoup n'entre en jeu.

Comment attendre les éléments dynamiques au lieu de deviner avec sleep ?

Utilisez une attente explicite. WebDriverWait(driver, timeout).until(EC.presence_of_element_located((By.CSS_SELECTOR, sel))) interroge la page et revient à l'instant où l'élément apparaît, jusqu'au délai d'attente. C'est plus rapide qu'un time.sleep() fixe car il n'attend pas plus longtemps que nécessaire, et plus fiable car il est lié à l'élément réel dont vous avez besoin plutôt qu'à une durée devinée.

Dois-je encore télécharger ChromeDriver manuellement ?

Pas depuis Selenium 4.10. Selenium Manager résout et télécharge automatiquement la version ChromeDriver qui correspond à votre Chrome installé la première fois que vous lancez le navigateur. Vous ne gérez le driver manuellement que dans des environnements verrouillés où les téléchargements automatiques sont bloqués, auquel cas vous pointez Selenium vers un binaire de driver que vous fournissez.

Selenium va-t-il me faire passer les systèmes anti-bot ?

Pas seul à grande échelle. Un navigateur headless expose encore des signaux (empreintes d'automatisation, adresses IP de centres de données, en-têtes manquants ou incohérents) que les défenses modernes détectent, vous rencontrerez donc des CAPTCHAs et des blocages d'IP à mesure que le volume augmente. Atténuer cela implique d'ajouter une rotation de proxy et des correctifs d'empreinte, c'est pourquoi une API Crawling gérée qui gère le rendu et la rotation d'IP ensemble est souvent moins de travail que de durcir une flotte de navigateurs.

Quand devrais-je utiliser l'API Crawling au lieu de Selenium et BeautifulSoup ?

Utilisez l'API Crawling quand le travail consiste à rendre de nombreuses pages de manière fiable depuis un serveur et que vous ne souhaitez pas faire tourner ou débloquer une flotte de navigateurs. Elle rend côté serveur, fait tourner des IP résidentielles, et renvoie du HTML terminé que votre code BeautifulSoup existant analyse sans modification. Gardez Selenium quand vous avez besoin d'une véritable interaction avec état comme la connexion, la soumission de formulaires ou la navigation dans un flux multi-étapes.

Puis-je réutiliser mon code d'analyse BeautifulSoup avec l'API Crawling ?

Oui, et c'est l'attrait principal. L'API Crawling renvoie le HTML rendu sous forme de chaîne, donc le même appel BeautifulSoup(html, "html.parser") et les mêmes sélecteurs fonctionnent sans modification. Vous remplacez uniquement la façon dont le HTML est obtenu : au lieu de driver.page_source d'un navigateur local, vous lisez response["body"] depuis l'appel API.

Commencer à construire

Crawlez n'importe quel site à grande échelle, sans combattre l'infrastructure.

Crawlbase gère les proxies, les empreintes et les CAPTCHA afin que votre équipe livre des pipelines de données au lieu de maintenir la plomberie de crawl. 1 000 requêtes gratuites, sans carte requise.

En libre-service · Sans appel commercial requis · Volumes de crawl entreprise disponibles