Comment scraper n'importe quel site avec Python en 2026

Un workflow /scrape général : récupération statique par défaut, escalade vers le rendu JS et les proxies premium si nécessaire, puis parsing avec BeautifulSoup.

La plupart des guides de scraping sont écrits pour une seule cible : Google, Amazon, un marketplace précis. Celui-ci est pour l’autre cas, une URL que vous ne contrôlez pas et que vous n’avez jamais scrapée, où vous devez obtenir le HTML de façon fiable puis trouver vous-même comment le parser.

La requête de base

Un endpoint, un paramètre requis :

import requests

API_KEY = "faf_live_..."  # votre clé FastAF

resp = requests.get(
    "https://api.fastaf.co/v1/scrape",
    params={"url": "https://example.com/product/123"},
    headers={"Authorization": f"Bearer {API_KEY}"},
    timeout=60,
)
resp.raise_for_status()
html = resp.text

Le corps de la réponse est la page elle-même, avec son content type (HTML par défaut, markdown avec formats=markdown), et non un wrapper JSON.

Par défaut, il s’agit d’une récupération statique, sans navigateur, sans rendu, acheminée via une infrastructure conçue pour ne pas se faire bloquer en le faisant. C’est aussi le palier le moins cher : 1 crédit.

Quand avez-vous besoin de plus que le défaut ?

Escaladez dans l’ordre, seulement aussi loin que la cible l’exige réellement :

  1. Statique, 1 crédit. Commencez toujours ici. Fonctionne pour les pages rendues côté serveur, la plupart des blogs, les sites de documentation, et toute page où le contenu voulu est dans la réponse HTML initiale.
  2. dynamic=true, 5 crédits. La page construit son contenu avec JavaScript après le chargement, courant sur les sites en React/Vue/Svelte et les listes à défilement infini. Notre article sur le rendu JS explique exactement comment savoir dans quel cas vous êtes avant de dépenser les crédits supplémentaires.
  3. premium=true, 10 crédits. La récupération statique se fait bloquer ou reçoit une page dégradée, non par manque de JavaScript mais à cause de défenses basées sur l’IP. Premium achemine la requête via une infrastructure de proxies de meilleure qualité.
  4. Les deux ensemble, 25 crédits. La cible rend à la fois côté client et se défend agressivement contre les IP automatisées. Réservez ce palier aux sites où vous avez confirmé avoir besoin des deux, pas comme réglage par défaut « au cas où ».
def scrape(url, dynamic=False, premium=False, wait=None):
    params = {"url": url}
    if dynamic:
        params["dynamic"] = "true"
    if premium:
        params["premium"] = "true"
    if wait:
        params["wait"] = wait
    resp = requests.get(
        "https://api.fastaf.co/v1/scrape",
        params=params,
        headers={"Authorization": f"Bearer {API_KEY}"},
        timeout=90,
    )
    resp.raise_for_status()
    return resp.text

wait (en millisecondes) compte surtout sur les requêtes dynamic=true pour les pages qui déclenchent un appel API après le chargement et rendent le résultat un instant plus tard ; sans cela, vous pouvez obtenir l’état de chargement au lieu du contenu.

Extraire vos données

C’est la partie qui vous revient quel que soit le palier utilisé. /scrape vous procure le même HTML qu’un navigateur afficherait pour cette URL, de façon fiable et sans se faire bloquer. Il ne sait pas à quoi ressemble un « prix » ou un « titre de produit » sur votre cible spécifique, parce que chaque site les balise différemment. C’est un travail différent de /serp, /maps ou /amazon, où nous connaissons déjà la structure de la page et vous fournissons des champs parsés ; ici, la récupération est résolue, le parsing reste à votre charge :

from bs4 import BeautifulSoup

html = scrape("https://example.com/product/123")
soup = BeautifulSoup(html, "html.parser")

title = soup.select_one("h1.product-title")
price = soup.select_one("span.price")

print(title.get_text(strip=True) if title else None)
print(price.get_text(strip=True) if price else None)

Écrivez vos sélecteurs contre le site cible de la même façon que vous le feriez contre du HTML récupéré vous-même. Si ce site se refait une beauté, vos sélecteurs cassent, comme pour tout scraper ; ce que vous avez retiré du problème, c’est le blocage et le besoin d’une ferme de navigateurs pour récupérer la page en premier lieu.

Erreurs courantes

Utiliser dynamic=true par défaut pour tout. C’est un coût 5 fois supérieur en crédits par rapport au statique, et la plupart des pages n’en ont pas besoin. Vérifiez d’abord avec la méthode de l’article sur le rendu JS plutôt que de payer la prime par défaut.

Ne pas régler wait sur les pages dynamiques lentes à charger. Si le contenu se charge via un appel API différé, l’attente par défaut peut capturer la page avant qu’elle soit prête. Si des champs reviennent systématiquement vides avec dynamic=true, c’est la première chose à vérifier.

Traiter toute réponse autre que 200 comme un scrape raté. Une cible qui répond 404 ou 410 revient en HTTP 422 target_unavailable et ne coûte rien. Un mur de connexion ou une page d’erreur “douce” revient en revanche comme un 200 normal avec cette page dans le body, vérifiez donc html pour voir ce qui a réellement été renvoyé.

Parser avant de vérifier que html n’est pas vide. Une réponse bloquée ou vide passée directement dans BeautifulSoup échoue à l’intérieur du parser au lieu de l’endroit où le vrai problème est visible. Vérifiez d’abord la réponse.

Coder en dur des sélecteurs sans repli. Un simple .select_one() qui renvoie None après une refonte devrait être un signal que vous vérifiez, pas une exception qui tue tout le lot.

En bref

Commencez en statique, à 1 crédit. Escaladez vers dynamic=true uniquement quand vous avez confirmé que le contenu est rendu en JavaScript, vers premium=true uniquement quand vous êtes bloqué au niveau IP, et vers les deux uniquement quand une cible en a vraiment besoin. La récupération est la partie que FastAF résout ; parser le résultat avec BeautifulSoup, les sélecteurs et la gestion d’erreurs pour votre cible spécifique reste la partie que vous écrivez.