From 76017e3ea3dde710c2ea88e35d2d941cca6f807b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20GUEZO?= Date: Sat, 7 Feb 2026 19:02:19 +0100 Subject: [PATCH 1/3] ajout(test_main): question 1 assertion si bien une liste --- test_main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test_main.py b/test_main.py index 3d6b355..cb5f653 100644 --- a/test_main.py +++ b/test_main.py @@ -103,5 +103,6 @@ def test_soup(scraper: Scraper): def test_getProductName(scraper: Scraper): jsondata = scraper.getjsondata("nino-negri-5-stelle-sfursat-2022.html") assert jsondata["productName"] == "Nino Negri : 5 Stelle Sfursat 2022" + assert isinstance(jsondata["items"], list) assert len(jsondata["items"]) > 0 assert jsondata["items"][0]["offerPrice"] == 390 \ No newline at end of file From 74482af7f054d8e87e387fd87c543bf3b282afee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20GUEZO?= Date: Sat, 7 Feb 2026 20:36:09 +0100 Subject: [PATCH 2/3] =?UTF-8?q?ajout:=20nouvelle=20classe=20pour=20donn?= =?UTF-8?q?=C3=A9es=20et=20testes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 64 ++++++++++++++++++++++++++++++++++++++++------------ test_main.py | 40 ++++++++++++++++++++++++-------- 2 files changed, 79 insertions(+), 25 deletions(-) diff --git a/main.py b/main.py index 078fa91..45f1542 100644 --- a/main.py +++ b/main.py @@ -1,5 +1,5 @@ from sys import stderr -from typing import cast +from typing import Any, cast from requests import Response, Session from bs4 import BeautifulSoup, Tag from json import JSONDecodeError, loads @@ -22,6 +22,7 @@ class Scraper: self._session: Session = Session() # Système de cache pour éviter de solliciter le serveur inutilement self._latest_request: tuple[(str, Response | None)] = ("", None) + self._latest_soup: tuple[(str, BeautifulSoup | None)] = ("", None) def _request(self, subdir: str) -> Response: """ @@ -56,12 +57,12 @@ class Scraper: """ rq_subdir, rq_response = self._latest_request - if rq_response is None or subdir != rq_subdir: - request: Response = self._request(subdir) - self._latest_request = (subdir, request) - return request + if rq_response is not None and subdir == rq_subdir: + return rq_response - return rq_response + request: Response = self._request(subdir) + self._latest_request = (subdir, request) + return request def getsoup(self, subdir: str = "") -> BeautifulSoup: """ @@ -76,12 +77,19 @@ class Scraper: Raise: HTTPError: Si le serveur renvoie un code d'erreur (4xx, 5xx). """ - markup: str = self.getresponse(subdir).text - return BeautifulSoup(markup, features="html.parser") + rq_subdir, rq_soup = self._latest_soup - def getjsondata( - self, subdir: str = "", id: str = "__NEXT_DATA__" - ) -> dict[str, object]: + if rq_soup is not None and subdir == rq_subdir: + return rq_soup + + soup: BeautifulSoup = BeautifulSoup( + markup=self.getresponse(subdir).text, features="html.parser" + ) + + self._latest_soup = (subdir, soup) + return soup + + def getjsondata(self, subdir: str = "", id: str = "__NEXT_DATA__") -> ScraperData: """ Extrait les données JSON contenues dans la balise __NEXT_DATA__ du site. Beaucoup de sites modernes (Next.js) stockent leur état initial dans @@ -94,7 +102,7 @@ class Scraper: Raises: HTTPError: Soulevée par `getresponse` si le serveur renvoie un code d'erreur (4xx, 5xx). JSONDecodeError: Soulevée par `loads` si le contenu de la balise n'est pas un JSON valide. - ValueError: Soulevée manuellement si l'une des clés attendues (props, pageProps, etc.) + ValueError: Soulevée manuellement si l'une des clés attendues (props, pageProps, etc.) est absente de la structure JSON. Returns: @@ -120,13 +128,39 @@ class Scraper: # si current_data est bien un dictionnaire et que la clé # est bien dedans if isinstance(current_data, dict) and key in current_data: - current_data = current_data[key] + current_data: object = current_data[key] else: raise ValueError(f"Clé manquante dans le JSON : {key}") if isinstance(current_data, dict): - return cast(dict[str, object], current_data) + return ScraperData(data=cast(dict[str, object], current_data)) except (JSONDecodeError, ValueError) as e: print(f"Erreur lors de l'extraction JSON : {e}", file=stderr) - return {} + return ScraperData({}) + + +class ScraperData: + def __init__(self, data: dict[str, object]) -> None: + if not data: + raise ValueError("Données insuffisantes pour créer un ScraperData.") + self._data: dict[str, object] = data + + def _getattributes(self) -> dict[str, object] | None: + current_data: object = self._data.get("attributes") + if isinstance(current_data, dict): + return cast(dict[str, object], current_data) + return None + + def appellation(self) -> str | None: + current_value: dict[str, object] | None = self._getattributes() + if current_value is not None: + app_dict: dict[str, object] = cast( + dict[str, object], current_value.get("appellation") + ) + if app_dict: + return cast(str, app_dict.get("value")) + return None + + def getdata(self) -> dict[str, object]: + return self._data diff --git a/test_main.py b/test_main.py index cb5f653..d96d3a9 100644 --- a/test_main.py +++ b/test_main.py @@ -2,7 +2,7 @@ from json import dumps from bs4 import Tag import pytest from requests_mock import Mocker -from main import Scraper +from main import Scraper, ScraperData @pytest.fixture(autouse=True) @@ -24,7 +24,7 @@ def mock_site(): "productName": "Nino Negri : 5 Stelle Sfursat 2022", "productNameForSearch": "Nino Negri : 5 Stelle Sfursat 2022", "storeId": "11652", - "longdesc": "

Caractéristiques et conseils de dégustation du 5 Stelle Sfursat 2022 de Nino Negri

Dégustation

Robe

La robe dévoile une couleur grenat d'intensité moyenne.

Nez

Le nez révèle des arômes singuliers de fruits mûrs accompagnés de notes d'épices douces.

Bouche

En bouche, ce vin séduit par son équilibre remarquable, sa richesse et son caractère corsé. La dégustation dévoile une concentration intense et vigoureuse, portée par un fond aristocratique de mûre bien mûre et d'épices. La finale se distingue par sa longueur et sa persistance.

Accords mets et vins

Ce vin de caractère accompagne parfaitement les viandes rouges braisées, le gibier en sauce ou encore les fromages affinés à pâte dure.

Service et garde

Le 5 Stelle Sfursat 2022 gagnera à être servi à une température comprise entre 16 et 18°C.

Un Sforzato di Valtellina d'exception élaboré par la Maison Nino Negri

La propriété

Fondée en 1897 par Nino Negri à Chiuro en Valteline, cette Maison lombarde représente aujourd'hui la plus importante cave de la région. Propriété du Gruppo Italiano Vini depuis 1986, elle cultive 38 hectares de vignobles en terrasses sur des pentes alpines aux sols granitiques et calcaires. Sous la houlette de l'œnologue Danilo Drocco, Nino Negri perpétue l'excellence du nebbiolo valtelin, notamment à travers son emblématique Sforzato élaboré selon la méthode traditionnelle d'appassimento.

Le vignoble

Le 5 Stelle Sfursat est issu de l'appellation Sforzato di Valtellina DOCG, territoire d'exception où le nebbiolo s'épanouit sur des terrasses alpines escarpées. Les raisins proviennent de vignobles implantés sur des pentes granitiques et calcaires, bénéficiant d'une exposition optimale permettant une maturation idéale du nebbiolo.

Vinification et élevage

Le 5 Stelle Sfursat 2022 est produit uniquement lors des saisons les plus favorables. Les raisins sont récoltés manuellement et disposés en couche unique dans des caisses de 4 kg. Ils sont ensuite soumis à un séchage naturel dans un grenier pendant environ trois mois avant la vinification, selon la méthode traditionnelle de l'appassimento. Ce processus permet aux baies de perdre près de 30 % de leur poids, concentrant ainsi les arômes et les sucres naturels.

Cépage

Ce vin de Lombardie est un 100 % nebbiolo

", + "longdesc": "

Caractéristiques et conseils de dégustation du 5 Stelle Sfursat 2022 de Nino Negri

Dégustation

Robe

La robe dévoile une couleur grenat d'intensité moyenne.

Nez

Le nez révèle des arômes singuliers de fruits mûrs accompagnés de notes d'épices douces.

Bouche

En bouche, ce vin séduit par son équilibre remarquable, sa richesse et son caractère corsé. La dégustation dévoile une concentration intense et vigoureuse, portée par un fond aristocratique de mûre bien mûre et d'épices. La finale se distingue par sa longueur et sa persistance.

Accords mets et vins

Ce vin de caractère accompagne parfaitement les viandes rouges braisées, le gibier en sauce ou encore les fromages affinés à pâte dure.

Service et garde

Le 5 Stelle Sfursat 2022 gagnera à être servi à une température comprise entre 16 et 18°C.

Un Sforzato di Valtellina d'exception élaboré par la Maison Nino Negri

La propriété

Fondée en 1897 par Nino Negri à Chiuro en Valteline, cette Maison lombarde représente aujourd'hui la plus importante cave de la région. Propriété du Gruppo Italiano Vini depuis 1986, elle cultive 38 hectares de vignobles en terrasses sur des pentes alpines aux sols granitiques et calcaires. Sous la houlette de l'œnologue Danilo Drocco, Nino Negri perpétue l'excellence du nebbiolo valtelin, notamment à travers son emblématique Sforzato élaboré selon la méthode traditionnelle d'appassimento.

Le vignoble

Le 5 Stelle Sfursat est issu de l'appellation Sforzato di Valtellina DOCG, territoire d'exception où le nebbiolo s'épanouit sur des terrasses alpines escarpées. Les raisins proviennent de vignobles implantés sur des pentes granitiques et calcaires, bénéficiant d'une exposition optimale permettant une maturation idéale du nebbiolo.

Vinification et élevage

Le 5 Stelle Sfursat 2022 est produit uniquement lors des saisons les plus favorables. Les raisins sont récoltés manuellement et disposés en couche unique dans des caisses de 4 kg. Ils sont ensuite soumis à un séchage naturel dans un grenier pendant environ trois mois avant la vinification, selon la méthode traditionnelle de l'appassimento. Ce processus permet aux baies de perdre près de 30 % de leur poids, concentrant ainsi les arômes et les sucres naturels.

Cépage

Ce vin de Lombardie est un 100 % nebbiolo

", "image": "J4131_2022NM_c.png", "seoKeyword": "nino-negri-5-stelle-sfursat-2022.html", "title": "Nino Negri : 5 Stelle Sfursat 2022", @@ -66,7 +66,17 @@ def mock_site(): "stockOrigin": "EUR", "isPrevSale": False, } - ], + ], + "attributes": { + "appellation": { + "valueId": "433", + "name": "Appellation", + "value": "Sforzato di Valtellina", + "url": "sforzato-di-valtellina.html", + "isSpirit": False, + "groupIdentifier": "appellation_433", + }, + }, } } } @@ -82,7 +92,10 @@ def mock_site(): """ - m.get("https://www.millesima.fr/nino-negri-5-stelle-sfursat-2022.html", text=html_product) + m.get( + "https://www.millesima.fr/nino-negri-5-stelle-sfursat-2022.html", + text=html_product, + ) # on return m sans fermer le server qui simule la page yield m @@ -100,9 +113,16 @@ def test_soup(scraper: Scraper): assert h1.text == "MILLESIMA" -def test_getProductName(scraper: Scraper): - jsondata = scraper.getjsondata("nino-negri-5-stelle-sfursat-2022.html") - assert jsondata["productName"] == "Nino Negri : 5 Stelle Sfursat 2022" - assert isinstance(jsondata["items"], list) - assert len(jsondata["items"]) > 0 - assert jsondata["items"][0]["offerPrice"] == 390 \ No newline at end of file +# def test_getProductName(scraper: Scraper): +# jsondata = scraper.getjsondata("nino-negri-5-stelle-sfursat-2022.html") +# assert jsondata["productName"] == "Nino Negri : 5 Stelle Sfursat 2022" +# assert isinstance(jsondata["items"], list) +# assert len(jsondata["items"]) > 0 +# assert jsondata["items"][0]["offerPrice"] == 390 + + +def test_appellation(scraper: Scraper): + appellation: ScraperData = scraper.getjsondata( + "nino-negri-5-stelle-sfursat-2022.html" + ) + assert appellation.appellation() == "Sforzato di Valtellina" From 6992a0ca16a8df11698bea082daf2d241f680947 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20GUEZO?= Date: Sat, 7 Feb 2026 22:48:26 +0100 Subject: [PATCH 3/3] fix: format lint --- main.py | 54 ++++++++++++++++++++++++++-------------------------- test_main.py | 3 --- 2 files changed, 27 insertions(+), 30 deletions(-) diff --git a/main.py b/main.py index 45f1542..349e81c 100644 --- a/main.py +++ b/main.py @@ -1,10 +1,36 @@ from sys import stderr -from typing import Any, cast +from typing import cast from requests import Response, Session from bs4 import BeautifulSoup, Tag from json import JSONDecodeError, loads +class ScraperData: + def __init__(self, data: dict[str, object]) -> None: + if not data: + raise ValueError("Données insuffisantes pour créer un ScraperData.") + self._data: dict[str, object] = data + + def _getattributes(self) -> dict[str, object] | None: + current_data: object = self._data.get("attributes") + if isinstance(current_data, dict): + return cast(dict[str, object], current_data) + return None + + def appellation(self) -> str | None: + current_value: dict[str, object] | None = self._getattributes() + if current_value is not None: + app_dict: dict[str, object] = cast( + dict[str, object], current_value.get("appellation") + ) + if app_dict: + return cast(str, app_dict.get("value")) + return None + + def getdata(self) -> dict[str, object]: + return self._data + + class Scraper: """ Scraper est une classe qui permet de gerer @@ -138,29 +164,3 @@ class Scraper: except (JSONDecodeError, ValueError) as e: print(f"Erreur lors de l'extraction JSON : {e}", file=stderr) return ScraperData({}) - - -class ScraperData: - def __init__(self, data: dict[str, object]) -> None: - if not data: - raise ValueError("Données insuffisantes pour créer un ScraperData.") - self._data: dict[str, object] = data - - def _getattributes(self) -> dict[str, object] | None: - current_data: object = self._data.get("attributes") - if isinstance(current_data, dict): - return cast(dict[str, object], current_data) - return None - - def appellation(self) -> str | None: - current_value: dict[str, object] | None = self._getattributes() - if current_value is not None: - app_dict: dict[str, object] = cast( - dict[str, object], current_value.get("appellation") - ) - if app_dict: - return cast(str, app_dict.get("value")) - return None - - def getdata(self) -> dict[str, object]: - return self._data diff --git a/test_main.py b/test_main.py index d96d3a9..28dc330 100644 --- a/test_main.py +++ b/test_main.py @@ -24,11 +24,8 @@ def mock_site(): "productName": "Nino Negri : 5 Stelle Sfursat 2022", "productNameForSearch": "Nino Negri : 5 Stelle Sfursat 2022", "storeId": "11652", - "longdesc": "

Caractéristiques et conseils de dégustation du 5 Stelle Sfursat 2022 de Nino Negri

Dégustation

Robe

La robe dévoile une couleur grenat d'intensité moyenne.

Nez

Le nez révèle des arômes singuliers de fruits mûrs accompagnés de notes d'épices douces.

Bouche

En bouche, ce vin séduit par son équilibre remarquable, sa richesse et son caractère corsé. La dégustation dévoile une concentration intense et vigoureuse, portée par un fond aristocratique de mûre bien mûre et d'épices. La finale se distingue par sa longueur et sa persistance.

Accords mets et vins

Ce vin de caractère accompagne parfaitement les viandes rouges braisées, le gibier en sauce ou encore les fromages affinés à pâte dure.

Service et garde

Le 5 Stelle Sfursat 2022 gagnera à être servi à une température comprise entre 16 et 18°C.

Un Sforzato di Valtellina d'exception élaboré par la Maison Nino Negri

La propriété

Fondée en 1897 par Nino Negri à Chiuro en Valteline, cette Maison lombarde représente aujourd'hui la plus importante cave de la région. Propriété du Gruppo Italiano Vini depuis 1986, elle cultive 38 hectares de vignobles en terrasses sur des pentes alpines aux sols granitiques et calcaires. Sous la houlette de l'œnologue Danilo Drocco, Nino Negri perpétue l'excellence du nebbiolo valtelin, notamment à travers son emblématique Sforzato élaboré selon la méthode traditionnelle d'appassimento.

Le vignoble

Le 5 Stelle Sfursat est issu de l'appellation Sforzato di Valtellina DOCG, territoire d'exception où le nebbiolo s'épanouit sur des terrasses alpines escarpées. Les raisins proviennent de vignobles implantés sur des pentes granitiques et calcaires, bénéficiant d'une exposition optimale permettant une maturation idéale du nebbiolo.

Vinification et élevage

Le 5 Stelle Sfursat 2022 est produit uniquement lors des saisons les plus favorables. Les raisins sont récoltés manuellement et disposés en couche unique dans des caisses de 4 kg. Ils sont ensuite soumis à un séchage naturel dans un grenier pendant environ trois mois avant la vinification, selon la méthode traditionnelle de l'appassimento. Ce processus permet aux baies de perdre près de 30 % de leur poids, concentrant ainsi les arômes et les sucres naturels.

Cépage

Ce vin de Lombardie est un 100 % nebbiolo

", - "image": "J4131_2022NM_c.png", "seoKeyword": "nino-negri-5-stelle-sfursat-2022.html", "title": "Nino Negri : 5 Stelle Sfursat 2022", - "metaDesc": "Nino Negri : 5 Stelle Sfursat 2022 : Vente en ligne, Grand vin d'origine garantie en provenance directe de la propriété - ✅ Qualité de stockage", "items": [ { "_id": "J4131/22/C/CC/6-11652",