In this Article
Apprendre à scraper des pages web dynamiques implique de gérer du contenu qui n’existe pas dans le HTML que votre première requête télécharge. Les sites modernes génèrent dans le navigateur, avec JavaScript, les grilles de produits, les prix, les commentaires et les fils d’actualité, si bien qu’une simple requête HTTP renvoie une coquille vide là où les données devraient se trouver.
Ce guide parcourt le flux de travail complet en Python. Il commence par détecter le contenu dynamique en comparant la réponse brute au DOM rendu, puis classe les options d’extraction de la plus légère à la plus lourde : trouver l’API JSON cachée, piloter Playwright ou Selenium, gérer le scroll infini et les boutons « charger plus », réduire la consommation de bande passante via le proxy, ajouter des tentatives, scraper en parallèle et exporter des fichiers JSON et CSV propres.
DataImpulse est un fournisseur éthique de proxies proposant plus de 90 millions d’adresses IP résidentielles, mobiles et datacenter dans 195 pays. Son modèle à l’usage commence à 1 dollar par Go, sans expiration du trafic, et convient au web scraping, à la vérification publicitaire, à la surveillance des prix, aux études de marché et à la gestion de comptes multiples.
Points clés
- Scraping dynamique : le contenu construit par JavaScript est absent du HTML brut, il faut donc soit appeler l’API JSON sous-jacente que la page interroge, soit rendre la page dans un navigateur headless.
- Meilleur type de proxy : les proxies résidentiels rotatifs, qui s’appuient sur de vraies IP de particuliers et aident à contourner la détection.
- Prix : à partir de 1 dollar par Go, à l’usage, avec un trafic qui n’expire pas et sans abonnement.
- Couverture : plus de 90 millions d’IP obtenues de façon éthique dans 195 pays.
- Fiabilité : 99,51 % de taux de réussite, noté 4,8 sur 5 sur G2.
- Protocoles et ciblage : HTTP, HTTPS et SOCKS5, avec le ciblage par pays inclus.

Qu’est-ce qui rend une page web dynamique ?
Une page est dynamique lorsque le contenu utile est généré par JavaScript dans le navigateur plutôt que livré dans la réponse HTML initiale. Le serveur envoie une structure légère, puis le code côté client récupère les données et les injecte dans le DOM une fois la page ouverte.
La vérification manuelle la plus rapide consiste à comparer deux vues de la même page. Faites un clic droit et choisissez Afficher le code source pour voir le HTML brut renvoyé par le serveur, puis ouvrez les outils de développement et inspectez le panneau Elements, qui montre le DOM en direct après l’exécution des scripts. Si le prix ou la fiche recherchée apparaît dans le panneau Elements mais est absent du code source, le contenu est dynamique et une seule requête ne permettra pas de le récupérer.
- Le contenu statique est présent dans le code source et peut être analysé directement depuis une réponse HTTP.
- Le contenu dynamique n’apparaît que dans le DOM rendu et est chargé via des requêtes en arrière-plan.
Vous pouvez automatiser ce diagnostic en code. Commencez par compter les éléments cibles dans le HTML brut téléchargé par un client HTTP classique :
import requests
from bs4 import BeautifulSoup
url = "https://example.com/products"
headers = {"User-Agent": "Mozilla/5.0"}
resp = requests.get(url, headers=headers, timeout=30)
soup = BeautifulSoup(resp.text, "html.parser")
raw_cards = soup.select("div.product-card")
print("Cards in raw HTML:", len(raw_cards))
# If this prints 0 but the page clearly shows products in a browser,
# the content is injected by JavaScript and the page is dynamic.
Ensuite, rendez la même URL dans un navigateur headless et comptez à nouveau. L’écart entre les deux nombres confirme le diagnostic :
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/products", wait_until="networkidle")
rendered_cards = page.query_selector_all("div.product-card")
print("Cards after JavaScript runs:", len(rendered_cards))
browser.close()
# Raw HTML 0, rendered DOM 48 means the page builds its content dynamically.
Avant d’écrire un scraper complet, il vaut aussi la peine de confirmer que la cible autorise l’accès automatisé. Notre guide sur la façon de vérifier si un site autorise le scraping explique comment lire les règles robots et les conditions d’utilisation.
Quelle méthode utiliser pour scraper des pages dynamiques ?
Choisissez la méthode la plus légère qui renvoie les données de façon fiable : appelez une API JSON cachée quand elle existe, et ne recourez à un navigateur headless que lorsqu’il n’y en a pas. Chaque option offre un compromis entre vitesse, coût et fidélité au rendu de la page.
Le tableau ci-dessous compare les quatre approches courantes afin que vous puissiez choisir avant d’écrire du code. En pratique, la plupart des projets en combinent deux, en utilisant une API cachée pour le gros du chargement et un navigateur pour les pages pour lesquelles cela ne suffit pas.
| Méthode | Vitesse | Coût | Idéal pour | Limite principale |
|---|---|---|---|---|
| API JSON cachée | La plus rapide | Le plus faible | Données derrière un appel XHR propre | L’endpoint peut changer ou exiger des tokens |
| Playwright | Modérée | Plus élevé | Rendu complet, contrôle asynchrone moderne | Consomme CPU, mémoire et bande passante |
| Selenium | Modérée | Plus élevé | Stacks existantes, vaste prise en charge des langages | Configuration plus lente, attentes plus verbeuses |
| requests-html | Lente | Faible | JavaScript léger sur des pages simples | Peu maintenu, rendu fragile |
Le coût compte, car un navigateur headless routé via un proxy facturé au volume télécharge bien plus d’octets qu’un simple appel d’API. Si vous scrapez à grande échelle, la méthode choisie pèse autant que le code sur la facture. Pour la discipline plus large du rythme et des en-têtes, voyez nos bonnes pratiques de web scraping.
Comment trouver l’API cachée derrière une page dynamique ?
Cherchez la requête en arrière-plan que la page effectue pour récupérer ses données, car cet endpoint renvoie généralement du JSON propre que vous pouvez appeler directement. C’est l’approche la plus rapide et la plus fiable, et elle évite entièrement d’exécuter un navigateur.
Ouvrez les outils de développement, allez dans l’onglet Network, filtrez par Fetch/XHR et rechargez la page. Lorsque le contenu s’affiche, repérez les requêtes renvoyant du JSON contenant les valeurs voulues. Inspectez l’URL de la requête, la méthode, les paramètres de requête et les éventuels en-têtes ou tokens qu’elle envoie, puis faites un clic droit et copiez-la en cURL pour voir sa forme complète. Une fois que vous pouvez reproduire cet appel avec un client HTTP, vous évitez entièrement le rendu et récupérez des données structurées à une fraction du coût.
Voici un exemple minimal qui appelle un endpoint JSON via un proxy et lit les champs directement :
import requests
proxy = "http://USERNAME:[email protected]:823"
proxies = {"http": proxy, "https": proxy}
headers = {
"User-Agent": "Mozilla/5.0",
"Accept": "application/json",
"Referer": "https://example.com/products",
}
params = {"category": "laptops", "page": 1}
r = requests.get(
"https://example.com/api/v2/products",
headers=headers,
params=params,
proxies=proxies,
timeout=30,
)
r.raise_for_status()
data = r.json()
for item in data["items"]:
print(item["name"], item["price"])
Si l’endpoint est paginé, incrémentez le paramètre de page ou suivez le curseur suivant que renvoie la réponse. Lorsqu’une requête nécessite un token, repérez où la page l’émet pour la première fois et rejouez-le au sein de la même session. Faire passer l’appel via DataImpulse permet à l’endpoint de voir une IP résidentielle plutôt que l’adresse de votre serveur, ce qui compte quand l’API limite le débit par origine. Si vous vous heurtez à un mur d’authentification sur le proxy lui-même, notre note sur l’authentification de proxy explique le format des identifiants.
Comment scraper des pages dynamiques avec Playwright ?
Lorsqu’il n’existe aucune API exploitable, pilotez un navigateur headless comme Playwright pour que le JavaScript s’exécute exactement comme pour un utilisateur, puis lisez le DOM finalisé. Playwright est le choix moderne par défaut car il offre une attente automatique fiable, une excellente prise en charge de l’asynchrone et une option de proxy simple.
Installez-le et téléchargez ensuite le navigateur :
pip install playwright
playwright install chromium
La discipline essentielle consiste à attendre l’élément précis dont vous avez besoin plutôt que de deviner avec des pauses fixes. L’exemple ci-dessous lance Chromium, le fait passer par un proxy défini sous forme de dictionnaire, attend la grille de produits et extrait les valeurs rendues :
from playwright.sync_api import sync_playwright
PROXY = {
"server": "http://gw.dataimpulse.com:823",
"username": "USERNAME",
"password": "PASSWORD",
}
with sync_playwright() as p:
browser = p.chromium.launch(headless=True, proxy=PROXY)
context = browser.new_context(user_agent="Mozilla/5.0")
page = context.new_page()
page.goto("https://example.com/products", wait_until="domcontentloaded")
page.wait_for_selector("div.product-card", timeout=15000)
cards = page.query_selector_all("div.product-card")
for card in cards:
name = card.query_selector(".title").inner_text()
price = card.query_selector(".price").inner_text()
print(name, price)
browser.close()
La stratégie d’attente détermine souvent la réussite ou l’échec la plupart des scrapers dynamiques ; il est donc utile de connaître les quatre stratégies et quand chacune convient. Préférez d’attendre un sélecteur ou une condition précise plutôt que networkidle, car cette option peut rester bloquée sur des pages gardant des connexions de longue durée ouvertes :
# Wait for a specific element (most reliable)
page.wait_for_selector("div.product-card", timeout=15000)
# Wait for the initial DOM to be built, before every script finishes
page.goto(url, wait_until="domcontentloaded")
# Wait until there are no network connections for 500 ms
page.goto(url, wait_until="networkidle")
# Wait for a custom condition, such as a minimum item count
page.wait_for_function(
"document.querySelectorAll('div.product-card').length > 20"
)
Si votre cible repose fortement sur des scripts exécutés dans le navigateur, notre article complémentaire sur le web scraping avec JavaScript approfondit le rendu côté client qui rend ces pages difficiles.
Comment gérer le scroll infini et les boutons « charger plus » ?
Déclenchez les mêmes événements que ceux utilisés par la page pour charger plus de contenu, puis attendez que chaque nouveau lot soit rendu avant de le lire. Le scroll infini et les boutons « charger plus » ne récupèrent des données qu’au fur et à mesure des actions de l’utilisateur, si bien qu’un seul chargement de page ne récupère donc que le contenu affiché initialement.
Avant de recourir au navigateur, vérifiez de nouveau l’onglet Network, car le scroll infini est presque toujours alimenté par un appel XHR paginé, et itérer sur cet endpoint avec l’approche requests décrite plus haut coûte bien moins cher que de faire défiler la page. Si vous devez faire défiler, faites-le dans une boucle contrôlée qui s’arrête dès qu’aucun nouvel élément n’apparaît :
def scroll_until_stable(page, item_selector, max_rounds=30):
seen = 0
for _ in range(max_rounds):
page.evaluate("window.scrollTo(0, document.body.scrollHeight)")
page.wait_for_timeout(1500)
count = len(page.query_selector_all(item_selector))
if count == seen:
break # no new items loaded, stop
seen = count
return seen
Les pages qui utilisent un bouton explicite plutôt que le défilement demandent une boucle de clics. Relevez le nombre d’éléments avant chaque clic, cliquez sur le bouton et attendez que le nombre augmente pour ne pas anticiper la fin du rendu :
def click_load_more(page, button_selector, item_selector, max_clicks=50):
for _ in range(max_clicks):
button = page.query_selector(button_selector)
if button is None or not button.is_visible():
break
before = len(page.query_selector_all(item_selector))
button.click()
page.wait_for_function(
"([sel, n]) => document.querySelectorAll(sel).length > n",
arg=[item_selector, before],
)
return len(page.query_selector_all(item_selector))
Éliminez les doublons au fil de la collecte, car relire les mêmes nœuds à chaque passage est courant. Conservez un ensemble d’ID ou d’URL uniques et n’ajoutez que les enregistrements que vous n’avez pas encore vus.
Peut-on utiliser Selenium au lieu de Playwright ?
Oui. Selenium pilote lui aussi un navigateur réel et constitue un choix solide lorsque votre équipe l’utilise déjà ou a besoin de son large support de langages. Le schéma reflète celui de Playwright : chargez la page, attendez une condition avec WebDriverWait, puis lisez les éléments du DOM rendu.
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
options = Options()
options.add_argument("--headless=new")
options.add_argument("--proxy-server=http://gw.dataimpulse.com:823")
driver = webdriver.Chrome(options=options)
driver.get("https://example.com/products")
WebDriverWait(driver, 15).until(
EC.presence_of_element_located((By.CSS_SELECTOR, "div.product-card"))
)
for card in driver.find_elements(By.CSS_SELECTOR, "div.product-card"):
name = card.find_element(By.CSS_SELECTOR, ".title").text
price = card.find_element(By.CSS_SELECTOR, ".price").text
print(name, price)
driver.quit()
Attention : la simple option –proxy-server n’accepte pas de nom d’utilisateur ni de mot de passe, donc un proxy authentifié nécessite une solution complémentaire. La bibliothèque Selenium Wire injecte proprement les identifiants, et notre tutoriel sur la configuration du proxy Selenium Wire montre la configuration à appliquer. Selenium est plus lourd à configurer que Playwright et ses attentes sont plus verbeuses, si bien que les nouveaux projets démarrent souvent avec Playwright, sauf raison de rester sur Selenium.
Comment réduire la consommation de bande passante via le proxy lors du rendu des pages ?
Bloquez les ressources dont vous n’avez pas besoin pour que le navigateur cesse de télécharger les images, les polices et les médias que vous n’analysez jamais. Un navigateur headless récupère chaque ressource par défaut, et chacun de ces octets coûte de l’argent lorsqu’il transite par un proxy facturé au gigaoctet.
Playwright vous permet d’intercepter les requêtes et d’abandonner celles qui sont sans intérêt pour votre extraction. Bloquer les images, les médias et les polices peut réduire fortement le trafic du proxy tout en laissant intacts le JSON et le HTML que vous lisez réellement :
BLOCK = {"image", "media", "font"}
def block_heavy(route):
if route.request.resource_type in BLOCK:
route.abort()
else:
route.continue_()
context = browser.new_context()
context.route("**/*", block_heavy)
page = context.new_page()
page.goto("https://example.com/products", wait_until="domcontentloaded")
Vous pouvez étendre le même gestionnaire pour ignorer les domaines d’analytics et de publicité qui ajoutent de la charge sans ajouter de données. Avec le trafic DataImpulse facturé à l’usage à partir de 1 dollar par Go et sans expiration, supprimer les octets inutiles réduit directement le coût de chaque scrape. Réutiliser un même contexte de navigateur entre pages liées évite aussi le coût de démarrage répété, et la requête la moins coûteuse reste celle que vous évitez en utilisant une API cachée.
Comment ajouter la gestion des erreurs et les tentatives ?
Enveloppez chaque navigation dans une boucle de tentatives avec un backoff exponentiel pour qu’un seul chargement lent ou un blocage temporaire ne ruine pas toute l’exécution. Les pages dynamiques échouent par intermittence, et un scraper qui abandonne à la première expiration de délai perdra une grande part des enregistrements à grande échelle.
Interceptez le timeout que Playwright déclenche, attendez un intervalle croissant et réessayez un nombre limité de fois avant d’enregistrer l’échec et de passer à la suite :
import time
from playwright.sync_api import TimeoutError as PlaywrightTimeout
def fetch_with_retries(page, url, selector, retries=3, backoff=2):
for attempt in range(1, retries + 1):
try:
page.goto(url, wait_until="domcontentloaded", timeout=30000)
page.wait_for_selector(selector, timeout=15000)
return page.query_selector_all(selector)
except PlaywrightTimeout:
wait = backoff ** attempt
print(f"Attempt {attempt} timed out, retrying in {wait}s")
time.sleep(wait)
raise RuntimeError(f"Failed to load {url} after {retries} attempts")
Associez les nouvelles tentatives à la rotation des proxies pour que chaque essai sorte par une IP fraîche. Si une IP de sortie est limitée en débit, la requête suivante arrive sur une autre adresse et réussit souvent. Un pool rotatif de proxies résidentiels ou de proxies mobiles attribue automatiquement une nouvelle IP par requête, ce qui permet souvent de surmonter les blocages persistants avec une simple nouvelle tentative. Pour gérer précisément les échecs au niveau du proxy, notre note sur l’erreur HTTP 407 couvre la réponse d’authentification la plus courante.
Comment scraper de nombreuses pages dynamiques en parallèle ?
Utilisez l’API asynchrone de Playwright avec un sémaphore asyncio pour que plusieurs pages se rendent à la fois sans saturer votre machine ni la cible. La concurrence permet de préserver un bon débit en scraping dynamique, puisque chaque page de navigateur passe l’essentiel de son temps à attendre le réseau.
Le sémaphore plafonne le nombre de pages exécutées en parallèle. Partagez un même navigateur et un même contexte entre les tâches, ouvrez une page par URL et rassemblez les résultats :
import asyncio
from playwright.async_api import async_playwright
PROXY = {
"server": "http://gw.dataimpulse.com:823",
"username": "USERNAME",
"password": "PASSWORD",
}
async def scrape_one(context, url, sem):
async with sem:
page = await context.new_page()
try:
await page.goto(url, wait_until="domcontentloaded")
await page.wait_for_selector("div.product-card", timeout=15000)
cards = await page.query_selector_all("div.product-card")
return [await c.inner_text() for c in cards]
finally:
await page.close()
async def main(urls):
sem = asyncio.Semaphore(5) # at most 5 pages at once
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True, proxy=PROXY)
context = await browser.new_context()
tasks = [scrape_one(context, u, sem) for u in urls]
results = await asyncio.gather(*tasks)
await browser.close()
return results
urls = ["https://example.com/products?page=%d" % i for i in range(1, 21)]
data = asyncio.run(main(urls))
Gardez une limite de concurrence modeste. Un trop grand nombre de pages en parallèle épuise la mémoire et déclenche des limites de débit, ce qui entraîne plus de nouvelles tentatives que le parallélisme ne fait gagner de temps. Commencez autour de cinq et ne l’augmentez que tant que les taux de réussite restent élevés.
Comment exporter les données extraites en JSON et CSV ?
Rassemblez les résultats de chaque page dans une liste de dictionnaires, puis écrivez cette liste en JSON pour les données imbriquées, ou en CSV pour des tableaux plats faciles à utiliser dans un tableur. Séparer l’extraction et l’export rend le scraper plus facile à tester et à relancer.
import json
import csv
records = [
{"name": "Laptop A", "price": "999"},
{"name": "Laptop B", "price": "1299"},
]
# Export to JSON
with open("products.json", "w", encoding="utf-8") as f:
json.dump(records, f, ensure_ascii=False, indent=2)
# Export to CSV
with open("products.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=["name", "price"])
writer.writeheader()
writer.writerows(records)
Choisissez JSON lorsque les enregistrements contiennent des listes ou des objets imbriqués, et CSV lorsque chaque enregistrement partage les mêmes champs plats et que les analystes devront les ouvrir dans un tableur. Normalisez vos champs avant l’écriture pour que chaque enregistrement ait les mêmes clés, et l’étape d’export reste triviale quelle que soit la façon dont la page dynamique a été rendue. Ce même JSON peut ensuite alimenter une base de données ou un traitement analytique sans transformation supplémentaire.

Questions fréquentes
Comment savoir si une page est dynamique avant d’écrire un scraper ?
Comparez le code source avec le panneau Elements des outils de développement, ou comptez un élément cible dans le HTML brut par rapport au DOM rendu. Si les données n’apparaissent qu’après l’exécution de JavaScript, la page est dynamique et nécessite un appel d’API ou un navigateur headless.
Vaut-il mieux appeler l’API cachée ou utiliser un navigateur headless ?
Appelez l’API cachée quand vous pouvez la trouver, car elle renvoie du JSON propre à une fraction du coût et plus vite que le rendu. N’utilisez un navigateur headless que lorsqu’aucune API reproductible n’existe ou que les données dépendent de scripts complexes côté client.
Dois-je utiliser Playwright ou Selenium pour le scraping dynamique ?
Playwright est le choix moderne par défaut grâce à une attente automatique fiable et un support asynchrone intégré, raison pour laquelle les nouveaux projets commencent souvent avec lui. Selenium convient bien lorsque votre stack l’utilise déjà ou que vous avez besoin de son support de langages plus large.
Comment réduire la consommation de bande passante via le proxy avec un navigateur headless ?
Interceptez les requêtes et abandonnez les images, les polices, les médias et les ressources d’analytics dont vous n’avez pas besoin. Cela peut réduire fortement le trafic puisque le coût du proxy se mesure au gigaoctet, tout en accélérant chaque chargement de page.
Comment scraper du contenu qui se charge en scroll infini ?
Vérifiez d’abord dans l’onglet Network l’appel XHR paginé derrière le défilement puis itérez directement sur cet endpoint. Si vous devez faire défiler, faites-le dans une boucle contrôlée et attendez que chaque nouveau lot soit rendu avant de le lire.
Quand DataImpulse n’est-il pas le bon choix ?
Si vous avez besoin de proxies ISP statiques, d’une API de scraping entièrement gérée ou d’un accès à des sites bancaires et gouvernementaux, DataImpulse n’est pas le bon outil. Il se concentre sur les proxies résidentiels, mobiles et datacenter rotatifs pour la collecte de données publiques et l’accès à des contenus.
Scrapez des pages dynamiques avec des proxies fiables
Si votre scraper a besoin d’IP résidentielles, mobiles ou datacenter qui se comportent comme de vrais visiteurs, vous pouvez démarrer avec du trafic à l’usage à partir de 1 dollar par Go. Créez un compte DataImpulse et routez votre scraping dynamique via des sessions rotatives ou sticky.
