Une grande partie des données qui valent la peine d'être collectées sur le web moderne n'apparaît jamais dans le code source de la page. Une grille de produits qui se remplit au défilement, un tableau qui se met à jour quand vous changez un filtre, un tableau de bord qui charge ses chiffres un instant après la mise en page : tous ces éléments utilisent AJAX (Asynchronous JavaScript and XML) pour récupérer du contenu en arrière-plan et l'injecter dans une page déjà chargée. Cela offre une expérience fluide, et cela casse discrètement les scrapers les plus simples.
Ce guide vous montre comment scraper des données depuis des sites web pilotés par AJAX avec Python. Vous construisez un petit scraper fonctionnel qui rend la page via la Crawling API, attend que le contenu asynchrone arrive, capture les données que la page charge via XHR, les analyse et exporte un JSON et un CSV propres. La démonstration utilise un exemple de listing neutre afin que vous puissiez suivre la mécanique, puis appliquer le même flux à votre propre cible.
Ce que vous allez construire
Un script Python qui récupère une page AJAX via Crawlbase, lit les données que la page a chargées de manière asynchrone et transforme chaque article en un enregistrement structuré. L'exemple utilisé est un listing public générique où chaque carte contient un nom, un prix et une catégorie. Nous extrayons ces champs par article :
- Nom le titre affiché sur chaque carte de listing.
- Prix le prix numérique rendu pour l'article.
- Catégorie le groupe ou l'étiquette auquel appartient l'article.
- Lien l'URL de la page de détail de l'article.
Vous verrez deux chemins vers les mêmes données : répliquer l'appel AJAX directement et rendre la page complète, les deux aboutissant à la même étape d'export.
Pourquoi une requête simple échoue sur les pages AJAX
Faites une requête vers une URL pilotée par AJAX avec un client HTTP basique et vous obtenez un statut 200 en retour mais quasiment aucune des données que vous cherchez. La raison est liée au timing. Le serveur envoie un squelette HTML léger, le navigateur exécute le JavaScript de la page, et seulement à ce moment-là le script déclenche les requêtes en arrière-plan (les appels AJAX) qui retournent le vrai contenu et l'injectent dans le DOM. Un simple requests.get s'arrête au squelette : il n'exécute jamais le JavaScript, donc il ne déclenche jamais les appels de suivi, et le corps que vous analysez est principalement un gabarit vide.
Il existe deux façons honnêtes de contourner cela. La première consiste à trouver l'endpoint AJAX que la page appelle en arrière-plan et à le requêter directement, ce qui est rapide car vous évitez le rendu. La seconde consiste à rendre toute la page dans un vrai navigateur pour que le contenu asynchrone se charge, puis à analyser le HTML terminé, ce qui est le chemin à privilégier quand l'endpoint est signé ou difficile à reproduire. Le guide sur le crawling de sites web JavaScript couvre en profondeur le côté rendu.
Crawlbase propose deux types de tokens. Le token normal récupère du HTML statique ; le token JavaScript (JS) rend d'abord la page dans un vrai navigateur. Comme le contenu AJAX n'apparaît qu'après l'exécution des scripts, vous utilisez le token JS ici, associé à ajax_wait et page_wait pour que l'API attende que les appels en arrière-plan se terminent avant de capturer la page.
Prérequis
Quelques éléments doivent être en place avant d'écrire du code.
Bases de Python. Vous devez être à l'aise pour écrire et exécuter un script Python et installer des paquets avec pip. Si l'analyse est nouvelle pour vous, le guide BeautifulSoup se complète bien avec ce tutoriel.
Python 3.8 ou version ultérieure. Vérifiez votre version avec python --version. Si vous ne l'avez pas, installez-le depuis python.org et assurez-vous que Python est dans votre PATH.
Un compte Crawlbase et un token JS. Inscrivez-vous, ouvrez votre tableau de bord et copiez votre token JavaScript (JS). Crawlbase inclut 1 000 requêtes gratuites pour commencer, ce qui est largement suffisant pour ce guide. Traitez le token comme un mot de passe et ne le mettez pas dans le contrôle de version.
Configurer le projet
Créez un environnement virtuel pour que les dépendances du projet restent isolées, puis installez les bibliothèques dont le scraper a besoin.
python --version python -m venv ajax_env source ajax_env/bin/activate pip install crawlbase beautifulsoup4
Sous Windows, activez l'environnement avec ajax_env\Scripts\activate à la place de la ligne source. Deux dépendances font le travail : crawlbase est le client officiel pour la Crawling API, et beautifulsoup4 analyse le HTML retourné quand vous choisissez la route de la page rendue. json et csv sont fournis avec la bibliothèque standard, il n'y a donc rien de plus à installer pour l'étape d'export.
Étape 1 : Identifier la requête AJAX
Avant tout code, trouvez l'appel en arrière-plan que la page effectue. Ouvrez la cible dans Chrome, faites un clic droit et choisissez Inspecter (ou appuyez sur Ctrl+Shift+I), et passez à l'onglet Réseau. Filtrez sur XHR, ce qui isole les appels XMLHttpRequest et fetch des images et feuilles de style, puis rechargez la page. Au fur et à mesure que le contenu se remplit, la requête qui l'a transporté apparaît. Cliquez dessus pour lire l'URL de la requête, ses paramètres de requête et le JSON qu'elle a retourné.
Pour l'exemple utilisé ici, la page charge ses articles depuis un endpoint JSON qui ressemble à ceci :
https://example.com/api/items?page=1&limit=20
Cet endpoint retourne les mêmes données que la page affiche, mais sous forme de JSON propre plutôt que de HTML rendu. Quand un appel de ce type existe et est accessible, le requêter directement est le chemin le plus simple. Quand il est signé, lié à une session ou autrement difficile à reproduire, vous rendez la page à la place. Les deux routes sont décrites ci-dessous.
Étape 2 : Récupérer l'endpoint AJAX via Crawlbase
Même un endpoint JSON propre peut limiter le débit ou bloquer le trafic automatisé depuis une IP de datacenter. Router l'appel via Crawlbase vous donne une IP de confiance et une rotation intégrée, de sorte que la requête ressemble à celle d'un visiteur réel. Importez la classe CrawlingAPI, initialisez-la avec votre token, et requêtez l'endpoint. Vérifier pc_status avant d'analyser permet de rendre les échecs visibles plutôt que silencieux.
import json from crawlbase import CrawlingAPI api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"}) endpoint = "https://example.com/api/items?page=1&limit=20" def fetch_json(url): response = api.get(url) if response["headers"]["pc_status"] == "200": return json.loads(response["body"].decode("utf-8")) print(f"Request failed: {response['headers']['pc_status']}") return None if __name__ == "__main__": data = fetch_json(endpoint) print(data if data else "No data returned")
Exécutez le script avec python ajax_scraper.py et vous devriez voir le JSON brut que la page aurait chargé dans le navigateur, récupéré en un seul appel sans rien rendre. Cela confirme que l'endpoint est accessible avant d'écrire une ligne d'analyse.
La récupération ci-dessus a atteint un endpoint AJAX sans que vous ayez à exécuter un navigateur ni gérer des IPs, ce qui est exactement ce que gère la Crawling API. Donnez-lui un token normal pour un endpoint JSON propre, ou le token JS avec ajax_wait et page_wait quand vous avez besoin que la page complète soit rendue. Elle fait tourner des IPs résidentielles côté serveur et retourne le contenu terminé, vous évitant d'exploiter une flotte sans tête et un pool de proxies. Pointez-la sur une page publique avec le niveau gratuit d'abord.
Étape 3 : Analyser la réponse JSON
L'endpoint retourne du JSON structuré, il n'y a donc pas de HTML à analyser. Parcourez l'objet jusqu'à la liste d'articles et extrayez les champs que vous voulez. Les noms de clés exacts dépendent de votre cible, donc inspectez la réponse de l'étape 1 et associez-les. Pour l'exemple, les articles se trouvent sous une clé items, chacun avec name, price, category et url.
def parse_items(data): records = [] for item in data.get("items", []): records.append({ "name": item.get("name"), "price": item.get("price"), "category": item.get("category"), "link": item.get("url"), }) return records
Utiliser dict.get au lieu de l'accès par crochets signifie qu'une clé manquante retourne None plutôt que de lever une KeyError, de sorte qu'un article malformé ne termine pas l'exécution. Passez le JSON de l'étape 2 dans parse_items et vous obtenez une liste ordonnée d'enregistrements, prêts à exporter.
Étape 4 : Rendre la page complète quand il n'y a pas d'endpoint propre
Parfois l'appel AJAX est signé, lié à un cookie de session ou réparti sur plusieurs requêtes, et le reproduire demande plus de travail que ça n'en vaut. Dans ce cas, rendez toute la page avec le token JS, laissez Crawlbase attendre le contenu asynchrone, puis analysez le balisage terminé avec BeautifulSoup comme pour n'importe quelle page statique.
from bs4 import BeautifulSoup RENDER_OPTIONS = { "ajax_wait": "true", "page_wait": 5000, } def fetch_rendered(url): response = api.get(url, RENDER_OPTIONS) if response["headers"]["pc_status"] == "200": return response["body"].decode("utf-8") print(f"Request failed: {response['headers']['pc_status']}") return None def parse_cards(html): soup = BeautifulSoup(html, "html.parser") records = [] for card in soup.select("div.item-card"): link = card.select_one("a.item-link") records.append({ "name": text_of(card, "h2.item-name"), "price": text_of(card, "span.item-price"), "category": text_of(card, "span.item-category"), "link": link["href"] if link else None, }) return records
Les deux options d'attente portent la charge ici. ajax_wait indique à l'API d'attendre que le contenu asynchrone finisse de se charger, et page_wait ajoute une pause fixe en millisecondes après le chargement pour que les cartes à rendu tardif apparaissent avant la capture. Cinq secondes est un point de départ raisonnable ; augmentez si les articles reviennent incomplets. Le helper parse_cards lit ensuite chaque div.item-card et associe les mêmes quatre champs, de sorte que sa sortie corresponde exactement à celle de parse_items. Le helper text_of utilisé ici est défini dans le script complet ci-dessous.
Étape 5 : Gérer la pagination et assembler le script
Une seule page est rarement l'ensemble du jeu de données. La plupart des listings AJAX paginent via un paramètre de requête (ici page), donc vous bouclez sur les numéros de page, collectez des enregistrements de chacune, et vous arrêtez quand une page revient vide. Reliez cette boucle avec les étapes de récupération, d'analyse et d'export en un script fonctionnel.
import csv import json import time from crawlbase import CrawlingAPI from bs4 import BeautifulSoup api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"}) BASE = "https://example.com/api/items?limit=20&page=" def fetch_json(url): response = api.get(url) if response["headers"]["pc_status"] == "200": return json.loads(response["body"].decode("utf-8")) print(f"Request failed: {response['headers']['pc_status']}") return None def text_of(node, selector): el = node.select_one(selector) return el.get_text(strip=True) if el else None def parse_items(data): records = [] for item in data.get("items", []): records.append({ "name": item.get("name"), "price": item.get("price"), "category": item.get("category"), "link": item.get("url"), }) return records def collect_all(max_pages=5): all_records = [] for page in range(1, max_pages + 1): data = fetch_json(f"{BASE}{page}") if not data: break records = parse_items(data) if not records: break all_records.extend(records) time.sleep(2) return all_records def save_outputs(records): with open("items.json", "w") as f: json.dump(records, f, indent=2) if not records: return with open("items.csv", "w", newline="") as f: writer = csv.DictWriter(f, fieldnames=records[0].keys()) writer.writeheader() writer.writerows(records) def main(): records = collect_all(max_pages=5) save_outputs(records) print(f"Saved {len(records)} items") if __name__ == "__main__": main()
Le script parcourt jusqu'à cinq pages de l'endpoint AJAX, analyse chacune en enregistrements, s'arrête dès qu'une page ne retourne rien et rythme la boucle avec une pause de deux secondes. save_outputs écrit à la fois un fichier JSON et un CSV, en utilisant les clés du premier enregistrement comme en-tête. Si votre cible n'a pas d'endpoint propre, remplacez le couple fetch_json et parse_items par le couple fetch_rendered et parse_cards de l'étape 4 ; l'étape d'export ne change pas.
À quoi ressemble la sortie
Exécutez le script complet avec python ajax_scraper.py et vous obtenez un enregistrement structuré propre par article, prêt pour l'analyse, une base de données ou un tableur.
[ { "name": "Wireless Keyboard", "price": "49.00", "category": "Accessories", "link": "https://example.com/items/wireless-keyboard" }, { "name": "Standing Desk", "price": "299.00", "category": "Furniture", "link": "https://example.com/items/standing-desk" } ]
Le CSV correspondant porte les mêmes colonnes, une ligne par article, ce qui s'intègre directement dans pandas ou tout tableur pour filtrer par tranche de prix ou par catégorie. Pour aller plus loin dans l'analyse, utiliser pandas pour analyser les données prend le relais là où cet export s'arrête, et JSON vs CSV explique quel format convient à quelle utilisation.
Rester non bloqué à grande échelle
Même avec le rendu et une IP de confiance gérés, une cible AJAX surveille le trafic qui ressemble à celui d'un scraper. Quelques habitudes maintiennent une exécution plus longue en bonne santé.
- Rythmez vos requêtes. Envoyer des appels dans une boucle serrée est le moyen le plus rapide d'être throttlé. La pause de deux secondes ci-dessus est un plancher, pas un plafond ; élargissez-la pour les tâches plus importantes.
- Misez sur la rotation. Un pool d'adresses IP résidentielles répartit les requêtes sur de nombreuses adresses d'utilisateurs réels afin qu'aucune ne déclenche une limite de débit. La Crawling API gère cela pour vous.
-
Lisez les codes de statut. Une exécution qui commence à retourner des valeurs
pc_statusdifférentes de 200 vous indique que le taux ou le niveau d'IP actuel n'est plus suffisant. Traitez cela comme un signal pour reculer.
Pour les crawls plus importants, le Crawler asynchrone met les requêtes en file d'attente et livre les résultats via un webhook, ce qui convient à l'exécution de nombreuses pages AJAX sans maintenir de connexions ouvertes. Pour le manuel général, consultez comment scraper des sites web sans être bloqué et scraper des pages JavaScript avec Python.
Scraper de manière responsable
Gardez ce travail limité aux données publiques et traitez les règles de la cible comme la frontière à ne pas franchir. Lisez les conditions d'utilisation du site et son robots.txt avant de pointer un scraper dessus, et ne collectez que les données que tout visiteur peut voir sans compte. Rythmer vos requêtes pour ne pas surcharger le serveur, et ne touchez jamais à quoi que ce soit derrière une connexion ni à toute tentative de contournement de l'authentification. Quand les données impliquent des personnes identifiables, la loi sur la vie privée comme le RGPD ou le CCPA s'applique, donc évitez les données personnelles ou de contact à moins d'avoir une base légale claire pour les collecter. Si une cible offre une API officielle pour les données dont vous avez besoin, c'est généralement le chemin plus propre et plus durable que le scraping de la page rendue.
Points clés
- Le contenu AJAX se charge après le squelette. Une requête simple s'arrête au HTML initial et n'exécute jamais les scripts qui récupèrent les vraies données, donc le corps que vous analysez est principalement vide.
- Deux routes mènent aux mêmes données. Répliquez directement l'endpoint XHR en arrière-plan pour la rapidité, ou rendez la page complète avec le token JS quand l'endpoint est signé ou difficile à reproduire.
-
Attendez le contenu. Sur la route rendue,
ajax_waitetpage_waitmaintiennent la pause pour que les appels asynchrones se terminent avant que Crawlbase capture la page. - Normalisez puis exportez. Associez les deux routes à la même structure d'enregistrement, paginez via le paramètre de requête et écrivez les résultats en JSON et CSV depuis une seule fonction.
- Scrapez de manière responsable. Respectez les conditions d'utilisation et le robots.txt, restez sur les données publiques, rythmer les requêtes et appliquez les règles RGPD ou CCPA quand des données personnelles sont impliquées.
Foire aux questions
Qu'est-ce qu'AJAX et pourquoi rend-il le scraping plus difficile ?
AJAX (Asynchronous JavaScript and XML) est une technique qui permet à une page de récupérer du contenu en arrière-plan et de mettre à jour une partie du DOM sans recharger. Elle rend le scraping plus difficile car les données ne sont pas dans le HTML initial ; elles n'arrivent qu'après que le navigateur a exécuté le JavaScript de la page et que les appels en arrière-plan ont retourné. Une requête HTTP simple n'exécute jamais ce JavaScript, elle capture donc un squelette léger avec le vrai contenu manquant.
Puis-je scraper du contenu AJAX sans rendre un navigateur ?
Souvent, oui. Filtrez l'onglet Réseau de vos outils de développement sur XHR et trouvez la requête qui transporte les données. Si cet endpoint est accessible, vous pouvez le requêter directement et analyser le JSON qu'il retourne, ce qui est plus rapide que de rendre la page. Quand l'endpoint est signé, lié à une session ou réparti sur plusieurs appels, le rendu avec un token JS est le chemin le plus fiable.
Ai-je besoin du token normal ou du token JS ?
Cela dépend de la route. Pour un endpoint JSON propre trouvé dans l'onglet Réseau, le token normal suffit car il n'y a rien à rendre. Pour charger une page complète dont le contenu n'apparaît qu'après l'exécution des scripts, utilisez le token JS avec ajax_wait et page_wait pour que Crawlbase attende que les appels asynchrones se terminent avant de capturer le HTML.
Que font réellement ajax_wait et page_wait ?
ajax_wait indique à l'API d'attendre que les requêtes asynchrones de la page aient fini de se charger, plutôt que de capturer au moment où le HTML initial arrive. page_wait ajoute une pause fixe en millisecondes après le chargement, ce qui couvre le contenu qui se rend avec un léger décalage. Cinq secondes est un bon point de départ ; augmentez si les articles reviennent incomplets et réduisez une fois que vous confirmez que la page se stabilise plus vite.
Ma liste analysée est vide. Qu'est-ce qui ne va pas ?
Vérifiez trois choses dans l'ordre. Premièrement, confirmez que pc_status a retourné 200 ; une valeur différente de 200 signifie que la requête a échoué. Deuxièmement, sur la route de l'endpoint, ré-inspectez les clés JSON, car elles peuvent différer des noms utilisés dans l'exemple. Troisièmement, sur la route rendue, augmentez page_wait et vérifiez vos sélecteurs CSS sur la page en direct, car les noms de classe sur le balisage généré changent sans préavis.
Comment passer à l'échelle sur de nombreuses pages ?
Bouclez sur le paramètre de pagination et arrêtez-vous quand une page ne retourne plus d'articles, comme le fait la fonction collect_all ci-dessus, en maintenant une courte pause entre les requêtes. Pour les tâches importantes, passez au Crawler asynchrone pour que les requêtes soient mises en file d'attente et que les résultats arrivent via un webhook plutôt que de maintenir des connexions ouvertes, et misez sur la rotation d'IPs intégrée pour qu'aucune adresse unique ne déclenche une limite de débit.
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.
