Files
scrap/verif_amazon.md
Gilles Soulier e18976ad51 before maj scrap
2026-01-18 07:38:37 +01:00

47 KiB
Raw Permalink Blame History

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

# 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 AmazonNe 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 :

# 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 - méthode _extract_amazon_choice()

Correction proposée :

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.

# 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 - méthode _extract_stock_details()

Correction proposée :

# 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 - 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 :

Product.msrp = 139.00   Valeur stockée incorrecte

Parser actuel (test aujourd'hui) :

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 - 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)

# 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

# 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

# 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

-- 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 - Logique d'extraction Amazon

    • _extract_images() : filtrer les sections publicitaires
    • _extract_msrp() : restreindre au conteneur principal
  2. 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

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 :

# 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 :

# 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

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

# 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 :

<!-- 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)

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

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

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 :

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 :

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 :

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 :

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 _extract_msrp() (L276-285) Cibler srpPriceBlock, exclure basisPrice, valider MSRP > prix ERREUR 1
store.py _extract_amazon_choice() (L496-520) Vérifier texte non vide avant retourner True ERREUR 2
store.py _extract_stock_details() (L304-326) Reconnaître "expédié sous", détecter bouton panier ERREUR 3
selectors.yml Section msrp Ajouter srpPriceBlock, srpPriceBlockAUI, basisPrice ERREUR 1

Backend - Infrastructure

Fichier Fonction/Section Modification Erreur corrigée
base.py Classe BaseStore Ajouter attribut prefer_playwright: bool = False ERREUR 6
store.py Classe AmazonStore Définir prefer_playwright = True ERREUR 6
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 Template carte produit Supprimer ligne "EVOL. 30 JOURS" ERREUR 4
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 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)

    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 withinIN_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