shopping-radar
Enables Claude to search and analyze product listings from multiple French marketplaces, evaluating price, delivery, and distance to a reference point to find the best value.
README
shopping-radar 🛒🔎
Serveur MCP qui permet à Claude de récupérer les annonces d'un produit sur plusieurs places de marché françaises et de les analyser rigoureusement : prix, date, livraison, et distance au point de référence (Montpellier) quand l'article n'est pas livrable.
Pensé pour trouver le meilleur rapport qualité-prix (ex. un cadeau), en comparant l'occasion au prix du neuf.
Sources (8)
| Source | Type | Clé requise | État |
|---|---|---|---|
| Leboncoin (direct) | occasion | — (intermittent, DataDome) | ✅ actif |
| Leboncoin (Apify) | occasion fiable | APIFY_TOKEN (~1 $/1000 résultats) |
✅ testé, opt-in |
| Vinted | occasion | — | ✅ actif |
| Dealabs | deals neuf / erreurs de prix | — | ✅ actif |
| eBay | occasion + reconditionné | EBAY_APP_ID + EBAY_CERT_ID (gratuit) |
🔌 sur clé |
| Google Shopping | neuf (multi-marchands) | SERPAPI_KEY (gratuit limité) |
🔌 sur clé |
| Idealo | comparateur neuf | APIFY_TOKEN (crédit gratuit) |
🔌 sur clé |
| Keepa | Amazon neuf + historique | KEEPA_API_KEY (payant) |
🔌 sur clé |
| Back Market | reconditionné garanti | APIFY_TOKEN + BACKMARKET_APIFY_ACTOR |
🔌 sur clé |
Les 3 premières fonctionnent sans aucune clé. Les autres s'activent en ajoutant la clé correspondante dans
.env. Aucune dépendance Python supplémentaire n'est nécessaire pour les activer (sauf Keepa) — tout passe parhttpx.
Couverture par usage
- Occasion : Leboncoin, Vinted, eBay
- Reconditionné : Back Market, eBay
- Neuf / prix de référence : Google Shopping (Amazon, Fnac, Darty, Boulanger…), Idealo, Keepa, Dealabs
Pipeline d'analyse & qualité des données
analyze enchaîne : recherche multi-sources → déduplication → filtre logistique
(Montpellier) → filtre de pertinence → enrichissement qualité → tri par score
composite. Chaque offre gardée est enrichie de :
condition_label— état normalisé sur une échelle standard (neuf, comme neuf, très bon, bon, correct, reconditionné, pour pièces).is_bundle— détecte les lots/packs (« + 2 batteries », « pack complet »).risk_level(ok/faible/moyen/eleve) +risk_flags:prix_tres_bas(sous médiane − 30 %),marque_incoherente(marque ≠ produit recherché),import_hors_fr(titre en langue étrangère → transfrontalier),vendeur_inconnu.deal_score— note composite 0-100 où le risque PRIME sur le prix : la valeur-prix est plafonnée sous le seuil « trop beau pour être vrai » (pas de bonus pour les prix d'arnaque) et un malus de risque (jusqu'à −45) écrase le bonus prix. Pondération : prix vs neuf (50) + état (20) + proximité (15) + confiance vendeur (10) − risque.- Segmentation par variante : les stats (médiane, seuil « trop bas ») sont calculées par segment (sous-modèle × premium/standard), pas sur un pool qui mélangerait X4 nue / Adventure / Bike… Le seuil bas est auto-calibré par z-score robuste (médiane − k·MAD intra-segment), au lieu d'un ratio fixe.
- Détection de mislabel : une variante premium (« Adventure », « Bike »…)
affichée sous le prix d'une version de base → flag
mislabel_suspect(résolu sans référence externe, par comparaison inter-segments). - Seuils configurables :
RISK_LOW_PRICE_RATIO,RISK_MAD_K,IMPORT_MARKERS,PREMIUM_KEYWORDS.
Limites encore ouvertes (assumées) : (1) la validation du modèle de risque repose sur des tests unitaires (change-detectors), pas sur un jeu labellisé précision/rappel ; (2) les stats vendeur (note, ancienneté, dernière connexion) et la fraîcheur/statut vendu des annonces ne sont pas encore exploitées — ce sont les signaux anti-fraude les plus forts ; (3) le score n'intègre pas encore garantie FR / retour / complétude accessoires.
also_on— autres sources où l'article a été repéré (déduplication inter-sources).
stats remonte aussi duplicates_removed, suspicious_count, new_reference_price.
Tests : python tests/test_core.py (tests déterministes, sans réseau).
Validation (au-delà des tests unitaires) : python eval/score.py <gold> mesure
le rappel scam (la vraie cible) séparément du risky, avec IC95% Wilson
(honnête sur petit N), + faux positifs sur les ok. Protocole de labellisation
figé dans eval/LABELING.md (verdict ok/risky/scam,
labellisation à l'aveugle).
Workflow gold réel :
python eval/make_batch.py→eval/to_label.jsonl(batch réel, aveugle, stratifié-pour-faire-mal : imports/bundles/refurb/prix bas/phrases FR ambiguës).- Labelliser à la main (
verdict+reason) seloneval/LABELING.md. python eval/score.py eval/to_label.jsonl→ vrais précision/rappel.
eval/gold.jsonl (25 lignes synthétiques) reste comme détecteur de régression
(il a chiffré puis verrouillé le fix « prix négociable »). C'est un détecteur de
modes d'échec, pas un go/no-go statistique au pourcent.
Vers un produit (SaaS) : voir docs/AUDIT.md (audit du code), docs/ARCHITECTURE.md (passage stateless→stateful : Postgres
- file de jobs + précalcul) et db/schema.sql. Le moteur actuel devient la couche worker, on ne le réécrit pas.
Sécurité free tier (quotas)
Un garde-fou (core/quota.py) compte les appels par provider et
bloque proprement avant de dépasser la limite gratuite (pas de facturation
surprise). Usage persisté dans .cache/usage.json. Limites par défaut (juin 2026,
surchargeables dans .env) :
| Provider | Limite gratuite | Fenêtre |
|---|---|---|
| SerpAPI | 250 recherches | mois |
| Apify (runs) | 50 runs (vrai plafond = 5 $ de crédit) | mois |
| eBay Browse | 5 000 appels | jour |
| Base Adresse Nationale | 2 000 (poli) | jour |
- Sources coûteuses opt-in : Idealo, Back Market (runs Apify) et Keepa
consomment du crédit → exclues des recherches par défaut. Pour les utiliser,
les nommer :
analyze(query, sources=["vinted","idealo"]). Google Shopping (SerpAPI, gratuit) fournit déjà le prix neuf de référence par défaut. - État des quotas visible via l'outil
list_sources.
Fiabilité Leboncoin & proxy
Important : la lib lbc fait déjà le plus dur — impersonation TLS via
curl_cffi (JA3 d'un vrai navigateur), amorçage du cookie DataDome (GET homepage),
et utilisation de l'API mobile avec un User-Agent d'app. La couche TLS n'est
donc PAS le problème. Les 503 intermittents viennent de la réputation de l'IP
(ton IP perso flaggée après quelques requêtes). Le seul vrai levier = une IP propre.
Du gratuit au plus fiable :
- Retries + rotation de fingerprint (gratuit, activé) :
LEBONCOIN_RETRIES=3. - Proxy résidentiel FR :
LEBONCOIN_PROXY=...ou un poolLEBONCOIN_PROXIES=p1,p2,...(rotation aléatoire). Options :LEBONCOIN_IMPERSONATE,LEBONCOIN_DATADOME_COOKIE. - ✅ Source
leboncoin_apify(la plus fiable, recommandée) : acteur Apifyfatihtahta/leboncoin-fr-scraper(proxy résidentiel inclus ~1 $/1000 résultats). Testée, données riches (GPS, code postal, vendeur pro/particulier). Opt-in :analyze(query, sources=["leboncoin_apify","vinted","google_shopping"]).
⚠️ Pas de proxy gratuit fiable contre DataDome (les gratuits = IP datacenter, filtrées). Voie fiable « sans CB » : la source
leboncoin_apify(crédit Apify).
Générique — fonctionne pour n'importe quel article
Rien n'est codé en dur pour un produit donné : la requête est un paramètre, le
point de référence est dans .env (HOME_LAT/LNG), et les heuristiques sont
génériques. Pour un domaine précis, on ajoute des mots-clés via .env :
ACCESSORY_KEYWORDS=perche,objectif,trepied (photo) ou …=pompe,antivol (vélo),
et BUNDLE_KEYWORDS=.... Le filtre de pertinence (is_relevant) s'adapte seul au
modèle recherché (tokens chiffrés type « x4 », « 13 »).
Règle livraison / distance (mise à jour)
Une annonce est rejetée uniquement si elle est explicitement non livrable ET trop loin. Si la livraison est inconnue (cas fréquent Leboncoin), l'annonce est gardée avec un drapeau « (!) livraison à vérifier » plutôt que jetée — on ne perd pas une bonne affaire potentiellement livrable.
Géolocalisation
Si une annonce n'a pas de coordonnées GPS (fréquent sur Leboncoin), le code
postal/ville est géocodé via l'API officielle Base Adresse Nationale
(gratuite, sans clé) pour calculer la distance à Montpellier. Cache dans
.cache/geocode.json.
Installation
python -m venv .venv
.venv\Scripts\activate # Windows
pip install -r requirements.txt
copy .env.example .env # puis éditer si besoin
Outils exposés à Claude (MCP)
list_sources()— sources disponibles + point de référence.search_all(query, max_price?, sources?, limit_per_source?)— récupère les offres normalisées de toutes les sources actives (en parallèle).analyze(query, max_price?, max_distance_km?, reference_price?, ...)— recherche + filtrage logistique (livrable OU à ≤ X km de Montpellier) + tri par prix et score de valeur vs prix neuf.build_candidates(query, budget?, priorities?, top_k?, ...)— produit le « fichier de candidats » pour un agent décideur : normalise chaque offre (schéma stable,total_cost_normalized,warranty_monthsoù null ≠ 0), met en quarantaine le risque élevé/mislabel (jamais transmis), et stratifie en top-K par (modèle × état). Renvoie aussi un brief de décision. Le déterministe ne tranche rien de subjectif — c'est le job de l'agent.
Brancher sur Claude Code / Claude Desktop
Ajouter dans la config MCP (ex. claude_desktop_config.json) :
{
"mcpServers": {
"shopping-radar": {
"command": "python",
"args": ["C:/Users/rayan/Desktop/Dev/Leboncoin/server.py"]
}
}
}
Puis, dans Claude : « Analyse les Insta360 X4 d'occasion sous 320 €, livrables ou à moins de 50 km de Montpellier. »
Règle métier « livraison / distance »
Une offre est gardée si :
- elle est livrable, OU
- sa distance au point de référence ≤
MAX_DISTANCE_KM(défaut 60 km).
Si livraison et distance sont inconnues, l'offre est gardée mais marquée « à vérifier » (on ne jette jamais une offre faute d'information).
Avertissements
- Les clients Leboncoin/Vinted reposent sur des API non-officielles qui peuvent casser sans préavis et dont l'usage peut contrevenir aux CGU des sites. Réservé à un usage personnel et raisonnable.
- L'analyse finale (recommandation) est faite par Claude ; ce serveur fournit la donnée normalisée et un pré-tri déterministe.
Architecture
server.py # serveur MCP (outils list_sources / search_all / analyze)
core/
models.py # Offer normalisée + parsing prix/date
geo.py # distance Montpellier (haversine) + géocodage des CP manquants
geocode.py # géocodage Base Adresse Nationale (gratuit) + cache disque
score.py # filtre logistique/accessoires + scoring qualité-prix
sources/
base.py # interface Source
leboncoin.py vinted.py dealabs.py # gratuit
leboncoin_apify.py # Leboncoin fiable (Apify, opt-in)
ebay.py google_shopping.py idealo.py # sur clé
keepa.py backmarket.py # sur clé
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。