Scraper une seule page d'une application web moderne est un problème. Crawler tout le site en est un autre, plus difficile que la plupart des tutoriels ne l'admettent. Quand vous vous lancez dans le crawling de sites JavaScript construits avec React, Vue, Angular ou tout framework qui remplit la page dans le navigateur, vous rencontrez deux obstacles qui se combinent. Chaque page ne montre son vrai contenu qu'après l'exécution de JavaScript, et la navigation que vous suivriez normalement pour découvrir davantage de pages est elle-même dessinée par JavaScript, donc une simple requête HTTP vous remet un document presque vide sans aucun lien à parcourir.

Ce guide vous montre comment construire un crawler fonctionnel qui parcourt un site rendu en JavaScript de bout en bout. Vous rendrez chaque page pour que ses liens et son contenu apparaissent, analyserez ces liens avec BeautifulSoup, maintiendrez une file d'attente de frontière et un ensemble de visités pour que le parcours se termine, et throttlerez poliment pour rester les bienvenus. La récupération est effectuée via la Crawling API de Crawlbase avec un token JavaScript, qui rend chaque page derrière une IP de confiance et renvoie du HTML fini. Pour les grands travaux, nous couvrons également le Crawler asynchrone pour que vous ne bloquiez pas sur chaque rendu.

Pourquoi crawler un site JS est deux problèmes, pas un

Un crawler traditionnel est une boucle serrée : récupérer une URL, extraire les ancres, pousser les nouvelles dans une file d'attente, répéter. Cette boucle suppose que le HTML que vous récupérez contient déjà à la fois le contenu et les liens. Sur un site rendu côté serveur, c'est le cas. Sur un site rendu côté client, non.

Le premier problème est le rendu. Quand vous demandez une route React ou Vue avec un simple client HTTP, le serveur renvoie une coquille : un <div> racine, un ensemble de balises script, et presque rien d'autre. Le texte de l'article, la grille de produits, le tableau que vous vouliez, tout est injecté après que le navigateur télécharge et exécute le JavaScript. Pas de navigateur, pas de contenu.

Le deuxième problème est la découverte de liens, et c'est celui qui casse silencieusement les crawlers naïfs. La navigation du site, la pagination et les liens « associés » sont souvent rendus côté client aussi. Donc même si vous vouliez seulement les liens et pas le contenu, une simple requête vous donne toujours rien à suivre. Le crawl meurt sur la première page parce que la frontière ne grandit jamais au-delà. Pour crawler un site JavaScript, vous devez rendre chaque page, pas parce que vous avez toujours besoin du corps, mais parce que le rendu est ce qui fait exister les liens.

Rendez d'abord, puis analysez

La règle unique qui fait fonctionner le crawling JS : rendez chaque page avant de chercher des liens. Le contenu et la navigation apparaissent dans le même passage de rendu, donc une fois que vous avez du HTML fini, vous pouvez extraire à la fois les données que vous voulez et les URLs à suivre ensuite, avec le même analyseur.

Ce que vous allez construire

Un crawler en largeur d'abord en Python qui part d'une URL de graine sur un site rendu en JavaScript et avance vers l'extérieur, en restant dans un seul domaine. Concrètement il va :

  • Rendre chaque page via la Crawling API avec un token JS, pour que le contenu et les liens soient tous les deux présents.
  • Extraire les liens du HTML rendu avec BeautifulSoup et les normaliser en URLs absolues du même domaine.
  • Gérer une file d'attente de frontière d'URLs à visiter et un ensemble de visités pour que rien ne soit récupéré deux fois et que le parcours se termine.
  • Throttler poliment avec un délai entre les requêtes et un plafond sur le nombre de pages visitées.

Prérequis

Vous avez besoin de quelques éléments en place avant d'écrire du code. Aucun ne prend longtemps.

Python de base. Vous devez être à l'aise pour exécuter un script et installer des packages avec pip. Si les files d'attente et les ensembles vous sont familiers, vous êtes prêt.

Python 3.8 ou version ultérieure. Confirmez avec python --version. Installez depuis python.org si vous ne l'avez pas.

Un compte Crawlbase et un token JS. Inscrivez-vous, ouvrez votre tableau de bord et copiez votre token JavaScript (JS) depuis la page de documentation du compte. Le token JS est celui qui rend les pages dans un vrai navigateur ; le token normal ne récupère que le HTML statique et vous remettrait la même coquille vide qu'une simple requête. Gardez le token hors du contrôle de version.

Configurer le projet

Créez un environnement virtuel pour que les dépendances restent isolées, puis installez les deux bibliothèques dont le crawler a besoin.

bash
python --version

python -m venv crawler_env
source crawler_env/bin/activate

pip install crawlbase beautifulsoup4

Sur Windows, activez avec crawler_env\Scripts\activate à la place de la ligne source. Le package crawlbase est le client officiel pour la Crawling API, et beautifulsoup4 analyse le HTML renvoyé pour que vous puissiez extraire à la fois les ancres et le contenu.

Étape 1 : Rendre une seule page et confirmer que les liens apparaissent

Avant de construire la boucle, prouvez que la partie difficile fonctionne : que rendre une page côté client fait apparaître des liens qu'une simple requête manquerait. Initialisez le client avec votre token JS et demandez une URL, en demandant à l'API d'attendre le contenu asynchrone.

python
from crawlbase import CrawlingAPI

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

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

if __name__ == "__main__":
    html = render("https://example.com/")
    print(len(html) if html else "No HTML returned")

Les deux options d'attente importent pour les cibles rendues côté client. ajax_wait dit à l'API d'attendre la fin du chargement du contenu asynchrone, et page_wait retient pendant un nombre fixe de millisecondes après le chargement pour que les éléments à rendu tardif apparaissent avant la capture. Cinq secondes est un bon point de départ ; augmentez si les liens d'une page reviennent vides. Comparez la longueur de ce corps rendu par rapport à un simple requests.get sur la même URL et vous verrez généralement que la version rendue est bien plus grande, car la navigation et le contenu sont maintenant présents.

Étape 2 : Extraire et normaliser les liens

Avec du HTML rendu en main, extrayez les ancres et transformez-les en URLs absolues propres que vous pouvez comparer et mettre en file. Deux détails maintiennent le crawl sain : résolvez les hrefs relatifs par rapport à la page dont ils proviennent, et supprimez les fragments d'URL pour que /page et /page#section ne soient pas traités comme deux pages.

python
from urllib.parse import urljoin, urldefrag, urlparse
from bs4 import BeautifulSoup

def extract_links(html, base_url, domain):
    soup = BeautifulSoup(html, "html.parser")
    links = set()
    for a in soup.select("a[href]"):
        href = urljoin(base_url, a["href"])
        href, _ = urldefrag(href)
        parsed = urlparse(href)
        if parsed.scheme in ("http", "https") and parsed.netloc == domain:
            links.add(href)
    return links

La vérification du même domaine (parsed.netloc == domain) empêche le crawler de s'égarer vers des sites externes, ce qui est la différence entre crawler un site et essayer accidentellement de crawler tout le web. Renvoyer un set déduplique les liens trouvés plusieurs fois sur une seule page. Comme vous les avez extraits depuis du HTML rendu, ils incluent les liens que JavaScript a dessinés, ce qu'un crawler basé sur une simple requête aurait manqué.

Crawlbase Crawling API

Crawler un site JS signifie rendre chaque page derrière une IP de confiance, encore et encore, sans être bloqué. La Crawling API prend un token JS, exécute chaque page dans un vrai navigateur, fait tourner des IPs résidentielles côté serveur, et renvoie du HTML fini pour que le contenu et les liens soient tous les deux présents. Vous évitez de gérer une flotte de navigateurs sans interface et un pool de proxies vous-même. Commencez sur le forfait gratuit et pointez-la vers une URL de graine.

Étape 3 : Gérer la frontière et l'ensemble de visités

Maintenant le cœur de tout crawler : une frontière d'URLs en attente d'être visitées et un ensemble de visités des URLs déjà vues. Sans l'ensemble de visités, un vrai site plein de liens mutuels bouclerait indéfiniment ; sans un plafond de pages, un grand site fonctionnerait jusqu'à épuisement du budget. Les deux garde-fous appartiennent à chaque crawler que vous écrivez.

python
import time
from collections import deque

def crawl_site(seed_url, max_pages=50, delay=2.0):
    domain = urlparse(seed_url).netloc
    frontier = deque([seed_url])
    visited = set()
    pages = []

    while frontier and len(visited) < max_pages:
        url = frontier.popleft()
        if url in visited:
            continue
        visited.add(url)

        html = render(url)
        if not html:
            continue

        pages.append({"url": url, "html": html})
        print(f"[{len(visited)}] crawled {url}")

        for link in extract_links(html, url, domain):
            if link not in visited:
                frontier.append(link)

        time.sleep(delay)

    return pages

Un deque utilisé avec popleft vous donne un parcours en largeur d'abord, donc le crawler se déploie sur le site plutôt que de plonger profondément dans une branche. Marquer une URL comme visitée au moment où vous la dépoppez (pas après la récupération) signifie qu'une page qui échoue à se rendre est quand même comptée comme vue, donc une URL capricieuse ne peut pas piéger la boucle. Le plafond max_pages et le delay entre les requêtes sont vos deux leviers de politesse ; ajustez-les au site et à votre propre budget.

Respectez robots.txt

Avant de crawler à tout volume, lisez le robots.txt de la cible et honorez ses règles de désautorisation et de délai de crawl. Le urllib.robotparser de la bibliothèque standard Python peut vérifier une URL par rapport aux règles en quelques lignes. Un rythme poli et rester hors des chemins désautorisés est ce qui maintient un crawler le bienvenu plutôt que bloqué.

Étape 4 : Tout assembler

Reliez le renderer, l'extracteur de liens et la boucle de frontière en un seul script exécutable. Cette version extrait également le titre de la page depuis chaque page rendue pour que vous voyiez du vrai contenu revenir, preuve que le rendu fait son travail sur tout le parcours.

python
import json
import time
from collections import deque
from urllib.parse import urljoin, urldefrag, urlparse
from crawlbase import CrawlingAPI
from bs4 import BeautifulSoup

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

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

def extract_links(html, base_url, domain):
    soup = BeautifulSoup(html, "html.parser")
    links = set()
    for a in soup.select("a[href]"):
        href, _ = urldefrag(urljoin(base_url, a["href"]))
        parsed = urlparse(href)
        if parsed.scheme in ("http", "https") and parsed.netloc == domain:
            links.add(href)
    return links

def title_of(html):
    soup = BeautifulSoup(html, "html.parser")
    return soup.title.get_text(strip=True) if soup.title else None

def crawl_site(seed_url, max_pages=50, delay=2.0):
    domain = urlparse(seed_url).netloc
    frontier = deque([seed_url])
    visited = set()
    results = []

    while frontier and len(visited) < max_pages:
        url = frontier.popleft()
        if url in visited:
            continue
        visited.add(url)

        html = render(url)
        if not html:
            continue

        results.append({"url": url, "title": title_of(html)})
        print(f"[{len(visited)}] {url}")

        for link in extract_links(html, url, domain):
            if link not in visited:
                frontier.append(link)

        time.sleep(delay)

    return results

def main():
    pages = crawl_site("https://example.com/", max_pages=25)
    with open("crawl.json", "w") as f:
        json.dump(pages, f, indent=2)
    print(f"Crawled {len(pages)} pages")

if __name__ == "__main__":
    main()

Exécutez-le avec python crawler.py et vous verrez la frontière grandir à mesure que chaque page rendue apporte de nouveaux liens, puis rétrécir quand le plafond de pages est atteint. La sortie est un fichier JSON de chaque URL que le crawler a visitée avec son titre. Remplacez title_of par une vraie fonction d'extraction et vous avez un crawler de contenu complet. Si vous voulez un tutoriel plus approfondi sur l'analyse du corps d'une seule page rendue, voir comment scraper des pages JavaScript avec Python.

Mise à l'échelle avec le Crawler asynchrone

La boucle synchrone ci-dessus est parfaite pour des dizaines ou des centaines de pages faibles, mais elle a un plafond structurel : elle bloque sur chaque rendu. Chaque page attend que l'API finisse un rendu complet du navigateur avant que la prochaine requête ne démarre, donc un rendu de cinq secondes sur mille pages représente bien plus d'une heure de temps de paroi passé à attendre, la plupart du temps inactif.

Pour les travaux plus importants, passez au Crawler asynchrone. Au lieu de récupérer une page et d'attendre, vous poussez des URLs dans le Crawler, et Crawlbase les rend sur sa propre infrastructure et livre le HTML fini vers un callback webhook que vous contrôlez. Votre code cesse d'être une boucle de rendu-et-attente et devient deux moitiés découplées : un soumissionnaire qui alimente des URLs aussi vite que vous les découvrez, et un récepteur qui ingère les pages rendues, extrait des liens, et soumet les nouveaux en retour. Vous crawlez au débit de la flotte du Crawler, pas à la latence d'un seul rendu.

La logique de crawling que vous avez déjà écrite se reporte directement. La frontière, l'ensemble de visités, la vérification du même domaine et l'extraction de liens sont identiques ; seul le transport change d'un appel api.get bloquant à un flux de soumission-et-callback. Pour un schéma complet, voir extraire des données en utilisant le Crawlbase Crawler. Si votre pile est sur la JVM plutôt que Python, le même design frontière-et-visités se transpose proprement sur la construction d'un crawler web en Java.

Pièges courants lors du crawling de sites JS

Quelques modes d'échec apparaissent encore et encore. Les connaître à l'avance évite beaucoup de débogage.

  • Ensembles de liens vides. Si extract_links ne renvoie rien sur une page que vous savez avoir une navigation, la page n'avait probablement pas fini de se rendre. Augmentez page_wait, et gardez ajax_wait actif, pour que les ancres injectées tardivement soient présentes quand vous analysez.
  • Frontières infinies. Les calendriers, les filtres à facettes et les chaînes de requête d'ID de session génèrent des URLs uniques sans fin. Normalisez les paramètres de suivi et envisagez d'ignorer les URLs au-delà d'une limite de profondeur pour que le crawl se termine réellement.
  • Crawler hors site. Sans le garde du même domaine, un lien externe transforme votre crawl de site en une exécution incontrôlée. Filtrez toujours sur netloc.
  • Marteler le serveur. Pas de délai signifie une rafale de requêtes qui ressemble à une attaque et vaut un blocage. Gardez un delay raisonnable et respectez tout crawl-delay dans robots.txt.

Si vous préférez router votre propre trafic de navigateur sans interface à travers un pool résidentiel tournant au lieu d'utiliser l'API gérée, le Smart AI Proxy vous donne la même rotation d'IP comme endpoint proxy drop-in, et vous gérez le rendu vous-même.

Récapitulatif

Points clés

  • Crawler un site JS est deux problèmes. Chaque page a besoin de rendu pour montrer le contenu, et les liens que vous suivez sont également construits en JS, donc vous devez rendre chaque page pour découvrir les suivantes.
  • Rendez avant d'analyser. La Crawling API avec un token JS plus ajax_wait et page_wait renvoie du HTML fini, donc le contenu et les liens arrivent ensemble.
  • Une frontière et un ensemble de visités sont obligatoires. Une file d'attente en largeur d'abord, un ensemble d'URLs vues, un filtre du même domaine et un plafond de pages sont ce qui fait terminer le parcours.
  • Soyez poli. Retardez entre les requêtes, honorez robots.txt, et normalisez les URLs pour que le crawler ne boucle pas sur les paramètres de suivi.
  • Mettez à l'échelle avec le Crawler asynchrone. Pour les grands travaux, soumettez des URLs et recevez des pages rendues via callback pour crawler au débit de la flotte plutôt qu'en bloquant sur chaque rendu.

Foire aux questions

Pourquoi un crawler ordinaire s'arrête-t-il après la première page sur un site JavaScript ?

Parce que les liens de navigation sont rendus côté client. Une simple requête HTTP renvoie une coquille avec les scripts mais aucune des ancres que le framework dessine après son exécution, donc votre extracteur de liens ne trouve rien à mettre en file et la frontière ne grandit jamais. Rendre chaque page d'abord est ce qui fait exister ces liens, ce qui explique pourquoi crawler un site JS nécessite un rendu même quand vous vous intéressez seulement à la découverte d'URLs.

Ai-je besoin du token normal ou du token JS pour crawler un site JavaScript ?

Le token JS. Le token normal récupère le HTML statique, qui sur un site rendu côté client est la coquille vide sans contenu ni liens rendus. Le token JS exécute la page dans un vrai navigateur avant de renvoyer le HTML, donc les données et la navigation sont toutes les deux présentes pour votre analyseur et votre frontière.

Comment empêcher le crawler de boucler indéfiniment ?

Maintenez un ensemble de visités et vérifiez-le avant chaque récupération, et marquez une URL comme visitée au moment où vous la dépoppez de la frontière plutôt qu'après son succès. Ajoutez un plafond max_pages et un filtre du même domaine. Ensemble, ceux-ci garantissent que le parcours se termine même sur un site où chaque page pointe vers toutes les autres.

En quoi le crawling diffère-t-il du scraping d'une seule page JS ?

Scraper une seule page est un rendu plus une analyse pour les champs que vous voulez. Crawler est ce même rendu-et-analyse répété sur de nombreuses pages, plus la machinerie supplémentaire de découverte de liens, de mise en file, de déduplication et de rythme du parcours. La technique de rendu est partagée ; le crawling ajoute la frontière, l'ensemble de visités et les contrôles de politesse par-dessus.

Quand devrais-je utiliser le Crawler asynchrone au lieu d'une boucle synchrone ?

Passez au Crawler asynchrone quand le blocage sur chaque rendu devient le goulot d'étranglement, généralement une fois que vous crawlez plus de quelques centaines de pages. Au lieu d'attendre chaque rendu en séquence, vous soumettez des URLs et recevez des pages finies via des callbacks webhook, donc vous crawlez au débit de la flotte de Crawlbase plutôt qu'à la latence d'un rendu à la fois.

Comment crawler poliment sans être bloqué ?

Ajoutez un délai entre les requêtes, plafonnez le nombre de pages visitées par exécution, et lisez le robots.txt du site pour honorer ses règles de désautorisation et son crawl-delay. Routez les requêtes via des IPs résidentielles tournantes, que la Crawling API gère pour vous, pour qu'aucune adresse individuelle ne déclenche une limite de débit. Surveillez les codes de statut et ralentissez quand des défis commencent à apparaître.

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