requests + BeautifulSoup contre une API de scraping : quand le fait maison suffit

Pour les sites statiques sans anti-bot, requests et BeautifulSoup ne coûtent rien et fonctionnent bien. Voici exactement où cela cesse d'être vrai.

Il existe une version de cet article qui essaie de vous vendre une API pour un problème que requests et BeautifulSoup résolvent déjà. Ce n’est pas cet article-ci. Si le site que vous scrapez est du HTML statique sans anti-bot devant lui, la voie du fait maison ne coûte rien et fonctionne bien, et vous ne devriez rien payer pour cela.

Quand le fait maison suffit vraiment

Disons-le clairement : pour une page statique, un script, et une tâche peu fréquente, c’est toute la solution.

import requests
from bs4 import BeautifulSoup

resp = requests.get("https://example.com/products", timeout=30)
resp.raise_for_status()

soup = BeautifulSoup(resp.text, "html.parser")
for card in soup.select(".product-card"):
    name = card.select_one(".product-name").get_text(strip=True)
    price = card.select_one(".price").get_text(strip=True)
    print(name, price)

Aucune dépendance au-delà de deux bibliothèques bien connues, aucune facture de proxy, aucun crédit dépensé. C’est la bonne approche pour : un site qui rend le HTML complet côté serveur, sans CAPTCHA ni blocage par IP après quelques requêtes, et une tâche que vous exécutez occasionnellement plutôt qu’en continu sur des milliers d’URL. Un script ponctuel pour un projet scolaire, un outil interne qui vérifie une poignée de pages par jour, une surveillance de prix personnelle sur quelques pages produit : tout cela relève du fait maison, et ajouter une API devant n’est que de la charge supplémentaire.

Là où ça cesse de fonctionner

Quatre choses cassent cette configuration, et elles cassent indépendamment, ce qui explique pourquoi « ajoutez juste des proxies » ou « ajoutez juste un wait() » ne fait jamais que corriger un symptôme :

Anti-bot. Défis Cloudflare, PerimeterX, limitation de débit qui escalade vers un CAPTCHA ou un 403 sec après une poignée de requêtes. requests n’a pas d’empreinte de navigateur, pas de comportement de cookies qui imite une vraie session, et aucun moyen de résoudre un défi. Ce n’est pas un problème d’en-têtes que l’on corrige en copiant une chaîne User-Agent ; les sites qui font attention à cela détectent spécifiquement l’absence d’un vrai navigateur.

Rendu JS. Si les données que vous voulez sont injectées par JavaScript après le chargement du HTML initial (une application React ou Vue, du contenu chargé via un appel XHR en arrière-plan, un défilement infini), resp.text ne les contient tout simplement pas. BeautifulSoup analyse ce que le serveur a envoyé, pas ce que le navigateur afficherait. C’est la raison la plus courante pour laquelle un scraper qui fonctionnait se met soudain à renvoyer des résultats vides du jour au lendemain : le site n’a pas changé son blocage, il a changé son rendu.

Volume. Une douzaine de pages par jour, c’est un script. Dix mille pages par jour, c’est de l’infrastructure : gestion de la concurrence, logique de réessai, un pool de proxies dimensionné au volume de requêtes, surveillance de la hausse du taux de bannissement. Rien de tout cela n’est difficile individuellement, mais ensemble c’est un petit bout d’infrastructure que vous possédez désormais et devez maintenir en marche.

Maintenance. Les sélecteurs pourrissent. Une refonte de site change .product-card en .product-tile et votre parser renvoie silencieusement du vide, ou pire, renvoie le mauvais champ associé au bon nom. C’est le coût qui n’apparaît pas la première semaine et qui ne disparaît jamais complètement.

Le tableau de décision

Signal Fait maison (requests + BeautifulSoup) API de scraping
HTML statique, pas de blocage, faible volume Oui Excessif
Tâche occasionnelle / ponctuelle Oui Excessif
Le site renvoie 403/CAPTCHA après quelques requêtes Non Oui (premium=true)
Contenu chargé via JavaScript après le chargement Non Oui (dynamic=true)
Des milliers de pages, sans surveillance, planifiées Pénible à maintenir Oui
Le parser doit survivre aux refontes sans que vous vous en aperceviez Aucune garantie dans un cas comme dans l’autre Les champs JSON structurés réduisent (sans l’éliminer) ce risque

Quand vous franchissez la limite

Sur FastAF, /scrape coûte 1 crédit pour une page statique, exactement le cas ci-dessus mais sans que vous exécutiez la requête vous-même :

import requests

API_KEY = "faf_live_..."

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

Le même appel avec dynamic=true (5 crédits) rend d’abord la page avec un vrai navigateur, de sorte que le contenu injecté par JavaScript est présent dans la réponse. premium=true (10 crédits) passe par un réseau résidentiel pour les sites qui bloquent sur la réputation IP. Les deux ensemble (25 crédits) couvrent une page rendue en JS qui doit en plus ressembler à un vrai visiteur résidentiel, la combinaison la plus difficile et la plus coûteuse, à réserver aux cas où le site l’exige réellement.

L’intérêt des paliers de crédits est que vous payez exactement pour le problème que vous avez. Une page statique ne devrait jamais coûter plus de 1 crédit, et payer pour premium=true sur un site sans anti-bot, c’est de l’argent dépensé pour un problème que vous n’avez pas.

En bref

Commencez avec requests et BeautifulSoup. C’est gratuit et cela suffit pour une grande partie des tâches de scraping. Passez à une API précisément quand vous rencontrez l’un des quatre modes d’échec ci-dessus, anti-bot, rendu JS, volume, ou une charge de maintenance que vous ne voulez pas porter, et payez par URL exactement pour ce problème-là plutôt que de construire un pool de proxies et une habitude de maintenance de parsers autour. L’offre gratuite vous donne 500 crédits sans carte si vous voulez tester où se situent vos cibles réelles sur ce tableau.