# 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: ``` **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:
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 des 30 derniers jours : " 127,49€ "Prix conseillé : " 169,99€ -30% ``` ### 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 |