1342 lines
47 KiB
Markdown
1342 lines
47 KiB
Markdown
# Vérification Scraping Amazon - Analyse des erreurs
|
||
|
||
**Date d'analyse**: 2026-01-18
|
||
|
||
---
|
||
|
||
# Résumé exécutif des bugs identifiés
|
||
|
||
## Bugs récurrents (P1 - Priorité haute)
|
||
|
||
| # | Bug | Occurrences | Cause racine |
|
||
|---|-----|-------------|--------------|
|
||
| 1 | **MSRP incorrect** | 5/8 produits | Sélecteur trop large capture prix de produits sponsorisés/recommandés |
|
||
| 1b | **MSRP = prix bas 30j** | 1/8 produits | `select_one()` prend le 1er prix barré (prix bas 30j) au lieu du 2ème (MSRP) |
|
||
| 2 | **Stock "Inconnu" affiché** | 3/8 produits | Parser ne reconnaît pas "expédié sous" + UI affiche "Inconnu" au lieu de masquer |
|
||
| 3 | **"EVOL. 30 JOURS" affiché** | Tous produits | Donnée calculée affichée alors qu'elle n'existe pas sur Amazon |
|
||
|
||
## Bugs ponctuels (P2)
|
||
|
||
| # | Bug | Produit | Cause |
|
||
|---|-----|---------|-------|
|
||
| 4 | Amazon Choice faux positif | Produit 3 | Détection sur élément vide |
|
||
| 5 | Type "Ecran" pour carte graphique | Produit 4 | Classification mots-clés incorrecte |
|
||
| 6 | Type "PC portable" pour RAM | Produit 6 | Classification mots-clés incorrecte |
|
||
|
||
## Notes importantes
|
||
|
||
- **Prix occasion** : Ne pas tenir compte des prix occasion (ex: 245€ vs 223€ neuf sur produit 6)
|
||
- **Différence HTTP/Playwright** : Le HTML diffère selon la méthode de fetch, impactant l'extraction MSRP
|
||
|
||
---
|
||
|
||
# Erreur Playwright détectée
|
||
|
||
## Message d'erreur
|
||
|
||
```
|
||
Erreur: Erreur Playwright: BrowserType.launch: Executable doesn't exist at /root/.cache/ms-playwright/chromium_headless_shell-1200
|
||
```
|
||
|
||
## Impact
|
||
|
||
Quand Playwright échoue, le système bascule sur HTTP. Le HTML retourné par HTTP diffère de celui de Playwright :
|
||
- **HTTP** : Contient plus de sections sponsorisées/recommandations avec des prix barrés
|
||
- **Playwright** : HTML plus "propre" après exécution JavaScript
|
||
|
||
Cela explique pourquoi certains MSRP incorrects sont capturés : le fallback HTTP expose des prix de produits tiers que Playwright aurait masqués ou chargés différemment.
|
||
|
||
## Correction requise
|
||
|
||
```bash
|
||
# Installer les navigateurs Playwright
|
||
playwright install
|
||
```
|
||
|
||
---
|
||
|
||
# Défauts globaux (tous produits)
|
||
|
||
## ❌ ERREUR UI: "EVOL. 30 JOURS" ne devrait pas être affiché
|
||
|
||
**Problème**: Le Web UI affiche "EVOL. 30 JOURS: +0.0%" (ou autre valeur) sur tous les produits, alors que cette donnée **n'existe pas sur Amazon**.
|
||
|
||
**Règle** : Seules les données scrapées depuis la source doivent être affichées dans le bloc produit. Une donnée absente de la source = ne pas afficher.
|
||
|
||
**Distinction importante** :
|
||
- ❌ "EVOL. 30 JOURS" → Donnée calculée, **non présente sur Amazon** → **Ne pas afficher**
|
||
- ✅ Graphique historique → Basé sur les scrapes passés de PriceWatch → **Légitime**
|
||
|
||
**Correction requise** : Supprimer la ligne "EVOL. 30 JOURS" du bloc d'informations produit.
|
||
|
||
---
|
||
---
|
||
|
||
# Produit 6 : Corsair Vengeance SODIMM 32Go
|
||
|
||
**URL**: https://www.amazon.fr/dp/B08GSTF5NJ?th=1
|
||
|
||
## Comparaison: Données Amazon vs Données Scrapées
|
||
|
||
| Champ | Valeur Amazon (source) | Valeur Scrapée (Web UI) | Statut |
|
||
|-------|------------------------|-------------------------|--------|
|
||
| **Titre** | Corsair Vengeance SODIMM 32Go (2x16Go) DDR4 3200MHz C22... | Corsair Vengeance SODIMM 32Go (2x16Go) DDR4 3200MHz C22... | ✅ OK |
|
||
| **Prix** | 245,00€ (occasion) / 223,02€ (neuf) | 223,02 € | ⚠️ VOIR NOTE |
|
||
| **Prix conseillé (MSRP)** | Non affiché sur Amazon | 606,99 € | ❌ ERREUR |
|
||
| **Stock** | Non affiché explicitement | "Inconnu" | ⚠️ À NE PAS AFFICHER |
|
||
| **Note** | 4,7 (8 626) | 4,7 (8 626) | ✅ OK |
|
||
| **Choix Amazon** | Non visible sur capture | Oui | ⚠️ À VÉRIFIER |
|
||
| **Référence (ASIN)** | B08GSTF5NJ | B08GSTF5NJ | ✅ OK |
|
||
| **Image principale** | Image RAM Corsair | Image RAM Corsair | ✅ OK |
|
||
| **Type** | - | PC portable | ❌ ERREUR |
|
||
|
||
## Défauts identifiés
|
||
|
||
### 1. ❌ ERREUR: MSRP incorrect (606,99 €) - Capture de prix tiers
|
||
|
||
**Problème**: Le Web UI affiche "PRIX CONSEILLÉ: 606,99€" alors qu'Amazon n'affiche aucun prix conseillé pour ce produit.
|
||
|
||
**Cause racine identifiée** :
|
||
Le sélecteur MSRP capture un prix barré d'un **produit tiers** dans la section "CardInstance" (recommandations).
|
||
|
||
```
|
||
Source HTML (HTTP): #CardInstanceZ_u0EeRMlrOaTZL9bV_a9A
|
||
Prix capturé: €606,99 (produit recommandé, pas le produit principal)
|
||
```
|
||
|
||
**Note** : Le HTML diffère entre HTTP et Playwright. En HTTP, le prix 606,99€ est trouvé. En Playwright, le parser retourne `None`.
|
||
|
||
**Lien avec erreur Playwright** : L'erreur `Executable doesn't exist at /root/.cache/ms-playwright/chromium_headless_shell-1200` force le fallback HTTP, qui capture ce prix incorrect depuis la section recommandations.
|
||
|
||
**Même cause récurrente** : Le sélecteur `span.a-text-price span.a-offscreen` n'est pas restreint au bloc prix principal.
|
||
|
||
---
|
||
|
||
### 2. ⚠️ NOTE: Prix neuf vs occasion
|
||
|
||
**Observation** : Amazon affiche en évidence le prix **occasion** (245,00€) alors que le prix **neuf** (223,02€) est moins visible.
|
||
|
||
Le parser capture correctement le prix neuf (223,02€), ce qui est le comportement souhaité.
|
||
|
||
**Pas une erreur**, mais une subtilité de mise en page Amazon à connaître.
|
||
|
||
---
|
||
|
||
### 3. ❌ ERREUR: Type "PC portable" incorrect
|
||
|
||
**Problème** : Le produit est de la **RAM SODIMM** (pour portables), pas un PC portable lui-même.
|
||
|
||
**Cause** : Le système de classification a probablement détecté "Ordinateur Portable" dans le titre et a mal catégorisé.
|
||
|
||
---
|
||
|
||
### 4. ⚠️ Stock "Inconnu" ne devrait pas être affiché
|
||
|
||
Comme établi précédemment, si Amazon n'affiche pas de stock → ne pas afficher "Inconnu".
|
||
|
||
---
|
||
|
||
## Résumé produit 6
|
||
|
||
| Priorité | Problème | Récurrent ? |
|
||
|----------|----------|-------------|
|
||
| 🟠 P1 | MSRP incorrect (donnée historique corrompue) | ✅ Oui |
|
||
| 🟡 P2 | Type "PC portable" au lieu de "RAM" | 🆕 Erreur classification |
|
||
| 🟡 P2 | Stock "Inconnu" affiché inutilement | ✅ Oui |
|
||
|
||
---
|
||
---
|
||
|
||
# Produit 5 : Samsung SSD 9100 Pro 2To
|
||
|
||
**URL**: https://www.amazon.fr/dp/B0DWFLPMM5?th=1
|
||
|
||
## Comparaison: Données Amazon vs Données Scrapées
|
||
|
||
| Champ | Valeur Amazon (source) | Valeur Scrapée (Web UI) | Statut |
|
||
|-------|------------------------|-------------------------|--------|
|
||
| **Titre** | Samsung SSD Interne 9100 Pro, NVMe 2.0 PCIe 5.0x4, Capacité 2To... | Samsung SSD Interne 9100 Pro, NVMe 2.0 PCIe 5.0x4, Capacité 2To... | ✅ OK |
|
||
| **Prix** | 249,99 € | 249,99 € | ✅ OK |
|
||
| **Prix conseillé (MSRP)** | 329,99 € | 329,99 € | ✅ OK |
|
||
| **Réduction** | -24 % | -24 % | ✅ OK |
|
||
| **Stock** | Non affiché explicitement (mais bouton "Ajouter au panier" présent) | "Inconnu" | ⚠️ AMÉLIORABLE |
|
||
| **Note** | 4,7 (966) | 4,7 (966) | ✅ OK |
|
||
| **Choix Amazon** | Non visible sur capture | Oui | ⚠️ À VÉRIFIER |
|
||
| **Référence (ASIN)** | B0DWFLPMM5 | B0DWFLPMM5 | ✅ OK |
|
||
| **Image principale** | Image SSD Samsung | Image SSD Samsung | ✅ OK |
|
||
| **Catégorie** | SSD | SSD | ✅ OK |
|
||
|
||
## Défauts identifiés
|
||
|
||
### 1. ⚠️ AVERTISSEMENT: Stock "Inconnu" alors que produit disponible
|
||
|
||
**Problème**: Le stock est affiché comme "Inconnu" alors que le produit est clairement disponible (bouton "Ajouter au panier", livraison Prime).
|
||
|
||
**Données HTML** :
|
||
```
|
||
#availability: "" (vide)
|
||
#availability span: "" (vide)
|
||
Bouton "Ajouter au panier": Présent
|
||
Livraison Prime: Présent
|
||
```
|
||
|
||
**Cause racine** :
|
||
Amazon n'affiche pas toujours "En stock" explicitement. Pour certains produits, le `#availability` est vide mais le produit est disponible.
|
||
|
||
**Amélioration proposée** :
|
||
```python
|
||
# Si #availability est vide mais bouton panier présent → IN_STOCK
|
||
if not text and soup.select_one('#add-to-cart-button'):
|
||
return StockStatus.IN_STOCK, "Disponible (panier)", True
|
||
```
|
||
|
||
---
|
||
|
||
### 2. ✅ MSRP correct
|
||
|
||
Ce produit confirme que le MSRP fonctionne quand il est réellement affiché sur la page (329,99€ barré visible sur Amazon).
|
||
|
||
---
|
||
|
||
## Résumé produit 5
|
||
|
||
| Priorité | Problème | Récurrent ? |
|
||
|----------|----------|-------------|
|
||
| 🟡 P2 | Stock "Inconnu" malgré disponibilité (pattern #availability vide) | 🆕 Nouveau pattern |
|
||
|
||
**Note positive** : Prix, MSRP (-24%), note et catégorie sont tous corrects.
|
||
|
||
---
|
||
---
|
||
|
||
# Produit 4 : MSI NVIDIA GeForce RTX 5060 Ti
|
||
|
||
**URL**: https://www.amazon.fr/dp/B0F32N1ZGH
|
||
|
||
## Comparaison: Données Amazon vs Données Scrapées
|
||
|
||
| Champ | Valeur Amazon (source) | Valeur Scrapée (Web UI) | Statut |
|
||
|-------|------------------------|-------------------------|--------|
|
||
| **Titre** | MSI NVIDIA GeForce RTX 5060 Ti 16G Inspire 2X OC Carte Graphique 16 Go GDDR7... | MSI NVIDIA GeForce RTX 5060 Ti 16G Inspire 2X OC Carte Graphique 16 Go GDDR... | ✅ OK |
|
||
| **Prix** | 509,00 € | 509 € | ✅ OK |
|
||
| **Prix conseillé (MSRP)** | Non affiché sur Amazon | 102,99 € | ❌ ERREUR |
|
||
| **Stock** | "En stock" | "En stock" | ✅ OK |
|
||
| **Note** | 4,6 (211) | 4,6 (211) | ✅ OK |
|
||
| **Choix Amazon** | Oui (badge visible) | Oui | ✅ OK |
|
||
| **Référence (ASIN)** | B0F32N1ZGH | B0F32N1ZGH | ✅ OK |
|
||
| **Image principale** | Image carte graphique MSI | Image carte graphique MSI | ✅ OK |
|
||
| **Catégorie** | Cartes graphiques | Informatique | ✅ OK |
|
||
| **Type** | - | Ecran | ❌ ERREUR |
|
||
|
||
## Défauts identifiés
|
||
|
||
### 1. ❌ ERREUR: MSRP incorrect (102,99 €)
|
||
|
||
**Problème**: Le Web UI affiche "PRIX CONSEILLÉ: 102,99€" pour une carte graphique à 509€. C'est absurde (MSRP < prix actuel).
|
||
|
||
**Cause racine identifiée** :
|
||
Le prix 102,99€ provient d'un **produit sponsorisé** affiché sur la page.
|
||
|
||
```
|
||
Source HTML: #sp_detail_B08ZCHF59L (produit sponsorisé)
|
||
```
|
||
|
||
**Même problème récurrent** : Le sélecteur MSRP capture des prix barrés de produits tiers (sponsorisés, similaires, etc.).
|
||
|
||
---
|
||
|
||
### 2. ❌ ERREUR: Type "Ecran" incorrect
|
||
|
||
**Problème**: Le produit est une **carte graphique**, pas un écran. La classification automatique est erronée.
|
||
|
||
**Cause probable** : Le système de classification par mots-clés a mal catégorisé le produit.
|
||
|
||
---
|
||
|
||
## Résumé produit 4
|
||
|
||
| Priorité | Problème | Récurrent ? |
|
||
|----------|----------|-------------|
|
||
| 🟠 P1 | MSRP incorrect (102,99€ d'un produit sponsorisé) | ✅ Oui (3ème occurrence) |
|
||
| 🟡 P2 | Type "Ecran" au lieu de "Carte graphique" | 🆕 Erreur classification |
|
||
|
||
---
|
||
---
|
||
|
||
# Produit 3 : ASUS TUF Gaming A16
|
||
|
||
**URL**: https://www.amazon.fr/dp/B0DQ8M74KL?th=1
|
||
|
||
## Comparaison: Données Amazon vs Données Scrapées
|
||
|
||
| Champ | Valeur Amazon (source) | Valeur Scrapée (Web UI) | Statut |
|
||
|-------|------------------------|-------------------------|--------|
|
||
| **Titre** | ASUS TUF Gaming A16-TUF608UH-RV054W 16 Pouces FHD Plus 165Hz... | ASUS TUF Gaming A16-TUF608UH-RV054W 16 Pouces FHD Plus 165Hz... | ✅ OK |
|
||
| **Prix** | 1 259,99 € | 1 259,99 € | ✅ OK |
|
||
| **Prix conseillé (MSRP)** | 1 699,99 € | 1 699,99 € | ✅ OK |
|
||
| **Réduction** | -26 % | -26 % | ✅ OK |
|
||
| **Stock** | "En stock" | "En stock" | ✅ OK |
|
||
| **Note** | 4,7 (7) | 4,7 (7) | ✅ OK |
|
||
| **Choix Amazon** | ❌ Non (c'est "Exclusivité Amazon") | Oui | ❌ ERREUR |
|
||
| **Référence (ASIN)** | B0DQ8M74KL | B0DQ8M74KL | ✅ OK |
|
||
| **Image principale** | Image laptop ASUS TUF | Image laptop ASUS TUF | ✅ OK |
|
||
| **Catégorie** | Ordinateurs portables classiques | Informatique | ✅ OK |
|
||
| **Type** | - | PC portable | ✅ OK |
|
||
|
||
## Défauts identifiés
|
||
|
||
### 1. ❌ ERREUR: "Choix Amazon" faux positif
|
||
|
||
**Problème**: Le Web UI affiche "CHOIX AMAZON: Oui" alors qu'Amazon affiche **"Exclusivité Amazon"** (badge différent).
|
||
|
||
**Données HTML** :
|
||
```
|
||
#acBadge_feature_div: "" (vide)
|
||
Badge réel: "Exclusivité Amazon" (classe: ae-badge-rectangle-desktop)
|
||
```
|
||
|
||
**Cause racine** :
|
||
La méthode `_extract_amazon_choice()` détecte l'élément `#acBadge_feature_div` qui existe dans le DOM mais est **vide**. Le parser retourne `True` simplement parce que l'élément existe.
|
||
|
||
**Fichier concerné**: [store.py:496-520](pricewatch/app/stores/amazon/store.py#L496-L520) - méthode `_extract_amazon_choice()`
|
||
|
||
**Correction proposée** :
|
||
```python
|
||
def _extract_amazon_choice(self, soup, debug):
|
||
for selector in selectors:
|
||
element = soup.select_one(selector)
|
||
if element:
|
||
text = element.get_text(strip=True)
|
||
# Ne retourner True que si le texte contient vraiment "Choix d'Amazon"
|
||
if not text:
|
||
continue # Élément vide = pas de badge
|
||
if "choix d'amazon" in text.lower() or "amazon's choice" in text.lower():
|
||
return True, text
|
||
return None, None
|
||
```
|
||
|
||
---
|
||
|
||
### 2. ℹ️ OBSERVATION: Badge "Exclusivité Amazon" non capturé
|
||
|
||
**Opportunité** : Ajouter un champ `amazon_exclusive` pour capturer ce badge distinct.
|
||
|
||
```python
|
||
# Nouveau sélecteur
|
||
amazon_exclusive:
|
||
- "span.ae-badge-text-desktop"
|
||
- ".badge-wrapper:-soup-contains('Exclusivité')"
|
||
```
|
||
|
||
---
|
||
|
||
## Résumé produit 3
|
||
|
||
| Priorité | Problème | Récurrent ? |
|
||
|----------|----------|-------------|
|
||
| 🟡 P2 | Choix Amazon faux positif (élément vide) | 🆕 Nouveau pattern |
|
||
| 🟢 P3 | Badge "Exclusivité Amazon" non capturé | Opportunité |
|
||
|
||
**Note positive** : Ce produit montre que le MSRP est correctement capturé quand il existe réellement sur la page produit (1699,99€). Le problème MSRP des produits 1 et 2 vient bien de prix barrés de produits tiers.
|
||
|
||
---
|
||
---
|
||
|
||
# Produit 2 : Crucial Basics DDR4 8Go
|
||
|
||
**URL**: https://www.amazon.fr/dp/B0CWLSQ8FS
|
||
|
||
## Comparaison: Données Amazon vs Données Scrapées
|
||
|
||
| Champ | Valeur Amazon (source) | Valeur Scrapée (Web UI) | Statut |
|
||
|-------|------------------------|-------------------------|--------|
|
||
| **Titre** | Crucial Basics Mémoire pour Ordinateur de Bureau DDR4 3200 MT/s CL22 UDIMM 288 Broches 1,2 V 8 Go | Crucial Basics Mémoire pour Ordinateur de Bureau DDR4 3200 MT/s CL22 UDIMM 28... | ✅ OK |
|
||
| **Prix** | 80,00 € | 80 € | ✅ OK |
|
||
| **Prix conseillé (MSRP)** | Non affiché sur Amazon | 606,99 € | ❌ ERREUR |
|
||
| **Stock** | "Habituellement expédié sous 5 à 6 jours" | "Inconnu" | ❌ ERREUR |
|
||
| **Note** | 3,7 (27) | 3,7 (27) | ✅ OK |
|
||
| **Choix Amazon** | Non visible sur capture | Oui | ⚠️ À VÉRIFIER |
|
||
| **Référence (ASIN)** | B0CWLSQ8FS | B0CWLSQ8FS | ✅ OK |
|
||
| **Image principale** | Image RAM Crucial | Image RAM Crucial | ✅ OK |
|
||
|
||
## Défauts identifiés
|
||
|
||
### 1. ❌ ERREUR: MSRP incorrect (606,99 €)
|
||
|
||
**Problème**: Le Web UI affiche "PRIX CONSEILLÉ: 606,99€" alors qu'Amazon n'affiche aucun prix conseillé pour ce produit à 80€.
|
||
|
||
**Cause racine identifiée** :
|
||
Le prix 606,99€ provient de la section **"Découvrir et s'inspirer"** (`sims-discoveryAndInspiration_feature_div`), un produit similaire/recommandé.
|
||
|
||
```
|
||
Source HTML: #CardInstance5M3xhZWYVx9FloZeghy3og
|
||
Section parente: #sims-discoveryAndInspiration_feature_div_01
|
||
```
|
||
|
||
**Même problème que produit 1** : Le sélecteur MSRP (`span.a-text-price span.a-offscreen`) capture des prix barrés de produits tiers.
|
||
|
||
---
|
||
|
||
### 2. ❌ ERREUR: Stock "Inconnu" au lieu de "Expédié sous 5-6 jours"
|
||
|
||
**Problème**: Le stock est affiché comme "Inconnu" alors qu'Amazon indique clairement "Habituellement expédié sous 5 à 6 jours".
|
||
|
||
**Données HTML** :
|
||
```
|
||
#availability span: "Habituellement expédié sous 5 à 6 jours"
|
||
```
|
||
|
||
**Cause racine** :
|
||
Le parser ne reconnaît pas ce texte comme indicateur de stock. La méthode `_extract_stock_details()` cherche uniquement :
|
||
- "en stock" / "in stock" / "available" → IN_STOCK
|
||
- "rupture" / "indisponible" / "out of stock" → OUT_OF_STOCK
|
||
|
||
Le texte "expédié sous X jours" n'est pas mappé → retourne `UNKNOWN`.
|
||
|
||
**Fichier concerné**: [store.py:304-326](pricewatch/app/stores/amazon/store.py#L304-L326) - méthode `_extract_stock_details()`
|
||
|
||
**Correction proposée** :
|
||
```python
|
||
# Ajouter la reconnaissance des délais d'expédition
|
||
if "expédié sous" in normalized or "dispatched within" in normalized:
|
||
return StockStatus.IN_STOCK, text, True # Expédié = disponible
|
||
```
|
||
|
||
---
|
||
|
||
### 3. ⚠️ AVERTISSEMENT: Choix Amazon potentiellement incorrect
|
||
|
||
Le Web UI affiche "CHOIX AMAZON: Oui" mais le badge n'est pas clairement visible sur la capture Amazon.
|
||
|
||
**À vérifier** : Le badge peut être présent dans le HTML mais non visible sur la capture d'écran.
|
||
|
||
---
|
||
|
||
## Résumé produit 2
|
||
|
||
| Priorité | Problème | Récurrent ? |
|
||
|----------|----------|-------------|
|
||
| 🟠 P1 | MSRP incorrect (606,99€ d'un produit tiers) | ✅ Oui (même cause que produit 1) |
|
||
| 🟠 P1 | Stock "Inconnu" au lieu de "En stock (délai)" | 🆕 Nouveau pattern |
|
||
|
||
---
|
||
---
|
||
|
||
# Produit 1 : Corsair Vengeance LPX DDR4 32Go
|
||
|
||
**URL**: https://www.amazon.fr/dp/B07RW6Z692?th=1
|
||
|
||
---
|
||
|
||
## Comparaison: Données Amazon vs Données Scrapées
|
||
|
||
| Champ | Valeur Amazon (source) | Valeur Scrapée (Web UI) | Statut |
|
||
|-------|------------------------|-------------------------|--------|
|
||
| **Titre** | Corsair Vengeance LPX DDR4 RAM 32Go (2x16Go) 3200MHz CL16 Intel XMP 2.0 Mémoire d'ordinateur - Noir (CMK32GX4M2E3200C16) | Corsair Vengeance LPX DDR4 RAM 32Go (2x16Go) 3200MHz CL16 Intel XMP 2.0... | ⚠️ TRONQUÉ |
|
||
| **Prix** | 230,54 € | 230,54 € | ✅ OK |
|
||
| **Prix conseillé (MSRP)** | Non affiché sur Amazon | 139 € | ❌ ERREUR |
|
||
| **Stock** | "Plus de 500 achetés au cours du mois dernier" (implicitement en stock) | "En stock" | ✅ OK |
|
||
| **Note** | 4,8 | 4,8 | ✅ OK |
|
||
| **Nombre d'avis** | 28 245 | 28 245 | ✅ OK |
|
||
| **Choix Amazon** | Oui (badge visible) | Oui | ✅ OK |
|
||
| **Référence (ASIN)** | B07RW6Z692 | B07RW6Z692 | ✅ OK |
|
||
| **Image principale** | Image RAM Corsair Vengeance | Image processeur AMD Ryzen 7 | ❌ ERREUR CRITIQUE |
|
||
| **Catégorie** | Mémoire RAM | Mémoire RAM | ✅ OK |
|
||
|
||
---
|
||
|
||
## Défauts identifiés
|
||
|
||
### 1. ❌ ERREUR CRITIQUE: Mauvaise image produit
|
||
|
||
**Problème**: L'image affichée dans le Web UI est un processeur AMD Ryzen 7 5800X, alors que le produit est de la RAM Corsair Vengeance.
|
||
|
||
**Cause racine identifiée** :
|
||
L'image provient de la section **"Fréquemment achetés ensemble"** (`_p13n-desktop-sims-fbt`).
|
||
|
||
```
|
||
Image fautive: https://images-eu.ssl-images-amazon.com/images/I/61IIbwz-+ML._AC_UL116_SR116,116_.jpg
|
||
Contexte HTML: <a class="_p13n-desktop-sims-fbt_fbt-desktop_image-display__2oZhY asin-2">
|
||
```
|
||
|
||
**Données en base** :
|
||
```
|
||
ProductImage position=0 : 61IIbwz-+ML (Ryzen) ← INCORRECT
|
||
ProductImage position=1 : 61wCOVcyvFL (Corsair RAM) ← CORRECT
|
||
```
|
||
|
||
**Explication** :
|
||
- Le sélecteur `.a-dynamic-image` capture toutes les images dynamiques de la page
|
||
- L'image du Ryzen (dans "Fréquemment achetés ensemble") est trouvée AVANT l'image principale du produit
|
||
- Elle est donc insérée en position 0 (image principale)
|
||
|
||
**Fichier concerné**: [store.py:328-378](pricewatch/app/stores/amazon/store.py#L328-L378) - méthode `_extract_images()`
|
||
|
||
**Correction proposée** :
|
||
1. Prioriser `#landingImage` comme source unique pour l'image principale
|
||
2. Filtrer les images provenant de sections publicitaires (`_p13n-`, `sp_detail`, `sims-fbt`)
|
||
3. Ne pas utiliser `.a-dynamic-image` seul car trop large
|
||
|
||
---
|
||
|
||
### 2. ❌ ERREUR: Prix conseillé (MSRP) incorrect
|
||
|
||
**Problème**: Le Web UI affiche "PRIX CONSEILLÉ: 139€" alors qu'Amazon n'affiche aucun prix barré/conseillé pour ce produit.
|
||
|
||
**Cause racine identifiée** :
|
||
Le prix 139€ provient d'un **produit sponsorisé** affiché sur la page.
|
||
|
||
```
|
||
Source HTML: <div id="sp_detail2_B0FNMQQK58" class="sp_offerVertical p13n-asin sp_ltr_offer">
|
||
Texte: "Conseillé : 139,00 €"
|
||
```
|
||
|
||
**Données en base** :
|
||
```sql
|
||
Product.msrp = 139.00 ← Valeur stockée incorrecte
|
||
```
|
||
|
||
**Parser actuel** (test aujourd'hui) :
|
||
```python
|
||
snapshot.msrp = None ← Le parser actuel retourne correctement None
|
||
```
|
||
|
||
**Explication** :
|
||
- Un scraping précédent a capté ce prix sponsorisé comme MSRP
|
||
- Le sélecteur `span.a-text-price span.a-offscreen` est trop générique
|
||
- Il capture n'importe quel prix barré de la page (pubs, suggestions, etc.)
|
||
|
||
**Fichier concerné**: [store.py:276-285](pricewatch/app/stores/amazon/store.py#L276-L285) - méthode `_extract_msrp()`
|
||
|
||
**Correction proposée** :
|
||
1. Restreindre la recherche MSRP au conteneur principal du produit (`#corePrice_feature_div`, `#apex_desktop`)
|
||
2. Exclure explicitement les sections sponsorisées (`#sp_detail`, `.sp_offerVertical`)
|
||
3. Ajouter une validation : MSRP > prix actuel (sinon, ignorer)
|
||
|
||
---
|
||
|
||
### 3. ⚠️ AVERTISSEMENT: Titre tronqué
|
||
|
||
**Problème**: Le titre complet inclut "(CMK32GX4M2E3200C16)" qui semble coupé dans l'affichage.
|
||
|
||
**Données en base** :
|
||
```
|
||
Product.title = "Corsair Vengeance LPX DDR4 RAM 32Go (2x16Go) 3200MHz CL16 Intel XMP 2.0 Mémoire d'ordinateur - Noir (CMK32GX4M2E3200C16)"
|
||
```
|
||
|
||
**Verdict** : Le titre est complet en base. C'est un problème d'affichage UI (CSS truncation), pas de scraping.
|
||
|
||
---
|
||
|
||
### 4. ℹ️ DONNÉES NON SCRAPÉES (opportunités)
|
||
|
||
| Donnée Amazon | Présence | Priorité |
|
||
|---------------|----------|----------|
|
||
| Prix en 4x (58,96€ x4 mois) | ❌ | Basse |
|
||
| "Frais d'importation Inclus" | ❌ | Moyenne |
|
||
| Marque: Corsair | ❌ (dans specs) | Basse |
|
||
| Specs techniques (DDR4, 32Go, 3200MHz) | ✅ (table specs) | - |
|
||
| "Plus de 500 achetés" | ❌ | Basse |
|
||
| Options de taille (variantes) | ❌ | Haute |
|
||
|
||
---
|
||
|
||
## Analyse technique approfondie
|
||
|
||
### État actuel du parser (test du 2026-01-18)
|
||
|
||
```python
|
||
# Résultat du parsing sur HTML frais
|
||
snapshot.title = "Corsair Vengeance LPX DDR4..." ✅
|
||
snapshot.price = 230.54 ✅
|
||
snapshot.msrp = None ✅ (correct!)
|
||
snapshot.main_image = "61wCOVcyvFL" ✅ (image RAM correcte)
|
||
snapshot.rating_value = 4.8 ✅
|
||
snapshot.rating_count = 28246 ✅
|
||
snapshot.amazon_choice = True ✅
|
||
```
|
||
|
||
**Conclusion** : Le parser actuel fonctionne correctement. Les erreurs proviennent de **données historiques** en base issues d'un scraping précédent bugué.
|
||
|
||
### Données corrompues en base
|
||
|
||
| Table | Champ | Valeur actuelle | Valeur attendue |
|
||
|-------|-------|-----------------|-----------------|
|
||
| `products` | `msrp` | 139.00 | NULL |
|
||
| `product_images` | position=0 | 61IIbwz-+ML (Ryzen) | 61wCOVcyvFL (RAM) |
|
||
|
||
---
|
||
|
||
## Résumé des priorités
|
||
|
||
| Priorité | Problème | Action requise |
|
||
|----------|----------|----------------|
|
||
| 🔴 P0 | Image principale incorrecte en BDD | Nettoyer les données + corriger l'extraction |
|
||
| 🟠 P1 | MSRP incorrect en BDD | Nettoyer les données + restreindre le sélecteur MSRP |
|
||
| 🟢 P2 | Parser actuel OK | Valider que les corrections persistent |
|
||
|
||
---
|
||
|
||
## Recommandations d'amélioration
|
||
|
||
### 1. Correction extraction images
|
||
|
||
```python
|
||
# Prioriser #landingImage uniquement pour l'image principale
|
||
landing = soup.select_one('#landingImage')
|
||
if landing:
|
||
main_image = landing.get('data-old-hires') or landing.get('src')
|
||
|
||
# Filtrer les images de sections publicitaires
|
||
EXCLUDED_PARENTS = ['_p13n-', 'sp_detail', 'sims-fbt', 'sponsored']
|
||
def _is_product_image(self, element) -> bool:
|
||
for parent in element.parents:
|
||
classes = ' '.join(parent.get('class', []))
|
||
if any(excl in classes for excl in EXCLUDED_PARENTS):
|
||
return False
|
||
return True
|
||
```
|
||
|
||
### 2. Correction extraction MSRP
|
||
|
||
```python
|
||
# Restreindre au conteneur principal
|
||
price_container = soup.select_one('#corePrice_feature_div, #apex_desktop')
|
||
if price_container:
|
||
msrp_element = price_container.select_one('span.a-text-price span.a-offscreen')
|
||
|
||
# Validation : MSRP doit être > prix actuel
|
||
if msrp and price and msrp <= price:
|
||
msrp = None # Ignorer les valeurs incohérentes
|
||
```
|
||
|
||
### 3. Migration données existantes
|
||
|
||
```sql
|
||
-- Corriger le MSRP incorrect
|
||
UPDATE products SET msrp = NULL WHERE reference = 'B07RW6Z692' AND msrp = 139.00;
|
||
|
||
-- Supprimer l'image Ryzen incorrecte
|
||
DELETE FROM product_images
|
||
WHERE product_id = (SELECT id FROM products WHERE reference = 'B07RW6Z692')
|
||
AND image_url LIKE '%61IIbwz%';
|
||
|
||
-- Réordonner les positions
|
||
UPDATE product_images SET position = position - 1
|
||
WHERE product_id = (SELECT id FROM products WHERE reference = 'B07RW6Z692')
|
||
AND position > 0;
|
||
```
|
||
|
||
---
|
||
|
||
## Fichiers à modifier
|
||
|
||
1. **[store.py](pricewatch/app/stores/amazon/store.py)** - Logique d'extraction Amazon
|
||
- `_extract_images()` : filtrer les sections publicitaires
|
||
- `_extract_msrp()` : restreindre au conteneur principal
|
||
|
||
2. **[selectors.yml](pricewatch/app/stores/amazon/selectors.yml)** - Ajouter sélecteurs exclusion
|
||
|
||
3. **Migration SQL** - Nettoyer les données corrompues existantes
|
||
|
||
---
|
||
|
||
## Prochaines étapes
|
||
|
||
1. ✅ Identifier les causes racines (fait)
|
||
2. ⬜ Implémenter les corrections dans le parser
|
||
3. ✅ Nettoyer les données corrompues en base → **Workaround actuel : suppression + ré-ajout du produit**
|
||
4. ⬜ Re-scraper le produit pour valider
|
||
5. ⬜ Tester sur d'autres produits Amazon
|
||
|
||
---
|
||
|
||
## Note importante
|
||
|
||
**Workaround temporaire** : La correction des données corrompues se fait actuellement par suppression puis ré-ajout du produit depuis le Web UI. Cela fonctionne car le parser actuel est correct.
|
||
|
||
**Limitation** : Cette méthode supprime l'historique des prix du produit. Une correction du parser reste nécessaire pour éviter de futures corruptions.
|
||
|
||
---
|
||
|
||
# Plan de corrections
|
||
|
||
## 1. Infrastructure : Installer Playwright
|
||
|
||
```bash
|
||
playwright install
|
||
```
|
||
|
||
**Impact** : Évite le fallback HTTP qui capture des MSRP incorrects.
|
||
|
||
---
|
||
|
||
## 1bis. Stratégie de fetch : Playwright prioritaire pour Amazon
|
||
|
||
### Constat
|
||
|
||
| Méthode | Avantage | Inconvénient pour Amazon |
|
||
|---------|----------|--------------------------|
|
||
| HTTP | Rapide | HTML contient plus de sections sponsorisées/recommandations |
|
||
| Playwright | HTML "propre" après JS | Plus lent |
|
||
|
||
### Flux actuel
|
||
|
||
```
|
||
HTTP d'abord → Si échec → Playwright (fallback)
|
||
```
|
||
|
||
### Flux proposé pour Amazon
|
||
|
||
```
|
||
Playwright d'abord → Si échec → HTTP (fallback)
|
||
```
|
||
|
||
### Implémentation
|
||
|
||
Ajouter une propriété `prefer_playwright` dans `BaseStore` :
|
||
|
||
```python
|
||
# pricewatch/app/stores/base.py
|
||
class BaseStore:
|
||
prefer_playwright: bool = False # Par défaut HTTP first
|
||
|
||
# pricewatch/app/stores/amazon/store.py
|
||
class AmazonStore(BaseStore):
|
||
prefer_playwright: bool = True # Playwright first pour Amazon
|
||
```
|
||
|
||
Modifier `scrape_product()` dans `tasks/scrape.py` :
|
||
|
||
```python
|
||
# Déterminer l'ordre de fetch selon le store
|
||
if store.prefer_playwright and use_playwright:
|
||
# Playwright d'abord
|
||
pw_result = fetch_playwright(...)
|
||
if pw_result.success:
|
||
html = pw_result.html
|
||
fetch_method = FetchMethod.PLAYWRIGHT
|
||
else:
|
||
# Fallback HTTP
|
||
http_result = fetch_http(...)
|
||
...
|
||
else:
|
||
# HTTP d'abord (comportement actuel)
|
||
...
|
||
```
|
||
|
||
---
|
||
|
||
## 1ter. Vérification du flux UI preview/commit
|
||
|
||
### Constat
|
||
|
||
Le flux **preview/commit existe déjà** et fonctionne correctement :
|
||
|
||
| Action UI | Endpoint | Persistence |
|
||
|-----------|----------|-------------|
|
||
| "Ajouter" → Preview | `/scrape/preview` | ❌ Non (save_db=False) |
|
||
| "Enregistrer" → Commit | `/scrape/commit` | ✅ Oui |
|
||
|
||
**Conclusion** : Pas de modification nécessaire côté UI. Les données sont bien persistées uniquement sur action explicite de l'utilisateur.
|
||
|
||
---
|
||
|
||
## 2. Parser : Restreindre extraction MSRP
|
||
|
||
**Fichier** : [store.py](pricewatch/app/stores/amazon/store.py)
|
||
|
||
### Problème identifié (Produit 8 - UGREEN)
|
||
|
||
Le parser utilise `select_one()` qui retourne le **premier** élément trouvé. Or Amazon affiche maintenant :
|
||
|
||
```
|
||
Prix actuel: 118,99€
|
||
Prix le plus bas des 30 derniers jours: 127,49€ ← PREMIER dans le DOM
|
||
Prix conseillé: 169,99€ (-30%) ← SECOND dans le DOM
|
||
```
|
||
|
||
**Résultat** : Le parser capture 127,49€ (prix bas 30j) au lieu de 169,99€ (MSRP).
|
||
|
||
### Analyse HTML réelle
|
||
|
||
```python
|
||
# Ordre des éléments span.a-text-price span.a-offscreen dans #corePriceDisplay_desktop_feature_div:
|
||
# 0: 127,49€ ← Prix le plus bas 30 jours (directive Omnibus UE)
|
||
# 1: 169,99€ ← Prix conseillé (MSRP réel)
|
||
```
|
||
|
||
### Structure HTML identifiée
|
||
|
||
Les deux prix sont dans des sections distinctes avec des classes CSS spécifiques :
|
||
|
||
```html
|
||
<!-- Prix le plus bas 30 jours (directive Omnibus UE) -->
|
||
<span class="basisPrice">
|
||
"Prix le plus bas des 30 derniers jours : "
|
||
<span class="a-price a-text-price" data-a-strike="true">
|
||
<span class="a-offscreen">127,49€</span>
|
||
</span>
|
||
</span>
|
||
|
||
<!-- Prix conseillé (MSRP) -->
|
||
<span class="srpPriceBlock">
|
||
"Prix conseillé : "
|
||
<span class="a-price a-text-price srpPriceBlockAUI">
|
||
<span class="a-offscreen">169,99€</span>
|
||
</span>
|
||
</span>
|
||
<span class="srpSavingsPercentageBlock">-30%</span>
|
||
```
|
||
|
||
### Classes identifiantes
|
||
|
||
| Élément | Classe CSS | Contenu |
|
||
|---------|------------|---------|
|
||
| Prix bas 30j | `basisPrice` | Prix le plus bas des 30 derniers jours |
|
||
| MSRP | `srpPriceBlock`, `srpPriceBlockAUI` | Prix conseillé |
|
||
|
||
### Correction proposée (méthode précise)
|
||
|
||
```python
|
||
def _extract_msrp(self, soup, debug):
|
||
"""Extrait le prix conseillé (MSRP) via les classes CSS spécifiques."""
|
||
# Méthode 1: Cibler directement srpPriceBlock (plus précis)
|
||
srp_block = soup.select_one('span.srpPriceBlock .a-offscreen, span.srpPriceBlockAUI .a-offscreen')
|
||
if srp_block:
|
||
return parse_price_text(srp_block.get_text(strip=True))
|
||
|
||
# Méthode 2: Fallback - chercher dans le conteneur prix en excluant basisPrice
|
||
price_container = soup.select_one('#corePriceDisplay_desktop_feature_div, #corePrice_feature_div, #apex_desktop')
|
||
if price_container:
|
||
for strike in price_container.select('span.a-text-price span.a-offscreen'):
|
||
# Exclure les prix dans basisPrice (prix bas 30j)
|
||
if not strike.find_parent(class_='basisPrice'):
|
||
return parse_price_text(strike.get_text(strip=True))
|
||
|
||
return None
|
||
```
|
||
|
||
### Avantages de cette approche
|
||
|
||
| Approche | Avantage | Inconvénient |
|
||
|----------|----------|--------------|
|
||
| `max()` des prix | Simple | Peut échouer si MSRP < prix bas 30j (rare) |
|
||
| Cibler `srpPriceBlock` | Précis, robuste | Dépend de la classe CSS Amazon |
|
||
| Exclure `basisPrice` | Fiable | Fallback si srpPriceBlock absent |
|
||
|
||
**Recommandation** : Combiner les deux méthodes (cibler srpPriceBlock en priorité, fallback avec exclusion basisPrice).
|
||
|
||
---
|
||
|
||
## 3. Parser : Reconnaître "expédié sous" comme stock disponible
|
||
|
||
**Fichier** : [store.py](pricewatch/app/stores/amazon/store.py)
|
||
|
||
```python
|
||
def _extract_stock_details(self, soup, debug):
|
||
# ... code existant ...
|
||
normalized = text.lower()
|
||
|
||
# Ajouter reconnaissance délai d'expédition
|
||
if "expédié sous" in normalized or "dispatched within" in normalized:
|
||
return StockStatus.IN_STOCK, text, True
|
||
|
||
# Ajouter reconnaissance bouton panier si #availability vide
|
||
if not text and soup.select_one('#add-to-cart-button'):
|
||
return StockStatus.IN_STOCK, "Disponible", True
|
||
```
|
||
|
||
---
|
||
|
||
## 4. Parser : Corriger détection Amazon Choice
|
||
|
||
**Fichier** : [store.py](pricewatch/app/stores/amazon/store.py)
|
||
|
||
```python
|
||
def _extract_amazon_choice(self, soup, debug):
|
||
element = soup.select_one('#acBadge_feature_div')
|
||
if element:
|
||
text = element.get_text(strip=True)
|
||
# Ne retourner True que si le texte contient vraiment le badge
|
||
if text and ("choix d'amazon" in text.lower() or "amazon's choice" in text.lower()):
|
||
return True, text
|
||
return None, None
|
||
```
|
||
|
||
---
|
||
|
||
## 5. UI : Ne pas afficher "Stock: Inconnu"
|
||
|
||
Si le stock n'est pas détecté, ne pas afficher la ligne plutôt que "Inconnu".
|
||
|
||
---
|
||
|
||
## 6. UI : Supprimer "EVOL. 30 JOURS"
|
||
|
||
Cette donnée est calculée, pas scrapée. Elle ne doit pas apparaître dans le bloc d'informations produit.
|
||
|
||
---
|
||
|
||
## 7. Classification : Améliorer règles de type
|
||
|
||
| Mot-clé actuel | Problème | Correction |
|
||
|----------------|----------|------------|
|
||
| "GeForce", "RTX" → Ecran | Carte graphique mal classée | Ajouter règle "Carte graphique" |
|
||
| "SODIMM" → PC portable | RAM mal classée | Prioriser "RAM" si DDR présent |
|
||
|
||
---
|
||
|
||
## Checklist de validation
|
||
|
||
- [ ] Playwright installé et fonctionnel
|
||
- [ ] MSRP = None pour produits sans prix barré sur Amazon
|
||
- [ ] MSRP > prix actuel (validation obligatoire)
|
||
- [ ] Stock "expédié sous X jours" → En stock
|
||
- [ ] Amazon Choice = False si élément vide
|
||
- [ ] UI ne montre pas "Stock: Inconnu"
|
||
- [ ] UI ne montre pas "EVOL. 30 JOURS"
|
||
- [ ] Classification correcte GPU / RAM
|
||
|
||
---
|
||
---
|
||
|
||
# SYNTHÈSE FINALE
|
||
|
||
## Produits analysés
|
||
|
||
| # | Produit | ASIN | Erreurs principales |
|
||
|---|---------|------|---------------------|
|
||
| 1 | Corsair Vengeance LPX DDR4 32Go | B07RW6Z692 | MSRP incorrect (139€), Image incorrecte (Ryzen) |
|
||
| 2 | Crucial Basics DDR4 8Go | B0CWLSQ8FS | MSRP incorrect (606,99€), Stock "Inconnu" |
|
||
| 3 | ASUS TUF Gaming A16 | B0DQ8M74KL | Amazon Choice faux positif |
|
||
| 4 | MSI RTX 5060 Ti | B0F32N1ZGH | MSRP incorrect (102,99€), Type "Ecran" |
|
||
| 5 | Samsung SSD 9100 Pro | B0DWFLPMM5 | Stock "Inconnu" (produit disponible) |
|
||
| 6 | Corsair Vengeance SODIMM 32Go | B08GSTF5NJ | MSRP incorrect (606,99€), Type "PC portable" |
|
||
| 7 | MSI Modern MD2412P Écran | B0CB4FDJT5 | ✅ OK (MSRP correct car affiché sur Amazon) |
|
||
| 8 | UGREEN Revodok Pro | B0FGHX59G2 | MSRP = prix bas 30j (127,49€), Amazon Choice faux positif, Type "Ecran" |
|
||
| 9 | Anker Prime Battery | B0BYNZXFM2 | MSRP < prix (39,99€ < 99,99€), Amazon Choice faux positif |
|
||
|
||
**Statistiques** : 8/9 produits avec erreurs, 1/9 correct
|
||
|
||
---
|
||
|
||
## Erreurs identifiées et solutions
|
||
|
||
### 🔴 ERREUR 1 : MSRP incorrect (prix de produits tiers)
|
||
|
||
**Fréquence** : 6/9 produits
|
||
|
||
**Symptômes** :
|
||
- MSRP provient de produits sponsorisés (`#sp_detail_*`)
|
||
- MSRP provient de recommandations (`#CardInstance*`, `#sims-*`)
|
||
- MSRP < prix actuel (absurde)
|
||
|
||
**Causes** :
|
||
1. Sélecteur `span.a-text-price span.a-offscreen` trop large (toute la page)
|
||
2. `select_one()` prend le 1er élément trouvé (peut être prix bas 30j)
|
||
3. Fallback HTTP expose plus de sections sponsorisées que Playwright
|
||
|
||
**Solutions** :
|
||
```python
|
||
def _extract_msrp(self, soup, debug, current_price):
|
||
# 1. Cibler directement la classe srpPriceBlock
|
||
srp = soup.select_one('span.srpPriceBlock .a-offscreen, span.srpPriceBlockAUI .a-offscreen')
|
||
if srp:
|
||
msrp = parse_price_text(srp.get_text(strip=True))
|
||
if msrp and current_price and msrp > current_price:
|
||
return msrp
|
||
|
||
# 2. Fallback: conteneur principal, exclure basisPrice
|
||
container = soup.select_one('#corePriceDisplay_desktop_feature_div, #corePrice_feature_div')
|
||
if container:
|
||
for strike in container.select('span.a-text-price span.a-offscreen'):
|
||
if not strike.find_parent(class_='basisPrice'):
|
||
msrp = parse_price_text(strike.get_text(strip=True))
|
||
if msrp and current_price and msrp > current_price:
|
||
return msrp
|
||
|
||
return None # Pas de MSRP valide
|
||
```
|
||
|
||
**Validation obligatoire** : `MSRP > prix actuel` (sinon ignorer)
|
||
|
||
---
|
||
|
||
### 🔴 ERREUR 2 : Amazon Choice faux positif
|
||
|
||
**Fréquence** : 3/9 produits
|
||
|
||
**Symptôme** : "CHOIX AMAZON: Oui" alors qu'aucun badge visible
|
||
|
||
**Cause** : L'élément `#acBadge_feature_div` existe dans le DOM mais est vide
|
||
|
||
**Solution** :
|
||
```python
|
||
def _extract_amazon_choice(self, soup, debug):
|
||
element = soup.select_one('#acBadge_feature_div')
|
||
if element:
|
||
text = element.get_text(strip=True)
|
||
# Vérifier que le texte contient vraiment le badge
|
||
if text and ("choix d'amazon" in text.lower() or "amazon's choice" in text.lower()):
|
||
return True, text
|
||
return None, None # Pas de badge
|
||
```
|
||
|
||
---
|
||
|
||
### 🟠 ERREUR 3 : Stock "Inconnu" affiché
|
||
|
||
**Fréquence** : 3/9 produits
|
||
|
||
**Symptômes** :
|
||
- "Habituellement expédié sous 5-6 jours" → Stock "Inconnu"
|
||
- Produit disponible (bouton panier) mais `#availability` vide → Stock "Inconnu"
|
||
|
||
**Causes** :
|
||
1. Parser ne reconnaît pas "expédié sous" comme indicateur de disponibilité
|
||
2. UI affiche "Inconnu" au lieu de masquer le champ
|
||
|
||
**Solutions** :
|
||
|
||
*Parser :*
|
||
```python
|
||
def _extract_stock_details(self, soup, debug):
|
||
# Reconnaître délai d'expédition
|
||
if "expédié sous" in normalized or "dispatched within" in normalized:
|
||
return StockStatus.IN_STOCK, text, True
|
||
|
||
# Détecter disponibilité via bouton panier
|
||
if not text and soup.select_one('#add-to-cart-button'):
|
||
return StockStatus.IN_STOCK, "Disponible", True
|
||
```
|
||
|
||
*UI :* Ne pas afficher "Stock: Inconnu" - masquer le champ si non détecté
|
||
|
||
---
|
||
|
||
### 🟠 ERREUR 4 : "EVOL. 30 JOURS" affiché
|
||
|
||
**Fréquence** : 9/9 produits (tous)
|
||
|
||
**Symptôme** : UI affiche "+0.0%" ou autre valeur calculée
|
||
|
||
**Cause** : Donnée calculée côté frontend, non scrapée
|
||
|
||
**Règle** : Seules les données présentes sur Amazon doivent être affichées
|
||
|
||
**Solution** : Supprimer la ligne "EVOL. 30 JOURS" du bloc d'informations produit
|
||
|
||
---
|
||
|
||
### 🟡 ERREUR 5 : Classification de type incorrecte
|
||
|
||
**Fréquence** : 3/9 produits
|
||
|
||
**Exemples** :
|
||
- Carte graphique RTX → "Ecran"
|
||
- RAM SODIMM → "PC portable"
|
||
- Docking station → "Ecran"
|
||
|
||
**Cause** : Règles de classification par mots-clés trop simplistes
|
||
|
||
**Solutions** :
|
||
|
||
| Mot-clé | Classification actuelle | Classification correcte |
|
||
|---------|------------------------|------------------------|
|
||
| RTX, GeForce, Radeon | Ecran | Carte graphique |
|
||
| DDR4, DDR5, SODIMM, DIMM | PC portable | RAM / Mémoire |
|
||
| DisplayPort, HDMI (seul) | Ecran | Vérifier contexte (docking ≠ écran) |
|
||
|
||
---
|
||
|
||
### 🟡 ERREUR 6 : Playwright non installé
|
||
|
||
**Symptôme** : Erreur `Executable doesn't exist at /root/.cache/ms-playwright/chromium_headless_shell-1200`
|
||
|
||
**Impact** : Fallback HTTP capture plus d'erreurs MSRP
|
||
|
||
**Solution** :
|
||
```bash
|
||
playwright install
|
||
```
|
||
|
||
---
|
||
|
||
## Priorités de correction
|
||
|
||
| Priorité | Correction | Impact | Effort |
|
||
|----------|------------|--------|--------|
|
||
| 🔴 P0 | Restreindre extraction MSRP + validation | 6 produits corrigés | Moyen |
|
||
| 🔴 P0 | Corriger détection Amazon Choice | 3 produits corrigés | Faible |
|
||
| 🟠 P1 | Installer Playwright | Réduit erreurs MSRP | Faible |
|
||
| 🟠 P1 | Améliorer détection stock | 3 produits corrigés | Faible |
|
||
| 🟠 P1 | Supprimer "EVOL. 30 JOURS" de l'UI | 9 produits corrigés | Faible |
|
||
| 🟡 P2 | Améliorer classification types | 3 produits corrigés | Moyen |
|
||
| 🟡 P2 | Masquer "Stock: Inconnu" dans l'UI | UX améliorée | Faible |
|
||
|
||
---
|
||
|
||
## Fichiers impactés par les corrections
|
||
|
||
### Backend - Parser Amazon
|
||
|
||
| Fichier | Fonction/Section | Modification | Erreur corrigée |
|
||
|---------|------------------|--------------|-----------------|
|
||
| [store.py](pricewatch/app/stores/amazon/store.py) | `_extract_msrp()` (L276-285) | Cibler `srpPriceBlock`, exclure `basisPrice`, valider MSRP > prix | ERREUR 1 |
|
||
| [store.py](pricewatch/app/stores/amazon/store.py) | `_extract_amazon_choice()` (L496-520) | Vérifier texte non vide avant retourner True | ERREUR 2 |
|
||
| [store.py](pricewatch/app/stores/amazon/store.py) | `_extract_stock_details()` (L304-326) | Reconnaître "expédié sous", détecter bouton panier | ERREUR 3 |
|
||
| [selectors.yml](pricewatch/app/stores/amazon/selectors.yml) | Section `msrp` | Ajouter `srpPriceBlock`, `srpPriceBlockAUI`, `basisPrice` | ERREUR 1 |
|
||
|
||
### Backend - Infrastructure
|
||
|
||
| Fichier | Fonction/Section | Modification | Erreur corrigée |
|
||
|---------|------------------|--------------|-----------------|
|
||
| [base.py](pricewatch/app/stores/base.py) | Classe `BaseStore` | Ajouter attribut `prefer_playwright: bool = False` | ERREUR 6 |
|
||
| [store.py](pricewatch/app/stores/amazon/store.py) | Classe `AmazonStore` | Définir `prefer_playwright = True` | ERREUR 6 |
|
||
| [scrape.py](pricewatch/app/tasks/scrape.py) | `scrape_product()` (L83-118) | Inverser ordre fetch si `store.prefer_playwright` | ERREUR 6 |
|
||
|
||
### Frontend - Web UI
|
||
|
||
| Fichier | Fonction/Section | Modification | Erreur corrigée |
|
||
|---------|------------------|--------------|-----------------|
|
||
| [App.vue](webui/src/App.vue) | Template carte produit | Supprimer ligne "EVOL. 30 JOURS" | ERREUR 4 |
|
||
| [App.vue](webui/src/App.vue) | Template carte produit | Masquer "Stock: Inconnu" (afficher seulement si stock connu) | ERREUR 3 |
|
||
|
||
### Base de données - Classification
|
||
|
||
| Fichier | Table/Section | Modification | Erreur corrigée |
|
||
|---------|---------------|--------------|-----------------|
|
||
| [repository.py](pricewatch/app/db/repository.py) | `apply_classification()` | Vérifier règles de classification | ERREUR 5 |
|
||
| Table `classification_rules` | Données | Ajouter règle "Carte graphique" (RTX, GeForce, Radeon) | ERREUR 5 |
|
||
| Table `classification_rules` | Données | Ajouter règle "RAM / Mémoire" (DDR4, DDR5, SODIMM, DIMM) | ERREUR 5 |
|
||
| Table `classification_rules` | Données | Modifier règle "Ecran" (exclure si contexte docking/carte graphique) | ERREUR 5 |
|
||
|
||
---
|
||
|
||
## Matrice de traçabilité Erreur → Fichiers
|
||
|
||
| Erreur | Fichiers impactés |
|
||
|--------|-------------------|
|
||
| **ERREUR 1** : MSRP incorrect | `store.py`, `selectors.yml` |
|
||
| **ERREUR 2** : Amazon Choice faux positif | `store.py` |
|
||
| **ERREUR 3** : Stock "Inconnu" | `store.py`, `App.vue` |
|
||
| **ERREUR 4** : "EVOL. 30 JOURS" | `App.vue` |
|
||
| **ERREUR 5** : Classification type | `repository.py`, table `classification_rules` |
|
||
| **ERREUR 6** : Playwright non installé | `base.py`, `store.py` (Amazon), `scrape.py` |
|
||
|
||
---
|
||
|
||
## Ordre de modification recommandé
|
||
|
||
1. **Installer Playwright** (prérequis)
|
||
```bash
|
||
playwright install
|
||
```
|
||
|
||
2. **Parser Amazon** (`store.py`)
|
||
- `_extract_msrp()` - correction principale
|
||
- `_extract_amazon_choice()` - correction simple
|
||
- `_extract_stock_details()` - amélioration
|
||
|
||
3. **Sélecteurs** (`selectors.yml`)
|
||
- Ajouter nouveaux sélecteurs MSRP
|
||
|
||
4. **Web UI** (`App.vue`)
|
||
- Supprimer "EVOL. 30 JOURS"
|
||
- Masquer "Stock: Inconnu"
|
||
|
||
5. **Classification** (BDD + `repository.py`)
|
||
- Ajouter règles manquantes
|
||
- Ajuster priorités
|
||
|
||
6. **Infrastructure fetch** (`base.py`, `scrape.py`)
|
||
- Optionnel : Playwright prioritaire pour Amazon
|
||
|
||
---
|
||
|
||
## Conclusion
|
||
|
||
L'analyse de 9 produits Amazon a révélé des erreurs systématiques principalement liées à :
|
||
|
||
1. **Extraction MSRP trop permissive** - Le parser capture des prix de produits tiers car le sélecteur n'est pas restreint au bloc prix principal
|
||
|
||
2. **Détection Amazon Choice sans validation** - Le parser retourne `True` si l'élément existe, même s'il est vide
|
||
|
||
3. **Affichage de données calculées** - L'UI affiche des données non présentes sur Amazon
|
||
|
||
Les corrections proposées sont relativement simples à implémenter et corrigeraient 8/9 produits testés. Le produit 7 (MSI Écran) était déjà correct car il possède un vrai MSRP affiché sur Amazon.
|
||
|
||
---
|
||
---
|
||
|
||
# PROPOSITION DE CORRECTION VALIDÉE
|
||
|
||
## 1. MSRP – extraction trop permissive
|
||
|
||
**Problème** : Le sélecteur global `span.a-text-price span.a-offscreen` attrape des prix barrés dans des sections non-produit (sponsorisé, recommandations, "prix bas 30j").
|
||
|
||
**Solution détaillée (backend)** :
|
||
- Cibler d'abord la zone prix du produit principal : `#corePriceDisplay_desktop_feature_div`, `#corePrice_feature_div`, `#apex_desktop`
|
||
- Dans ce conteneur, prioriser `span.srpPriceBlock .a-offscreen` ou `span.srpPriceBlockAUI .a-offscreen`
|
||
- Exclure `basisPrice` (directive Omnibus "prix bas 30 jours")
|
||
- **Validation métier obligatoire** : ignorer MSRP si `msrp <= price` (ou si price absent)
|
||
|
||
**Cas limites** :
|
||
- MSRP présent mais prix courant absent (produit indisponible) → garder MSRP si cohérent, mais marquer debug
|
||
- MSRP en devise/format inattendu → garder `None` et tracer
|
||
|
||
**Fichiers** : `store.py`, `selectors.yml`
|
||
|
||
---
|
||
|
||
## 2. MSRP – 1er match "prix bas 30j"
|
||
|
||
**Problème** : `select_one()` prend le premier prix barré, parfois le "prix bas 30j".
|
||
|
||
**Solution détaillée** :
|
||
- Si `srpPriceBlock` absent, parcourir tous les `span.a-text-price span.a-offscreen` du conteneur prix
|
||
- Exclure ceux dont un parent a la classe `basisPrice`
|
||
- Si plusieurs candidats, prendre le plus élevé uniquement si price est présent et `msrp > price`
|
||
|
||
**Fichier** : `store.py`
|
||
|
||
---
|
||
|
||
## 3. Amazon Choice faux positif
|
||
|
||
**Problème** : `#acBadge_feature_div` existe mais vide → renvoyé `True`.
|
||
|
||
**Solution détaillée** :
|
||
- Vérifier texte non vide + correspondance stricte ("choix d'amazon" ou "amazon's choice")
|
||
- Si vide → `False` + conserver debug avec le DOM trouvé
|
||
|
||
**Fichier** : `store.py`
|
||
|
||
---
|
||
|
||
## 4. Stock – "expédié sous X jours"
|
||
|
||
**Problème** : Texte d'expédition n'est pas reconnu comme dispo.
|
||
|
||
**Solution détaillée** :
|
||
- Normaliser le texte (`lower()`, `strip()`)
|
||
- Pattern matching :
|
||
- `expédié sous`, `habituellement expédié sous`, `dispatched within` → `IN_STOCK`
|
||
- Si `#availability` vide mais `#add-to-cart-button` présent → `IN_STOCK` ("Disponible")
|
||
- **UI** : si le statut reste `UNKNOWN`, ne rien afficher
|
||
|
||
**Fichiers** : `store.py`, `App.vue`
|
||
|
||
---
|
||
|
||
## 5. Images – mauvaise image principale
|
||
|
||
**Problème** : `.a-dynamic-image` agrège images de sections tiers (sims, fbt, ads).
|
||
|
||
**Solution détaillée** :
|
||
- Prioriser `#landingImage` (ou `img#imgTagWrapperId img`)
|
||
- Filtrer toute image dont un parent contient `_p13n-`, `sims-fbt`, `sp_detail`, `sponsored`
|
||
- Garder un fallback seulement si `landingImage` absent
|
||
|
||
**Fichier** : `store.py`
|
||
|
||
---
|
||
|
||
## 6. UI – "EVOL. 30 JOURS"
|
||
|
||
**Problème** : Info calculée non présente sur Amazon.
|
||
|
||
**Solution détaillée** : Retirer ce champ du bloc produit et garder l'historique dans le graphique si besoin.
|
||
|
||
**Fichier** : `App.vue`
|
||
|
||
---
|
||
|
||
## 7. Fetch Amazon – Playwright prioritaire
|
||
|
||
**Problème** : Fallback HTTP expose plus de sections sponsorisées.
|
||
|
||
**Solution détaillée** :
|
||
- Attribut `prefer_playwright` dans `BaseStore`
|
||
- Inverser l'ordre dans `scrape_product()` si store le demande
|
||
|
||
**Risque** : Temps de scraping plus long, besoin de navigateur installé.
|
||
|
||
**Fichiers** : `base.py`, `store.py`, `scrape.py`
|
||
|
||
---
|
||
|
||
## 8. Classification – type incorrect
|
||
|
||
**Problème** : Règles mots-clés trop générales.
|
||
|
||
**Solution détaillée** :
|
||
- Règles prioritaires :
|
||
- **GPU** : `RTX|GeForce|Radeon|GPU|Carte graphique`
|
||
- **RAM** : `DDR4|DDR5|SODIMM|DIMM|mémoire`
|
||
- **Docking** : `dock|hub|station` → ne pas classer en "Ecran"
|
||
- Appliquer priorité stricte (GPU > écran, RAM > PC portable)
|
||
|
||
**Fichiers** : `repository.py` et/ou table `classification_rules`
|
||
|
||
---
|
||
|
||
## 9. Données corrompues existantes
|
||
|
||
**Problème** : MSRP et image déjà stockés incorrectement.
|
||
|
||
**Solution détaillée** :
|
||
- Migration ou script de correction ciblée (par ASIN)
|
||
- Alternative : purge + re-scrape (perte historique)
|
||
|
||
**Fichiers** : SQL ou script dédié (à définir)
|
||
|
||
---
|
||
|
||
## Plan d'implémentation recommandé
|
||
|
||
### Phase 1 : Corrections critiques (P0)
|
||
|
||
| Étape | Action | Fichier | Test |
|
||
|-------|--------|---------|------|
|
||
| 1.1 | Installer Playwright | Terminal | `playwright install` |
|
||
| 1.2 | Corriger `_extract_msrp()` | `store.py` | Produits 1,2,4,6,8,9 |
|
||
| 1.3 | Corriger `_extract_amazon_choice()` | `store.py` | Produits 3,8,9 |
|
||
| 1.4 | Ajouter sélecteurs MSRP | `selectors.yml` | - |
|
||
|
||
### Phase 2 : Corrections importantes (P1)
|
||
|
||
| Étape | Action | Fichier | Test |
|
||
|-------|--------|---------|------|
|
||
| 2.1 | Corriger `_extract_stock_details()` | `store.py` | Produits 2,5 |
|
||
| 2.2 | Corriger `_extract_images()` | `store.py` | Produit 1 |
|
||
| 2.3 | Supprimer "EVOL. 30 JOURS" | `App.vue` | Tous produits |
|
||
| 2.4 | Masquer "Stock: Inconnu" | `App.vue` | Produits 2,5 |
|
||
|
||
### Phase 3 : Améliorations (P2)
|
||
|
||
| Étape | Action | Fichier | Test |
|
||
|-------|--------|---------|------|
|
||
| 3.1 | Ajouter règles classification | BDD | Produits 4,6,8 |
|
||
| 3.2 | Ajouter `prefer_playwright` | `base.py`, `scrape.py` | Amazon |
|
||
| 3.3 | Script correction données | SQL | Produits existants |
|
||
|
||
---
|
||
|
||
## Validation finale
|
||
|
||
Après implémentation, re-tester les 9 produits :
|
||
|
||
| # | ASIN | Erreurs avant | Attendu après |
|
||
|---|------|---------------|---------------|
|
||
| 1 | B07RW6Z692 | MSRP, Image | ✅ OK |
|
||
| 2 | B0CWLSQ8FS | MSRP, Stock | ✅ OK |
|
||
| 3 | B0DQ8M74KL | Amazon Choice | ✅ OK |
|
||
| 4 | B0F32N1ZGH | MSRP, Type | ✅ OK |
|
||
| 5 | B0DWFLPMM5 | Stock | ✅ OK |
|
||
| 6 | B08GSTF5NJ | MSRP, Type | ✅ OK |
|
||
| 7 | B0CB4FDJT5 | ✅ OK | ✅ OK |
|
||
| 8 | B0FGHX59G2 | MSRP, Amazon Choice, Type | ✅ OK |
|
||
| 9 | B0BYNZXFM2 | MSRP, Amazon Choice | ✅ OK |
|