Skip to content

CDucloux/Wine-Scraping

Repository files navigation

🍷 Wine-Scraping

RepoSize CodeSize Language CodeCoverage License

Il y a bien longtemps, dans une campagne lointaine, très lointaine...

ℹ️ Cliquez sur le logo pour accéder à l'application !

Le terroir est en guerre ! Menant une lutte acharnée pour l'information viticole, une bataille épique se déroule entre les données dissimulées et les amateurs assoiffés de connaissances sur le vin. Le chaos règne alors que les sources d'informations viticoles sont assaillies par des obstacles inattendus.

Avec une audace stupéfiante, les sites de revente de vin ont érigé des barrières insurmontables, empêchant l'accès aux détails les plus précieux sur les cépages, les millésimes et les appellations. La quête de ces informations devient une mission désespérée, une véritable lutte pour la liberté de l'information œnologique.

Face à cette situation, deux valeureux Chevaliers de la programmation Python s'élèvent pour secourir les amateurs de vin, menant une mission périlleuse pour libérer les données captives. Avec bravoure, ils s'attaquent aux défenses numériques pour délivrer les précieuses informations contenues dans les pages web tentaculaires...

Table des matières

Description

L'objectif de ce projet est de récupérer des données sur un site web, les stocker, les transformer puis les exploiter pour faire des modèles de Machine Learning ainsi qu'une application.

⚠️Si vous êtes intéréssé uniquement par l'utilisation de l'application et non pas les considérations techniques, alors vous pouvez directement vous rendre dans la partie Installation puis Utilisation de l'application.

Le projet, dans les grandes lignes :

  1. Scraping des données avec requests et bs4
  2. Restructuration des données avec polars 🐻
  3. Création de pipelines de Machine Learning avec sklearn 🤖
  4. Alimentation d'une base de données contenant les prédictions des modèles avec duckdb 💾
  5. Création d'une application pour visualiser les résultats avec streamlit et plotly 📊

Répond aussi à un certain nombre de normes de production et de reproductibilité :

  1. Annotations de type claires
  2. Des docstrings compréhensibles, avec exemples
  3. Gestion des dépendances et environnement virtuel avec poetry
  4. Modularité du projet, entièrement versionné sur Git
  5. Projet testé avec pytest et pytest-cov
  6. Docker

Scraping

Voici comment se déroule la récupération des données :

  1. On construit les URL des pages de recherche proprement avec query parameters en utilisant le package yarl.
URL_INIT = URL.build(scheme="https", host="vinatis.com")
WHITE = "achat-vin-blanc"
RED = "achat-vin-rouge"
ROSE = "achat-vin-rose"

>>> URL_INIT / WHITE % {"page": 1, "tri": 7}
... URL('https://vinatis.com/achat-vin-blanc?page=1&tri=7')
  1. create_session crée une session HTML avec un User-Agent et un Proxy aléatoire, pouvant changer entre les requêtes.
  2. Un décorateur @random_waiter(min, max) permet de générer un temps d'attente aléatoire entre les deux bornes spécifiées entre chaque requête GET pour éviter d'envoyer trop de requêtes dans un laps de temps réduit.
  3. create_all_wine_urls permet de créer l'ensemble des liens href.
  4. export_wine_links permet d'exporter ces liens dans un fichier CSV.

Note : Chaque lien href récupéré correspond à la page d'un vin.

Toutes ces étapes peuvent être éxécutées grâce au script page_scraper.py si on souhaite uniquement récupérer les liens des pages pour effectuer les requêtes plus tard.

Il ne suffit plus maintenant que de requêter ces liens pour récupérer les informations brutes des pages, en extraire des attributs de valeur et utiliser une @dataclass sérialisable en JSON pour stocker les informations.

Si les liens ont déjà été scrapés, alors lancer le script wine_scraper.py permettra de le faire.

Voici un exemple des caractéristiques d'un vin au format JSON :

{
        "name": "PINOT NOIR 2019 LAS PIZARRAS - ERRAZURIZ",
        "capacity": "0,75 L",
        "price": "94,90 €",
        "price_bundle": null,
        "characteristics": "Vin Rouge / Chili / Central Valley / Aconcagua Valley DO / 13,5 % vol / 100% Pinot noir",
        "note": null,
        "keywords": [
            "Elégance",
            "Finesse",
            "Harmonie"
        ],
        "others": null,
        "picture": "https://www.vinatis.com/67234-detail_default/pinot-noir-2019-las-pizarras-errazuriz.png",
        "classification": null,
        "millesime": "2019",
        "cepage": "100% Pinot noir",
        "gouts": "Rouge Charnu et fruité",
        "par_gouts": "Puissant",
        "oeil": "Robe rubis aux reflets violets.",
        "nez": "Nez complexe sur la griotte, les épices et les champignons (truffe).",
        "bouche": "Bouche fruitée et florale. Tanins structurés, élégants et fins. finale harmonieuse et persistante.",
        "temperature": "8-10°C",
        "service": "En bouteille ou en carafe",
        "conservation_1": "2026",
        "conservation_2": "A boire et à garder",
        "accords_vins": "Apéritif, Entrée, Charcuterie, Viande rouge, Viande blanche, Volaille, Gibier, Champignon, Barbecue, Cuisine du monde, Fromage, Dessert fruité, Dessert chocolaté",
        "accords_reco": "Gigot d'agneau aux herbes de Provence; Tikka massala; Plateau de fromages."
    }

Il est cependant possible de faire tourner la procédure complète une fois dans l'environnement virtuel du projet avec la commande :

python -m "src.modules.scraping_trigger"

Machine Learning

Que prédire ? Avec quoi le prédire ? Comment utiliser les résultats ? Les réponses sont ci-dessous :

  1. Deux variables à prédire : unit_price & type
  2. Nous utiliserons 6 modèles de Machine Learning
  3. ➶ Optimisation des hyperparamètres par Cross-Validation avec models.py
  4. 🏹 Prédiction sur les données de test avec prediction.py
  5. 🧪 Utilisation d'un pipeline sklearn
    • Evite le Data Leakage.
    • Procédure standardisée pour l'ensemble des modèles.

Les 21 variables explicatives sont les suivantes :

Variable Type Description
name str Nom du vin
capacity float Capacité en litres du vin
millesime int Année de vendange des raisins
cepage str Type de raisin utilisé pour confectionner le vin
par_gouts str Classification par goûts du vin
service str Comment se sert le vin
avg_temp float Température moyenne de conservation du vin
conservation_date int Date de conservation maximale du vin après achat
bio bool Indique si le vin est issu de l'agriculture biologique
customer_fav bool Indique si le vin est un coup de coeur client
is_new bool Indique si le vin est une nouveauté sur le site
top_100 bool Indique si le vin fait partie d'un classement dans le top 100
destock bool Indique si le vin est en déstockage
sulphite_free bool Indique si le vin est sans sulfites
alcohol_volume float Degré de concentration d'alcool
country str Pays d'origine du vin
bubbles bool Indique si le vin a des bulles
wine_note float Indique la note sur 5 du vin
nb_reviews int Nombre de commentaires
conservation_time float Durée de conservation du vin en années
cru bool Indique si le vin est un grand cru

Résultats du Machine Learning

5 tables de résultats de Machine Learning sont obtenues grâce à l'éxéuction de ml_trigger qui se charge d'éxécuter l'ensemble des scripts d'export. Mais plutôt que d'utiliser chaque csv indépendamment ou de tenter de concaténer les résultats, nous avons préféré utiliser une base de données pour l'implémentation dans notre application.

duckdb est une base de données particulière en ce sens qu'elle n'est pas Client-Server, mais in-memory. Cela permet d’obtenir des temps de réponse minimaux en éliminant le besoin d'accéder à des unités de disque standard (SSD). Une base de données in-memory est donc idéale pour une application effectuant de l’analyse de données en temps réel.

Voici un schéma du processus d'ingestion des tables :

graph LR;
A("👨‍🔬 pred_classification")-->F;
B("👨‍🔬 pred_regression")-->F;
C("👩‍🏫 result_ml_regression")-->F;
D("👩‍🏫 result_ml_classification")-->F;
E("🕵️‍♂️ importance")-->F[("🦆 In Memory Database")];

style A stroke:#adbac7,stroke-width:3px, fill:#222222;
style B stroke:#adbac7,stroke-width:3px, fill:#222222;
style C stroke:#adbac7,stroke-width:3px, fill:#222222;
style D stroke:#adbac7,stroke-width:3px, fill:#222222;
style E stroke:#adbac7,stroke-width:3px, fill:#222222;
style F stroke:#fff100,stroke-width:3px, fill:#222222;
Loading

Voici comment lancer la trigger une fois dans l'environnement virtuel du projet (pas nécessaire pour faire fonctionner l'application car les tables existent déjà):

python -m "src.modules.ml_trigger"

Attention, le temps d'éxécution moyen peut considérablement varier selon la machine utilisée !

Installation

En utilisant Visual Studio Code, il suffit de lancer le git bash avec ctrl+ù.

Ensuite, il faut cloner le dépôt avec la commande :

$ git clone https://github.com/CDucloux/Wine-Scraping.git
  • Toutes les dépendances sont contenues dans le pyproject.toml généré par poetry

La commande suivante se chargera d'installer l'ensemble de ces dépendances dans l'environnement virtuel dédié du projet :

py -m poetry install

Il faut ensuite lancer le shell poetry :

py -m poetry shell

Une fois dans le shell, pour lancer l'application, il faut simplement faire :

python -m streamlit run "streamlit_app.py"

🎉 Félicitations, vous devriez voir apparaitre le message suivant dans le terminal et l'application se lancer dans le navigateur !

  You can now view your Streamlit app in your browser.

  Local URL: http://localhost:8501

Docker

En plus d'une installation "classique", il est aussi possible de lancer la création d'une image Docker à partir du Dockerfile fourni dans le repository. Il faudra avant tout installer Docker Desktop $\Rightarrow$ https://docs.docker.com/desktop/install/windows-install/.

Une fois installé, l'image est construite en exécutant la commande suivante dans un terminal :

docker image build . -t "wine_scraping"

Une fois la création de l'image terminée, on peut consulter la taille de celle-ci avec :

docker images

Ensuite, pour lancer le conteneur Docker avec l'utilisateur app sur le port initial (8501) de streamlit, il suffit de faire :

docker run -u app -p 8501:8501 wine_scraping

🎉 Félicitations, le conteneur est lancé et fonctionnel ! Vous devriez le voir apparaitre dans Docker Desktop. Pour accéder à l'application, il suffit maintenant simplement de se rendre sur http://localhost:8501/.


Note : Si trop d'images s'accumulent, elles peuvent considérablement réduire l'espace disque disponible. Pour éviter cela on peut retirer tous les conteneurs inactifs et les images (Attention cependant, il faudra reconstruire l'image après) :

docker system prune -a --volumes

Note : Pour décharger la mémoire vive quand le conteneur n'est plus utilisé et que Docker Desktop est fermé, on peut arrêter Windows Subsystem for Linux dans le terminal avec :

wsl --shutdown

Utilisation de l'application

L'application dispose d'une barre latérale permettant de filtrer les résultats, et possède 6 onglets ayant des fonctions différentes.

Tous les onglets partagent aussi les métriques statiques sur le nombre de vins par type (Vin rouge, blanc et rosé).

Onglet 1 : Data Overview

  • Sidebar utilisable

Le premier onglet de l'application contient les données sous forme de tableau filtrable grâce à la barre latérale. Il est possible pour l'utilisateur d'étudier une multitude d'informations :

  • Le nom du vin, le prix unitaire, l'image de la bouteille, la capacité en litres, le type de vin, millésime, la durée de conservation, les mots-clés associés, le cépage, etc.

Démonstration :

Onglet 2 : Statistiques Descriptives

  • Sidebar utilisable

L'onglet 2 permet quant à lui d'obtenir un aperçu rapide d'une analyse exploratoire de données :

  • L'histogramme des prix
  • La matrice des corrélations
  • Un diagramme en barres des cépages majoritaires

Démonstration :

Onglet 3 : Charts

  • Sidebar utilisable

Le troisième onglet se focalise sur le lien entre le prix unitaire d'un vin et sa durée de conservation. Il est possible de sélectionner l'échelle et des régressions locales LOESS sont affichées pour chaque type de vin.

Démonstration :

Onglet 4 : Provenance

  • Sidebar utilisable

Ce quatrième onglet permet de visualiser une carte de la provenance des vins ainsi qu'une indication du nombre de vins commercialisés par pays. (Soyons honnêtes, c'est plus pour le style qu'autre chose.)

Démonstration :

NB : Etant donné que le revendeur est français, il est évident que le nombre de vins commercialisés par la France est prépondérant.

Onglet 5 : Machine Learning

  • Sidebar utilisable

Ce cinquième onglet est probablement le plus complexe et le plus intéressant. Il se décline en 3 parties :

  • Exploration
  • Investigation
  • Prédiction

Démonstration :

Exploration permet de comparer le score d'entrainement et le score de test des 6 modèles de Machine Learning pour vérifier si il y a un problème de sur-apprentissage.


Investigation approfondit l'exploration en ayant accès aux hyperparamètres optimaux de chaque modèle. En plus, selon le mode sélectionné (classification ou régression), différentes métriques d'évaluation s'affichent :

  • Pour la classification $\Rightarrow$ Accuracy, Precision, Recall, $F_1$ score, $MCC$ (Coefficient de corrélation de Matthews), Rapport de classificatiton et Matrice de Confusion.

  • Pour la régression $\Rightarrow$ $MAE$ (Erreur Absolue Moyenne), $MSE$ (Erreur Quadratique Moyenne), $R^2$, Erreur Résiduelle Maximale.

Enfin, pour les modèles de Boosting et de Random Forest, l'importance relative des variables dans le modèle est disponible graphiquement.


Prédiction permet à l'utilisateur de choisir un vin sur lesquels les modèles n'ont pas été entrainés. En bonus, la bouteille de vin est même visualisée 😉.

Ensuite, il peut choisir entre la prédiction du prix ou bien la classification du type de vin, et à la fin, sélectionner le modèle pour effectuer la prédiction !

Celle-ci est ensuite comparée à la réalité, avec un indicateur permettant de vérifier si il y a une correspondance.


Pour la prédiction du prix, pour que la prédiction soit considérée comme "acceptable", il faut que le prix prédit soit compris entre :

$$0.8 \times unit\textunderscore price_{\text{true}} < unit\textunderscore price_{\text{true}} < 1.2 \times unit\textunderscore price_{\text{true}}$$

  • C'est à dire entre 80 et 120% du prix réel.

Ce seuil est évidemment discutable car il n'est pas extrêmement précis pour les vins à prix elevé, néanmoins, pour les vins à bas prix, les écarts ne sont pas anormalement elevés.

Axes d'amélioration

Si la User Interface de notre application est bien réussie, il n'en reste pas moins que les modèles de régression des prix ont des performances plutôt moyennes. En effet, même si le paramètre le plus important est évidemment la durée de conservation d'un vin, de nombreux autres paramètres sont indisponibles dans nos données - comme le domaine ou la rareté du vin.

Il est clair que l'ajout de ces variables améliorerait nettement nos résultats.

Une autre piste à explorer serait d'utiliser du NLP avec spacy pour exploiter au maximum les données textuelles disponibles dans des variables comme la description, le goût en bouche, etc.

Auteurs