# Analyse Store Backmarket Date: 2026-01-13 Auteur: PriceWatch Development Team ## Résumé Exécutif Backmarket.fr est un marketplace de produits reconditionnés (smartphones, laptops, tablets, etc.). Le parsing est **très fiable** grâce à l'utilisation extensive de **JSON-LD schema.org**, mais nécessite **Playwright obligatoire** en raison de la protection anti-bot. **Score de parsing**: ⭐⭐⭐⭐⭐ (5/5) - Excellent **Difficulté d'accès**: 🔒🔒🔒 (3/5) - Anti-bot fort --- ## 🎯 Spécificités Backmarket ### 1. Marketplace de Reconditionné Contrairement à Amazon ou Cdiscount qui vendent du neuf, Backmarket vend **exclusivement du reconditionné**. **Implications**: - Chaque produit a plusieurs **offres** avec différents états/conditions - Prix variable selon la **condition** choisie (Correct, Bon, Excellent, etc.) - Le grade/condition doit être extrait et stocké dans `specs["Condition"]` **Grades Backmarket**: - **Correct** - État correct avec traces d'usage visibles - **Bon** - Quelques rayures légères - **Très bon** - Très peu de rayures - **Excellent** - Quasiment aucune trace d'usage - **Comme neuf** - État neuf ### 2. Protection Anti-bot Forte ❌ **HTTP simple ne fonctionne PAS** - retourne **403 Forbidden** ✅ **Playwright OBLIGATOIRE** pour récupérer le contenu ```python # ❌ NE FONCTIONNE PAS result = fetch_http("https://www.backmarket.fr/fr-fr/p/iphone-15-pro") # → 403 Forbidden # ✅ FONCTIONNE result = fetch_playwright("https://www.backmarket.fr/fr-fr/p/iphone-15-pro", headless=True) # → 200 OK avec contenu complet ``` **Temps de chargement**: ~2-3 secondes avec Playwright --- ## 📊 Structure HTML & Extraction ### JSON-LD Schema.org (Source Prioritaire) Backmarket utilise **schema.org Product** de manière **complète et fiable**. **Exemple de JSON-LD**: ```json { "@context": "https://schema.org", "@type": "Product", "name": "iPhone 15 Pro", "image": "https://d2e6ccujb3mkqf.cloudfront.net/...", "offers": { "@type": "Offer", "price": "571.00", "priceCurrency": "EUR", "availability": "https://schema.org/InStock" } } ``` **Avantages**: - ✅ Données structurées et stables - ✅ Prix toujours au format numérique propre - ✅ Devise explicite - ✅ URL d'image de haute qualité **Extraction prioritaire** dans `BackmarketStore._extract_json_ld()`: ```python def _extract_json_ld(self, soup: BeautifulSoup) -> dict: """Extrait les données depuis JSON-LD schema.org.""" json_ld_scripts = soup.find_all("script", {"type": "application/ld+json"}) for script in json_ld_scripts: data = json.loads(script.string) if data.get("@type") == "Product": return { "name": data.get("name"), "price": float(data["offers"]["price"]), "priceCurrency": data["offers"]["priceCurrency"], "images": [data.get("image")] } ``` ### Sélecteurs CSS (Fallback) Si JSON-LD n'est pas disponible, on utilise des sélecteurs CSS relativement stables. **Sélecteurs identifiés**: | Champ | Sélecteur | Stabilité | |-------|-----------|-----------| | **Titre** | `h1.heading-1` | ⭐⭐⭐⭐ Stable | | **Prix** | `div[data-test='price']` | ⭐⭐⭐⭐ Stable | | **Condition** | `button[data-test='condition-button']` | ⭐⭐⭐⭐ Stable | | **Images** | `img[alt]` | ⭐⭐⭐ Moyen (filtrer par nom produit) | | **Stock** | `button[data-test='add-to-cart']` | ⭐⭐⭐⭐ Stable | | **Specs** | `dl > dt, dd` | ⭐⭐⭐ Moyen | | **Category** | `nav[aria-label='breadcrumb'] a` | ⭐⭐⭐ Moyen | **Stabilité des sélecteurs**: **Bonne** - Backmarket utilise des classes sémantiques (`heading-1`) et des attributs `data-test` qui sont plus stables que les classes générées aléatoirement. --- ## 🔧 Implémentation Technique ### URL Pattern & SKU Extraction **Format URL**: `https://www.backmarket.fr/{locale}/p/{slug}` **Exemples**: - `https://www.backmarket.fr/fr-fr/p/iphone-15-pro` → SKU = `"iphone-15-pro"` - `https://www.backmarket.com/en-us/p/macbook-air-m2` → SKU = `"macbook-air-m2"` **Regex d'extraction**: ```python def extract_reference(self, url: str) -> Optional[str]: match = re.search(r"/p/([a-z0-9-]+)", url, re.IGNORECASE) if match: return match.group(1) return None ``` **Caractéristiques du slug**: - Format kebab-case: `product-name-variant` - Peut contenir chiffres: `iphone-15-pro`, `galaxy-s23` - Stable dans le temps (identifiant produit) ### Canonicalization On retire les paramètres de query et le fragment car ils ne changent pas le produit. ```python def canonicalize(self, url: str) -> str: parsed = urlparse(url) return f"{parsed.scheme}://{parsed.netloc}{parsed.path}" ``` **Exemples**: - `https://www.backmarket.fr/fr-fr/p/iphone-15-pro?color=black` → `https://www.backmarket.fr/fr-fr/p/iphone-15-pro` - `https://www.backmarket.fr/fr-fr/p/iphone-15-pro#specs` → `https://www.backmarket.fr/fr-fr/p/iphone-15-pro` --- ## 📈 Tests & Validation ### Test Coverage **Tests unitaires**: 19 tests **Tests fixtures**: 11 tests **Total**: 30 tests - **100% PASS** ✅ **Coverage du store**: **85%** **Fichiers de tests**: - `tests/stores/test_backmarket.py` - Tests unitaires (match, canonicalize, extract_reference, parse) - `tests/stores/test_backmarket_fixtures.py` - Tests avec HTML réel ### Fixture Réelle **Produit**: iPhone 15 Pro (reconditionné) **Fichier**: `pricewatch/app/stores/backmarket/fixtures/backmarket_iphone15pro.html` **Taille**: 1.5 MB **Date capture**: 2026-01-13 **Méthode**: Playwright (obligatoire) **Données extraites de la fixture**: ```json { "source": "backmarket", "title": "iPhone 15 Pro", "price": 571.0, "currency": "EUR", "reference": "iphone-15-pro", "images": ["https://d2e6ccujb3mkqf.cloudfront.net/..."], "is_complete": true } ``` --- ## ⚖️ Comparaison avec Autres Stores | Aspect | Amazon | Cdiscount | **Backmarket** | |--------|--------|-----------|----------------| | **Anti-bot** | Faible | Fort (Baleen) | Fort (Cloudflare) | | **Méthode** | HTTP OK | Playwright | **Playwright** | | **JSON-LD** | Partiel | ✗ Non | **✓ Oui (complet)** | | **Sélecteurs** | Stables (IDs) | Instables | **Stables (data-test)** | | **SKU format** | `/dp/{ASIN}` | `/f-{cat}-{SKU}` | **/p/{slug}** | | **Parsing fiabilité** | ⭐⭐⭐⭐ | ⭐⭐⭐ | **⭐⭐⭐⭐⭐** | | **Vitesse fetch** | Rapide (200ms) | Lent (2-3s) | Lent (2-3s) | | **Particularité** | - | Prix dynamiques | **Reconditionné** | **Conclusion**: Backmarket est le store **le plus fiable pour le parsing** grâce au JSON-LD, mais nécessite Playwright comme Cdiscount. --- ## ✅ Avantages de Backmarket 1. **JSON-LD schema.org complet** → Parsing ultra-fiable 2. **Classes CSS stables** → Moins de casse que Cdiscount 3. **URL propre et prévisible** → SKU facile à extraire 4. **Format prix standardisé** → Toujours numérique dans JSON-LD 5. **Sélecteurs `data-test`** → Conçus pour être stables --- ## ❌ Inconvénients & Défis ### 1. Protection Anti-bot Obligatoire **Impact**: - ⏱️ Temps de fetch: ~2-3 secondes (vs 200ms pour HTTP) - 💰 Coût: Plus de ressources CPU/mémoire - 🐳 Docker: Nécessite installation de Playwright browsers **Solution**: Utiliser le cache agressivement, éviter les fetch répétés. ### 2. Prix Variable selon Condition Le même produit peut avoir 5-10 prix différents selon l'état. **Problème**: Quel prix extraire ? **Solution actuelle**: On extrait le prix de l'offre par défaut sélectionnée (souvent "Excellent") **Amélioration future**: Extraire toutes les offres avec leurs conditions et prix. ### 3. Stock Complexe Le stock dépend de l'offre ET de la condition choisie. **Problème**: Un produit peut être "en stock" dans une condition mais "rupture" dans une autre. **Solution actuelle**: On extrait le stock de l'offre par défaut. ### 4. 404 Fréquents Les produits reconditionnés ont un **stock limité** et les URLs peuvent devenir invalides rapidement. **Exemple testé**: - `https://www.backmarket.fr/fr-fr/p/samsung-galaxy-s23` → 404 - `https://www.backmarket.fr/fr-fr/p/apple-macbook-air-133-pouces-m2-2022` → 404 **Impact**: Plus de gestion d'erreurs nécessaire dans le pipeline. --- ## 🎓 Recommandations ### Pour le Développement 1. **Toujours utiliser Playwright pour Backmarket** - Ne jamais tenter HTTP 2. **Prioriser JSON-LD** - C'est la source la plus fiable 3. **Extraire la condition** - Ajouter dans `specs["Condition"]` 4. **Gérer les 404 gracefully** - Produits limités en stock 5. **Cache long** - Minimiser les appels Playwright coûteux ### Pour les Tests 1. **Maintenir les fixtures à jour** - Les URLs expirent vite 2. **Tester avec différentes catégories** - Smartphones, laptops, tablets 3. **Tester les différentes conditions** - Prix varie selon état 4. **Tester les 404** - Cas fréquent pour le reconditionné ### Pour la Production 1. **Monitoring des 404** - Alertes sur produits devenus indisponibles 2. **Rotation des proxies** - Si scraping intensif 3. **Rate limiting** - Respecter le site (2-3s entre requêtes minimum) 4. **Cache agressif** - Playwright coûte cher en ressources --- ## 📝 Exemples d'Utilisation ### Scraping Simple ```python from pricewatch.app.scraping.pw_fetch import fetch_playwright from pricewatch.app.stores.backmarket.store import BackmarketStore url = "https://www.backmarket.fr/fr-fr/p/iphone-15-pro" # Fetch avec Playwright (obligatoire) result = fetch_playwright(url, headless=True, timeout_ms=60000) # Parse store = BackmarketStore() snapshot = store.parse(result.html, url) print(f"Title: {snapshot.title}") print(f"Price: {snapshot.price} {snapshot.currency}") print(f"Condition: {snapshot.specs.get('Condition', 'N/A')}") print(f"Complete: {snapshot.is_complete()}") ``` ### Détection Automatique ```python from pricewatch.app.core.registry import StoreRegistry url = "https://www.backmarket.fr/fr-fr/p/iphone-15-pro" # Détection automatique du store store = StoreRegistry.detect(url) print(f"Store détecté: {store.store_id}") # → "backmarket" print(f"Score: {store.match(url)}") # → 0.9 ``` ### Pipeline Complet ```python from pricewatch.app.cli.main import scrape_url url = "https://www.backmarket.fr/fr-fr/p/iphone-15-pro" # Pipeline complet: detect → fetch → parse → save snapshot = scrape_url( url, use_playwright=True, # Obligatoire pour Backmarket save_html=True, save_screenshot=True ) print(f"✓ Scraped: {snapshot.title} - {snapshot.price} {snapshot.currency}") ``` --- ## 🔍 Points d'Attention pour Debug ### Si le parsing échoue: 1. **Vérifier que Playwright est utilisé** - HTTP ne fonctionne jamais 2. **Vérifier le code status** - Peut être 404 si produit épuisé 3. **Inspecter le JSON-LD** - C'est la source prioritaire 4. **Vérifier la condition** - Prix peut varier selon offre sélectionnée 5. **Logs détaillés** - Activer `--debug` pour voir les erreurs ### Exemple de debug: ```bash # Activer les logs détaillés export PRICEWATCH_LOG_LEVEL=DEBUG # Scraper avec sauvegarde HTML pricewatch fetch https://www.backmarket.fr/fr-fr/p/iphone-15-pro \ --playwright \ --save-html \ --debug ``` --- ## 📚 Ressources **Documentation officielle**: https://www.backmarket.fr **Fichiers du projet**: - `pricewatch/app/stores/backmarket/store.py` - Implémentation - `pricewatch/app/stores/backmarket/selectors.yml` - Sélecteurs CSS - `pricewatch/app/stores/backmarket/fixtures/README.md` - Documentation des fixtures - `tests/stores/test_backmarket.py` - Tests unitaires - `tests/stores/test_backmarket_fixtures.py` - Tests avec HTML réel **Tests**: ```bash # Tous les tests Backmarket pytest tests/stores/test_backmarket*.py -v # Avec coverage pytest tests/stores/test_backmarket*.py --cov=pricewatch.app.stores.backmarket ``` --- ## 🎉 Conclusion Backmarket est un **excellent store à supporter** pour PriceWatch: - ✅ Parsing **très fiable** grâce au JSON-LD - ✅ Sélecteurs **stables** (data-test, classes sémantiques) - ✅ Tests **complets** (30 tests, 100% pass) - ⚠️ Nécessite **Playwright** (coût en performance) - ⚠️ URLs peuvent **expirer** (stock limité) **Score final**: ⭐⭐⭐⭐⭐ (5/5) pour la fiabilité du parsing. **Recommandation**: Store prioritaire à maintenir pour le marché du reconditionné.