Files
scrap/BACKMARKET_ANALYSIS.md
2026-01-13 19:49:04 +01:00

400 lines
12 KiB
Markdown
Executable File

# 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é.