Comment extraire des prix avec Python : un guide étape par étape (2026)
Tous les articles

Comment extraire des prix avec Python : un guide étape par étape (2026)

Ryan Turner
Ryan Turner · Head of Growth

Le « price scraping » avec Python consiste à écrire un script qui récupère la page d'un produit, identifie le prix dans le code HTML ou dans le résultat affiché, puis l'enregistre sous forme de données structurées que vous pouvez comparer ou suivre au fil du temps. La version la plus simple se résume à une requête HTTP et à un analyseur HTML. Les difficultés résident dans les mesures de protection anti-bot et les prix générés par JavaScript, et c’est sur ces aspects que porte l’essentiel de ce guide.

Il s'agit de la partie technique de notre guide plus complet consacré à suivi des prix de la concurrence. Si vous souhaitez avoir un aperçu de la stratégie et des outils, commencez par là. Si vous souhaitez du code exécutable, poursuivez votre lecture.

Points clés à retenir

  • Un « minimal price scraper » est un client HTTP (demandes ou httpx) ainsi qu'un analyseur syntaxique (selectolax ou BeautifulSoup) qui cible un sélecteur CSS.
  • Le point sensible ne réside pas dans l'analyse syntaxique, mais dans l'accès : les limites de débit, le filtrage des user-agents, le géocloaking et les blocages purs et simples d'adresses IP.
  • L'utilisation de proxys résidentiels à rotation est importante pour les pages de prix, car le prix qui s'affiche dépend du pays et de la réputation de l'adresse IP à l'origine de la requête.
  • Les prix générés par JavaScript nécessitent soit un navigateur sans interface graphique, soit un appel d'API issu d'une ingénierie inverse, soit un service de conversion au format Markdown.
  • Enregistrez chaque observation sous forme de ligne comprenant un horodatage et la devise, afin de pouvoir suivre l'évolution des données, et non pas seulement des instantanés. Respectez les conditions d'utilisation et le fichier robots.txt de chaque site.

Qu'est-ce que le « price scraping » ?

Le « price scraping » désigne l'extraction automatisée des prix des produits (ainsi que, généralement, des champs associés tels que le titre, la devise, la disponibilité et la référence) à partir de pages web. Les détaillants y ont recours pour surveiller leurs concurrents. Les marques l'utilisent pour faire respecter les prix minimaux annoncés. Les agrégateurs s'en servent pour alimenter Comparaison des tarifs du web scraping sites.

Les prix constituent une cible de grande valeur, activement protégée. Le rapport « Imperva Bad Bot Report 2025 » (une société du groupe Thales) a révélé que le trafic automatisé avait dépassé l’activité humaine pour la première fois en dix ans, représentant 51 % de l’ensemble du trafic web en 2024, les bots malveillants représentant quant à eux 37 %. Le commerce électronique figure parmi les secteurs les plus ciblés ; les pages de prix bénéficient donc des mêmes mesures de protection que celles qui bloquent les bots pratiquant le « credential stuffing » et l’accaparement de stocks. Votre scraper doit donc se faire passer pour un visiteur lambda.

Étape 1 : Un scraper de prix minimal en Python

Commencez par la solution la plus simple qui puisse fonctionner : une seule requête GET et un seul analyseur. Nous utilisons httpx (un moderne demandes(client compatible) et selectolax (un analyseur HTML rapide basé sur lexbor).


pip install httpx selectolax


import httpx
from selectolax.parser import HTMLParser

HEADERS = {
    "User-Agent": (
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
        "AppleWebKit/537.36 (KHTML, like Gecko) "
        "Chrome/126.0.0.0 Safari/537.36"
    ),
    "Accept-Language": "en-US,en;q=0.9",
}

def scrape_price(url: str, price_selector: str) -> str | None:
    resp = httpx.get(url, headers=HEADERS, timeout=20.0, follow_redirects=True)
    resp.raise_for_status()
    tree = HTMLParser(resp.text)
    node = tree.css_first(price_selector)
    return node.text(strip=True) if node else None

if __name__ == "__main__":
    price = scrape_price("https://example.com/product/123", "span.price")
    print(price)

pip install httpx selectolax

import httpx
from selectolax.parser import HTMLParser

HEADERS = {
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/126.0.0.0 Safari/537.36"
),
"Accept-Language": "en-US,en;q=0.9",
}

def scrape_price(url: str, price_selector: str) -> str | None:
resp = httpx.get(url, headers=HEADERS, timeout=20.0, follow_redirects=True)
resp.raise_for_status()
tree = HTMLParser(resp.text)
node = tree.css_first(price_selector)
return node.text(strip=True) if node else None

if __name__ == "__main__":
price = scrape_price("https://example.com/product/123", "span.price")
print(price)

La seule partie spécifique au site est sélecteur_de_prix. Ouvrez la page du produit dans votre navigateur, examinez l'élément « prix », puis copiez un sélecteur stable (une classe ou data- attribut, et non une structure profonde div > div > div chaîne qui se rompt lors du prochain déploiement).

Les résultats s'affichent sous forme de chaînes de caractères non formatées, telles que « 1 299,00 $ » ou « 1 299,00 € ». Effectuez la normalisation avant de stocker les données :

import re
from decimal import Decimal

def parse_amount(raw: str) -> Decimal | None:
if not raw:
return None
# Supprimer tout ce qui n'est pas un chiffre ou un séparateur
cleaned = re.sub(r"[^\d.,]", "", raw)
# Heuristique : si la virgule est le séparateur décimal (par ex. « 1.299,00 »)
si cleaned.count(",") == 1 et cleaned.rfind(",") > cleaned.rfind(".") :
cleaned = cleaned.replace(".", "").replace(",", ".")
else:
cleaned = cleaned.replace(",", "")
try:
return Decimal(cleaned)
except Exception:
return None

Veillez à toujours indiquer séparément le symbole monétaire d'origine. Ne partez pas du principe qu'il s'agit de l'USD.

Étape 2 : Gérer les mesures de protection anti-bot

L'exemple ci-dessus fonctionne sur un site de test, mais échoue chez la plupart des véritables détaillants. Voici les problèmes que vous rencontrerez et comment les résoudre.

Limites de débit et tentatives de réessai

Surcharger un site est le moyen le plus rapide de se faire bloquer et le comportement le plus grossier qui soit. Ajoutez à cela des délais, de la gigue et un recul exponentiel aux réponses qui vous invitent à « ralentir » (429) ou « temporairement indisponible » (503).

import random
import time
import httpx

def get_with_backoff(client: httpx.Client, url: str, max_tries: int = 4):
for attempt in range(max_tries):
resp = client.get(url, timeout=20.0)
si resp.status_code n’est pas dans (429, 503) :
renvoyer resp
wait = (2 ** attempt) + random.uniform(0, 1.0)
time.sleep(wait)
resp.raise_for_status()
return resp

Agents utilisateurs et en-têtes

Une requête sans en-têtes crie « script » à plein nez. Envoyez un User-Agent réaliste et à jour, correspondant à un navigateur, ainsi que les en-têtes qu'un vrai navigateur envoie (Accepter, Accept-Language, Accept-Encoding). Le fait d'alterner entre un petit nombre de chaînes d'identifiant d'utilisateur (UA) réelles et récentes aide, mais un identifiant d'utilisateur crédible associé à une adresse IP signalée se fait tout de même bloquer.

Blocs d'adresses IP, géocamouflage et l'importance des proxys résidentiels

C'est la partie que la plupart des tutoriels omettent. Deux requêtes adressées à la même URL de produit peuvent renvoyer des prix différents, des devises différentes ou une page d'erreur, en fonction uniquement de l'adresse IP à l'origine de la requête.

  • Adresses IP des centres de données (les plages de valeurs utilisées par la plupart des serveurs cloud) sont faciles à identifier et font souvent l'objet d'une limitation de débit, voire d'un blocage pur et simple, sur les pages de tarifs.
  • Masquage géographique Cela signifie qu'un site affiche le prix en dollars américains à un visiteur américain et le prix en euros à un visiteur allemand. Si vous effectuez une collecte de données à partir d'une seule région de centre de données, vous ne verrez jamais que les prix d'un seul marché, et vous tomberez parfois sur un message du type « non disponible dans votre région ».

Les proxys résidentiels rotatifs acheminent les requêtes via de véritables connexions grand public ; ils bénéficient ainsi de la réputation IP d’un visiteur lambda et vous permettent de choisir le pays (et parfois la ville) d’où la requête semble provenir. Pour le scraping des prix, cela signifie que vous voyez le prix réel localisé pour chaque marché et que vous évitez les blocs d’adresses IP de centres de données qui bloquent l’accès à ces pages. Massive exploite un réseau résidentiel couvrant plus de 195 pays (HTTP/HTTPS/SOCKS5) avec un ciblage géographique par pays/ville et des sessions rotatives ou fixes.

Pour intégrer un proxy dans le client, il suffit de modifier une seule ligne de code :

import os
import httpx

# Proxy résidentiel Massive. Les identifiants doivent être indiqués dans l'URL du proxy sous la forme « utilisateur:mot de passe ».
PROXY = (
f"https://{os.environ['MASSIVE_PROXY_USERNAME']}:"
f"{os.environ['MASSIVE_API_KEY']}@network.joinmassive.com:65535"
)

with httpx.Client(proxy=PROXY, headers=HEADERS, timeout=20.0) as client:
resp = client.get("https://example.com/product/123")
print(resp.status_code)

Utilisez un rotatif session lorsque vous souhaitez disposer d'une adresse IP différente à chaque requête (idéal pour répartir la charge) et une en tête de liste session lorsqu'un flux nécessite la même adresse IP pour plusieurs requêtes (par exemple, pour définir un cookie de région, puis charger le prix). Pour un guide pas à pas spécifique aux commerçants, consultez Récupérer les prix d'Amazon sans se faire bloquer.

Étape 3 : Gestion des prix générés par JavaScript

De nombreuses pages d'accueil envoient une structure HTML presque vide et affichent le prix côté client à l'aide de JavaScript. Si resp.text s'il ne contient pas le prix, l'analyseur de l'étape 1 renvoie Aucun quelle que soit la qualité de votre sélecteur. Vous disposez de trois options.

Option A : Identifiez l'API sous-jacente. Ouvrez DevTools, accédez à l'onglet « Réseau », filtrez les requêtes XHR/Fetch, puis actualisez la page. Le prix est presque toujours renvoyé dans une réponse JSON. Accéder directement à ce point de terminaison est plus rapide et plus stable que le rendu, lorsque l'API n'est pas elle-même verrouillée.

Option B : Utiliser un navigateur sans interface graphique. Playwright affiche la page dans un environnement Chromium réel ; ainsi, le prix figure déjà dans le DOM au moment où vous le lisez.

from playwright.sync_api import sync_playwright

def scrape_rendered_price(url: str, selector: str) -> str | None:
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
page.goto(url, wait_until="networkidle")
node = page.query_selector(selector)
value = node.inner_text() si node, sinon None
browser.close()
return value

Les navigateurs sans interface graphique sont gourmands en ressources : ils consomment bien plus de CPU et de mémoire qu'une requête HTTP et sont plus lents à évoluer.

Option C : Conversion au format Markdown (la méthode la plus simple). C'est la solution la plus simple. Une Web Render API exécute le rendu « headless » sur l'infrastructure d'un tiers : de véritables navigateurs exécutent le code JavaScript de la page, puis le résultat final est converti en Markdown épuré et renvoyé à votre propre code. La Web Render API de Massive dispose d'un point de terminaison « Browsing » qui fait exactement cela. Vous évitez à la fois la surcharge liée au navigateur sans interface graphique et la majeure partie du travail d’analyse du code HTML, car vous recherchez la ligne de prix dans du texte lisible au lieu de parcourir un DOM instable, et le Markdown est directement transmis à un client IA ou à une invite LLM si vous souhaitez extraire le prix de cette manière.

import os
import re
import httpx

# Web Render API de Massive, point de terminaison de navigation : elle effectue le rendu de la page sur
# le réseau de Massive et renvoie du code Markdown épuré.
def render_markdown(url: str, country: str = "US") -> str:
resp = httpx.get(
"https://render.joinmassive.com/browser",
params={"url": url, "format": "markdown", "country": country},
headers={"Authorization": f"Bearer {os.environ['MASSIVE_API_TOKEN']}"},
timeout=60.0, # le rendu est plus lourd qu’une simple requête GET, prévoyez suffisamment de temps
)
resp.raise_for_status()
return resp.text

# Le Markdown est bien plus facile à analyser que le HTML brut. Transmettez le pays pour afficher le
# prix tel qu’un acheteur local le voit (prix en dollars américains, en euros, etc.).
markdown = render_markdown("https://example.com/product/123")
match = re.search(r"\$[\d,]+\.\d{2}", markdown)
price = match.group(0) if match else None

Étape 4 : Structurer et stocker les données

Un prix n'a de valeur que s'il est considéré comme une série chronologique. Enregistrez chaque observation dans une ligne distincte accompagnée d'un horodatage, afin de pouvoir détecter les baisses, suivre les concurrents et établir un système de suivi des prix en haut.

import sqlite3
from datetime import datetime, timezone
from decimal import Decimal

def init_db(path: str = "prices.db") -> sqlite3.Connection:
conn = sqlite3.connect(path)
conn.execute(
"""
CREATE TABLE IF NOT EXISTS observations (
id INTEGER PRIMARY KEY,
sku TEXT NOT NULL,
url TEXT NOT NULL,
amount TEXT NOT NULL, -- stockez les valeurs de type Decimal sous forme de texte, jamais sous forme de float
currency TEXT NOT NULL,
country TEXT, -- marché d’origine de ce prix
scraped_at TEXT NOT NULL
)
"""
)
return conn

def record(conn, sku: str, url: str, amount: Decimal, currency: str, country: str):
conn.execute(
"INSERT INTO observations (sku, url, amount, currency, country, scraped_at) "
"VALUES (?, ?, ?, ?, ?, ?)",
(sku, url, str(amount), currency, country,
datetime.now(timezone.utc).isoformat()),
)
conn.commit()

Deux règles qui vous faciliteront la tâche par la suite : enregistrez le montant sous forme de texte ou de nombre entier correspondant au nombre d'unités mineures (jamais sous forme de nombre à virgule flottante binaire, car cela entraîne la perte des centimes), et notez toujours le devise et pays. La présence d’un « $50 » et d’un « 50 EUR » dans la même colonne sans indication de devise constitue une erreur de données silencieuse. Pour un fichier CSV ou un pipeline d’entrepôt, le schéma est le même ; la ligne comportant l’horodatage et le code de marché est l’unité qui fait foi. Si vous préférez acheter plutôt que de développer vous-même, comparez outils de suivi des prix de la concurrence.

Remarque concernant la légalité et les conditions générales d'utilisation

Il ne s'agit pas d'un avis juridique, mais il est utile de connaître la situation concrète. L'extraction de données accessibles au public a, à plusieurs reprises, résisté aux contestations américaines au titre de la loi sur la fraude et les abus informatiques (Computer Fraud and Abuse Act) : dans l'affaire de longue haleine hiQ Labs c. LinkedIn Dans le cadre d'un litige, la Cour d'appel du neuvième circuit a réaffirmé en 2022 que l'accès à des données de sites web accessibles au public ne constituait probablement pas un accès « sans autorisation » au sens de la CFAA. Cette décision portait spécifiquement sur la CFAA et ne constituait pas un feu vert général. D’autres fondements juridiques (violation des conditions d’utilisation d’un site, droit d’auteur, atteinte aux biens mobiliers et droit à la vie privée) peuvent toujours s’appliquer.

Quelques conseils pratiques : lisez et respectez les conditions d'utilisation de chaque site et robots.txt, ne collectez que les données publiques relatives aux prix (jamais celles accessibles uniquement après connexion, pour lesquelles vous vous êtes engagé à ne pas les collecter), respectez les limites de fréquence afin de ne pas nuire au bon fonctionnement du site, et évitez les données à caractère personnel. En cas de doute, consultez votre conseiller juridique.

Construisez-le sur un réseau qui reflète le prix réel

Le code, c'est la partie la plus facile. Ce qui est difficile, c’est d’accéder de manière cohérente à des prix corrects et localisés, et cela dépend de l’origine apparente de vos requêtes. Le réseau résidentiel de Massive (plus de 195 pays, ciblage géographique par pays/ville, sessions tournantes ou persistantes) et la Web Render API (pages rendues au format Markdown épuré) résolvent ces deux problèmes : la réputation de l’adresse IP et le ciblage géographique qui vous permettent de contourner les blocages, ainsi que le rendu qui vous fournit des prix chargés via JavaScript sans que vous ayez à lancer vous-même de navigateurs. Découvrez la Web Render API de Massive et ses proxys résidentiels.

Sources

Foire aux questions

Quel est le moyen le plus simple de se lancer dans le scraping de prix en Python ?+

Installer httpx et selectolax, envoyez une requête GET avec un en-tête User-Agent réaliste, puis ciblez le prix à l'aide d'un seul sélecteur CSS. Voilà un scraper fonctionnel en une quinzaine de lignes. Ajoutez des proxys et des tentatives de réessai dès que vous commencez à être bloqué, ce qui arrive rapidement sur les véritables sites de vente en ligne.

Pourquoi le prix qui s'affiche est-il différent (ou absent) de celui que je vois dans mon navigateur ?+

Il existe deux causes courantes. Premièrement, le prix peut être généré par JavaScript ; il ne figure donc pas dans le code HTML brut que reçoit votre client HTTP ; vous avez besoin d’un navigateur sans interface graphique ou d’un service de conversion vers Markdown. Deuxièmement, le site peut recourir au « geo-cloaking », c’est-à-dire afficher des prix différents selon les pays, ou bloquer votre adresse IP. En effectuant le scraping via une adresse IP résidentielle située dans le pays concerné, vous obtiendrez le prix localisé correct tout en évitant les blocages liés aux adresses IP de centres de données.

Ai-je besoin de serveurs proxy pour extraire les prix ?+

Pour quelques pages seulement, non. À un volume significatif, oui. Les pages de tarifs sont activement protégées, et une adresse IP de centre de données qui envoie des requêtes répétées est rapidement soumise à une limitation de débit ou bloquée. L'utilisation de proxys résidentiels en rotation répartit les requêtes sur de véritables adresses IP de particuliers et vous permet de cibler des pays spécifiques, ce qui est indispensable lorsque les prix varient selon les marchés.

Comment puis-je extraire les prix chargés par JavaScript ?+

Trois options, par ordre de préférence : trouver l'API JSON à laquelle la page fait appel (consultez l'onglet « Réseau » des Outils de développement), utiliser un navigateur sans interface utilisateur tel que Playwright, ou recourir à une Web Render API qui renvoie la page entièrement rendue (le format Markdown est le plus facile à analyser). Texte brut demandes/httpx ne peut pas, à lui seul, exécuter du code JavaScript.

Le « price scraping » est-il légal ?+

La collecte de données tarifaires accessibles au public a résisté aux contestations fondées sur la loi américaine CFAA (notamment hiQ c. LinkedIn), mais cela ne concerne pas les conditions d'utilisation, les droits d'auteur ni les réclamations relatives à la vie privée, et les lois varient selon les juridictions. Tenez-vous-en aux données publiques, respectez robots.txt et respectez les conditions générales d'utilisation, appliquez poliment les limites de débit, et consultez un avocat pour toute activité commerciale ou à grande échelle.