Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added scraper for veroniquecloutier.com #1411

Merged
merged 5 commits into from
Dec 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,7 @@ Scrapers available for:
- `https://www.vegetarbloggen.no/ <https://www.vegetarbloggen.no/>`_
- `https://vegolosi.it/ <https://vegolosi.it>`_
- `https://vegrecipesofindia.com/ <https://www.vegrecipesofindia.com/>`_
- `https://veroniquecloutier.com <https://veroniquecloutier.com>`_
- `https://www.waitrose.com/ <https://www.waitrose.com/>`_
- `https://watchwhatueat.com/ <https://watchwhatueat.com/>`_
- `https://wearenotmartha.com/ <https://wearenotmartha.com/>`_
Expand Down
2 changes: 2 additions & 0 deletions recipe_scrapers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,7 @@
from .vegetarbloggen import Vegetarbloggen
from .vegolosi import Vegolosi
from .vegrecipesofindia import VegRecipesOfIndia
from .veroniquecloutier import VeroniqueCloutier
from .waitrose import Waitrose
from .watchwhatueat import WatchWhatUEat
from .wearenotmartha import WeAreNotMartha
Expand Down Expand Up @@ -806,6 +807,7 @@
VegRecipesOfIndia.host(): VegRecipesOfIndia,
Vegetarbloggen.host(): Vegetarbloggen,
Vegolosi.host(): Vegolosi,
VeroniqueCloutier.host(): VeroniqueCloutier,
Waitrose.host(): Waitrose,
WatchWhatUEat.host(): WatchWhatUEat,
WeAreNotMartha.host(): WeAreNotMartha,
Expand Down
129 changes: 129 additions & 0 deletions recipe_scrapers/veroniquecloutier.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
from collections import defaultdict

from ._abstract import AbstractScraper
from ._exceptions import FieldNotProvidedByWebsiteException
from ._grouping_utils import IngredientGroup
from ._utils import normalize_string


class VeroniqueCloutier(AbstractScraper):
@classmethod
def host(cls):
return "veroniquecloutier.com"

def author(self):
return self.soup.find("strong").get_text()

def title(self):
return self.soup.find("h1", {"class": "title -main -page-title"}).get_text()

def total_time(self):
raise FieldNotProvidedByWebsiteException(return_value=None)

def yields(self):
potion_line = self.soup.find(
string=lambda text: text
and ("portions" in text.lower() or "donne " in text.lower())
)

if not potion_line:
return None

parent_text = potion_line.parent.get_text() if potion_line.parent else None
french_numbers = {
"un": 1,
"deux": 2,
"trois": 3,
"quatre": 4,
"cinq": 5,
"six": 6,
"sept": 7,
"huit": 8,
"neuf": 9,
"dix": 10,
"onze": 11,
"douze": 12,
"treize": 13,
"quatorze": 14,
"quinze": 15,
}
special_cases = {"dizaine": 10, "douzaine": 12}

for word in parent_text.split():
word_lower = word.lower()
if word.isdigit():
return f"{word} serving" if int(word) == 1 else f"{word} servings"
if word_lower in french_numbers:
number = french_numbers[word_lower]
return f"{number} serving" if number == 1 else f"{number} servings"
if word_lower in special_cases:
number = special_cases[word_lower]
return f"{number} serving" if number == 1 else f"{number} servings"

return None

def ingredients(self):
start = self.soup.find(
string=lambda text: text and text.lower() == "ingrédients"
)

ingredient_list = []
for sibling in start.find_all_next():
if sibling.string and "préparation" in sibling.string.lower():
break
if sibling.name == "ul":
ingredient_list.extend(li.text for li in sibling.find_all("li"))

return ingredient_list

def ingredient_groups(self):
start = self.soup.find(
string=lambda text: text and text.lower() == "ingrédients"
)

found_ingredients = []
groupings = defaultdict(list)
current_heading = None
for sibling in start.find_all_next():
if sibling.string and "préparation" in sibling.string.lower():
break

if sibling.name == "p" and sibling.text.strip():
current_heading = sibling.text.strip()

if sibling.name == "ul" and current_heading:
groupings[current_heading].extend(
li.text for li in sibling.find_all("li")
)
found_ingredients.extend(li.text for li in sibling.find_all("li"))
elif sibling.name == "ul":
found_ingredients.extend(li.text for li in sibling.find_all("li"))

if not groupings:
return [IngredientGroup(ingredients=found_ingredients)]

return [
IngredientGroup(purpose=heading, ingredients=items)
for heading, items in groupings.items()
]

def instructions(self):
start = self.soup.find(
string=lambda text: text and text.lower() == "préparation"
)

instruction_list = []
for sibling in start.find_all_next():
if sibling.name == "div":
break
if sibling.name == "ol":
instruction_list.extend(li.text for li in sibling.find_all("li"))
elif sibling.name == "p" and sibling.text[0].isdigit():
instruction_list.append(sibling.text[3:])

return "\n".join(instruction_list)

def description(self):
return normalize_string(
self.soup.find("div", {"class": "post-excerpt"}).get_text()
)
63 changes: 63 additions & 0 deletions tests/test_data/veroniquecloutier.com/veroniquecloutier.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{
"author": "Marilou",
"canonical_url": "https://veroniquecloutier.com/cuisine/boeuf-braise-a-loignon-et-puree-de-pommes-de-terre-au-bacon",
"site_name": "Véronique Cloutier",
"host": "veroniquecloutier.com",
"language": "fr-CA",
"title": "Boeuf braisé à l’oignon et purée de pommes de terre au bacon",
"ingredients": [
"30 ml (2 c. à soupe) de beurre et 30 ml (2 c. à soupe) de beurre supplémentaire au besoin",
"225 g (1/2 lb) de champignons tranchés",
"250 ml (1 t) d’oignons hachés",
"2 gousses d’ail entières pelées",
"900 g (2 lb) de rôti de palette de boeuf désossé",
"1 sachet de 50 g (2 oz) de soupe à l’oignon déshydratée du commerce",
"500 ml (2 t) d’eau",
"6 pommes de terre Yukon Gold, pelées et coupées en dés",
"30 ml (2 c. à soupe) de beurre",
"30 ml (2 c. à soupe) de crème 35 %",
"65 ml (1/4 t) de bacon croustillant émietté",
"Sel et poivre du moulin"
],
"ingredient_groups": [
{
"ingredients": [
"30 ml (2 c. à soupe) de beurre et 30 ml (2 c. à soupe) de beurre supplémentaire au besoin",
"225 g (1/2 lb) de champignons tranchés",
"250 ml (1 t) d’oignons hachés",
"2 gousses d’ail entières pelées",
"900 g (2 lb) de rôti de palette de boeuf désossé",
"1 sachet de 50 g (2 oz) de soupe à l’oignon déshydratée du commerce",
"500 ml (2 t) d’eau"
],
"purpose": "Boeuf braisé"
},
{
"ingredients": [
"6 pommes de terre Yukon Gold, pelées et coupées en dés",
"30 ml (2 c. à soupe) de beurre",
"30 ml (2 c. à soupe) de crème 35 %",
"65 ml (1/4 t) de bacon croustillant émietté",
"Sel et poivre du moulin"
],
"purpose": "Purée de pommes de terre au bacon"
}
],
"instructions_list": [
"Préchauffer le four à 150 °C (300 °F).",
"Dans une braisière ou une casserole pouvant aller au four, faire fondre le beurre et y faire dorer les champignons. Ajouter les oignons et l’ail, puis poursuivre la cuisson de 2 à 3 minutes.",
"Déposer le rôti sur les champignons et les oignons. Verser dessus le sachet de soupe à l’oignon.",
"Ajouter l’eau, mettre le couvercle, puis enfourner 3 1/2 heures.",
"Transférer la viande dans une grande assiette. Réserver.",
"Passer la sauce au mélangeur jusqu’à ce que qu’elle soit homogène. J’aime bien ajouter 2 c. à soupe de beurre, mais c’est optionnel.",
"Verser la sauce dans la braisière, remettre la viande dans la sauce, puis réserver jusqu’au moment de servir.",
"Faire bouillir les pommes de terre dans une casserole remplie d’eau salée jusqu’à ce qu’elles soient tendres.",
"Égoutter, puis passer au presse-purée. Bien assaisonner.",
"Ajouter le beurre et la crème avant de bien mélanger.",
"Ajouter le bacon et bien mélanger à nouveau."
],
"yields": "4 servings",
"description": "Enfin une recette simple pour nourrir de grandes tablées sans trop d’efforts! Les restes font d’excellents sandwiches à l’effilochée de boeuf.",
"total_time": null,
"image": "https://s3.amazonaws.com/rose.vero/wp-content/uploads/2015/01/12154413/boeuf-braise-1.jpg"
}
Loading