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

Add keukenliefde parser #877

Merged
merged 5 commits into from
Oct 3, 2023
Merged
Show file tree
Hide file tree
Changes from 4 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
2 changes: 2 additions & 0 deletions recipe_scrapers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@
from .justbento import JustBento
from .justonecookbook import JustOneCookbook
from .kennymcgovern import KennyMcGovern
from .keukenliefdenl import KeukenLiefdeNL
from .kingarthur import KingArthur
from .kitchenstories import KitchenStories
from .kochbar import Kochbar
Expand Down Expand Up @@ -425,6 +426,7 @@
JustBento.host(): JustBento,
JustOneCookbook.host(): JustOneCookbook,
KennyMcGovern.host(): KennyMcGovern,
KeukenLiefdeNL.host(): KeukenLiefdeNL,
KingArthur.host(): KingArthur,
KitchenStories.host(): KitchenStories,
Kochbar.host(): Kochbar,
Expand Down
86 changes: 86 additions & 0 deletions recipe_scrapers/keukenliefdenl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# mypy: allow-untyped-defs
from ._abstract import AbstractScraper
from ._exceptions import ElementNotFoundInHtml
from ._utils import get_minutes, get_yields, normalize_string


class KeukenLiefdeNL(AbstractScraper):
@classmethod
def host(cls):
return "keukenliefde.nl"

def author(self):
return self.soup.find("meta", {"name": "author"}).get("content")

def title(self):
return self.soup.find("meta", {"property": "og:title"}).get("content")

def category(self):
return self.soup.find(
"div", {"class": "article-meta-item sp gerecht"}
).getText()

def total_time(self):
return get_minutes(
self.soup.find("div", {"class": "article-meta-item sp tijd"}).get_text()
)

def yields(self):
yields = self.soup.find("div", {"class": "article-meta-item sp aantal"})
if yields:
return get_yields(yields.get_text())

return None

def image(self):
return self.schema.image()

def ingredients(self):
ingredents_container = self.soup.find("div", {"id": "clipboard-ingredients"})
if ingredents_container:
return self.process_ingredients(ingredents_container)

ingredient_header = self.soup.find("strong", string="Ingrediënten")
if ingredient_header:
return self.process_ingredients(
ingredient_header.parent.find_next_sibling("ul")
)

# Nothing found, we give up.
raise ElementNotFoundInHtml("Could not find ingredients.")
jayaddison marked this conversation as resolved.
Show resolved Hide resolved

def process_ingredients(self, container):
ingredients = container.findChildren("li")

return [normalize_string(i.get_text()) for i in ingredients]

def instructions(self):
# Old recipes are written in paragraphs, new ones are a list.
preparation = self.soup.find("div", {"class": "preparation"})
if preparation:
return self.normalize_instructions(
preparation.find_all("p") + preparation.find_all("li")
)

# There are some really old recipes that do not have the nice classes
instructions_heading = self.soup.find("strong", string="Bereiding")
if instructions_heading:
# We are doing some assumptions here
return self.normalize_instructions(
[instructions_heading.parent]
+ instructions_heading.parent.find_next_siblings("p")
)

raise ElementNotFoundInHtml("Could not find instructions.")

def normalize_instructions(self, instructions):
instructions = [
normalize_string(item.get_text())
for item in instructions
if normalize_string(item.get_text())
]

return "\n".join(instructions)

def description(self):
return self.soup.find("meta", {"name": "description"}).get("content")
388 changes: 388 additions & 0 deletions tests/test_data/keukenliefdenl_1.testhtml

Large diffs are not rendered by default.

298 changes: 298 additions & 0 deletions tests/test_data/keukenliefdenl_2.testhtml

Large diffs are not rendered by default.

381 changes: 381 additions & 0 deletions tests/test_data/keukenliefdenl_3.testhtml

Large diffs are not rendered by default.

65 changes: 65 additions & 0 deletions tests/test_keukenliefdenl_1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# mypy: allow-untyped-defs

from recipe_scrapers.keukenliefdenl import KeukenLiefdeNL
from tests import ScraperTest


class TestKeukenLiefdeNL1Scraper(ScraperTest):

scraper_class = KeukenLiefdeNL
test_file_name = "keukenliefdenl_1"

def test_host(self):
self.assertEqual("keukenliefde.nl", self.harvester_class.host())

def test_author(self):
self.assertEqual("Annemiek", self.harvester_class.author())

def test_title(self):
self.assertEqual("Macaroni stroganoff", self.harvester_class.title())

def test_category(self):
self.assertEqual("Hoofdgerecht", self.harvester_class.category())

def test_total_time(self):
self.assertEqual(15, self.harvester_class.total_time())

def test_yields(self):
self.assertEqual("4 servings", self.harvester_class.yields())

def test_image(self):
self.assertEqual(
"https://www.keukenliefde.nl/wp-content/uploads/2017/11/Macaroni-stroganoff-4568.jpg",
self.harvester_class.image(),
)

def test_ingredients(self):
self.assertEqual(
[
"300 g macaroni",
"1 ui, fijngesnipperd",
"1 teen knoflook, uitgeperst",
"2 paprika’s",
"250 g champignons",
"300 g rundergehakt",
"1 blikje tomatenpuree",
"1 el paprikapoeder",
"1 tl chilipoeder (optioneel)",
"1 runderbouillontablet",
"Scheut kookroom of crème fraîche",
"Olijfolie om in te bakken",
],
self.harvester_class.ingredients(),
)

def test_instructions(self):
self.assertEqual(
"Verhit een hapjespan met een scheut olie en fruit de ui en knoflook op laag vuur, totdat ze zacht zijn. Was ondertussen de paprika’s en verwijder de zaadlijsten. Snijd de paprika’s in blokjes. Borstel de champignons schoon en snijd in plakken. Voeg de paprika’s, champignons, paprikapoeder (en chilipoeder) toe aan de ui en zet het vuur wat hoger. Bak kort verder, totdat de groenten iets geslinkt zijn. Voeg het gehakt, de tomatenpuree en verkruimelde bouillontablet toe. Roer het gehakt los. Doe de deksel op de pan en laat op laag tot middelhoog vuur zachtjes pruttelen. Roer — als het gehakt en de groenten gaar zijn — een scheut room door de saus en warm nog even door.\nKook ondertussen de macaroni gaar volgens de omschrijving op het pak. Giet de macaroni af en roer door het gehakt.\nGarneer eventueel met wat vers gehakte peterselie.",
self.harvester_class.instructions(),
)

def test_description(self):
self.assertEqual(
"Een keer wat anders dan macaroni bolognese? Maak deze macaroni met stroganoffsaus: een goedgevulde saus met gehakt, paprika's en champignons.",
self.harvester_class.description(),
)
58 changes: 58 additions & 0 deletions tests/test_keukenliefdenl_2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# mypy: allow-untyped-defs

from recipe_scrapers.keukenliefdenl import KeukenLiefdeNL
from tests import ScraperTest


class TestKeukenLiefdeNL2Scraper(ScraperTest):

scraper_class = KeukenLiefdeNL
test_file_name = "keukenliefdenl_2"

def test_host(self):
self.assertEqual("keukenliefde.nl", self.harvester_class.host())

def test_author(self):
self.assertEqual("Johan", self.harvester_class.author())

def test_title(self):
self.assertEqual(
"Sticky kippenvleugels (van de kamado)", self.harvester_class.title()
)

def test_category(self):
self.assertEqual("Hapje", self.harvester_class.category())

def test_total_time(self):
self.assertEqual(5, self.harvester_class.total_time())

def test_yields(self):
self.assertEqual(None, self.harvester_class.yields())

def test_image(self):
self.assertEqual(
"https://www.keukenliefde.nl/wp-content/uploads/2023/09/Sticky-kippenvleugels-van-de-kamado-1675.jpg",
self.harvester_class.image(),
)

def test_ingredients(self):
self.assertEqual(
[
"Kippenvleugels",
"Kipkruiden",
"Teriyakisaus (zelfgemaakt of kant-en-klaar)",
],
self.harvester_class.ingredients(),
)

def test_instructions(self):
self.assertEqual(
"Stook de kamado indirect op tot 150 graden.\nLeg de kippenvleugels als de temperatuur stabiel op 150 graden blijft op het rooster en sluit de klep voor een half uur.\nDoe de teriyaki saus alvast in een vuurvaste pan en zet die na zo’n 20 minuten bij de kippenvleugels op het rooster.\nDe saus kan zo lekker opwarmen en iets dunner worden waardoor de vleugels zometeen lekker door de saus gehaald kunnen worden en goed veel saus aan de vleugels blijft plakken.\nNa 30 minuten open je de klep en haal je de vleugels met een tang door de saus. Zorg dat de kippenvleugels goed omhult zijn met saus. Leg de vleugels daarna weer terug op het rooster en laat ze met gesloten klep nog zo’n 10 minuten liggen.",
self.harvester_class.instructions(),
)

def test_description(self):
self.assertEqual(
"In dit recept laten we je zien hoe je deze smakelijke sticky kippenvleugels van de kamado (of in de oven) kunt maken. Ze zijn verslavend lekker!",
self.harvester_class.description(),
)
70 changes: 70 additions & 0 deletions tests/test_keukenliefdenl_3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# mypy: allow-untyped-defs

from recipe_scrapers.keukenliefdenl import KeukenLiefdeNL
from tests import ScraperTest


class TestKeukenLiefdeNL3Scraper(ScraperTest):

scraper_class = KeukenLiefdeNL
test_file_name = "keukenliefdenl_3"

def test_host(self):
self.assertEqual("keukenliefde.nl", self.harvester_class.host())

def test_author(self):
self.assertEqual("Annemiek", self.harvester_class.author())

def test_title(self):
self.assertEqual("Stroganoff-gehaktschotel", self.harvester_class.title())

def test_category(self):
self.assertEqual("Hoofdgerecht", self.harvester_class.category())

def test_total_time(self):
self.assertEqual(60, self.harvester_class.total_time())

def test_yields(self):
self.assertEqual("4 servings", self.harvester_class.yields())

def test_image(self):
self.assertEqual(
"https://www.keukenliefde.nl/wp-content/uploads/2014/11/Stroganoff-gehaktschotel-27.jpg",
self.harvester_class.image(),
)

def test_ingredients(self):
self.assertEqual(
[
"1,5 kilo aardappels, geschild",
"Melk",
"Roomboter",
"2 uien, gepeld en in ringen",
"2 tenen knoflook, gepeld en fijngehakt",
"3 paprika’s, in repen gesneden",
"250 g champignons, in plakjes gesneden",
"1 el paprikapoeder",
"1 blikje tomatenpuree",
"1 runderbouillonblokje",
"500 g rundergehakt",
"2 el crème fraîche",
"Worchestersaus",
"Tabasco",
"75 g geraspte kaas",
"Olie om in te bakken",
"Eventueel scheutje Vodka",
],
self.harvester_class.ingredients(),
)

def test_instructions(self):
self.assertEqual(
"Bereiding Verhit een grote koekenpan of braadpan met wat olie en bak de ui, knoflook, paprika’s en champignons op middelhoog vuur, totdat ze zacht zijn. Voeg de paprikapoeder en tomatenpuree toe en bak even mee.\nVerkruimel het bouillonblokje boven de pan en voeg het gehakt toe. Roer het gehakt met een vork goed rul. Blus het gehaktmengsel eventueel af met een scheut Vodka, laat inkoken en voeg dan 100 ml water toe.\nZet het vuur weer laag en laat 15 minuten zachtjes pruttelen, of totdat de saus is ingedikt. Roer de laatste 5 minuten van de kooktijd de crème fraîche door het gehakt. Proef de saus en breng indien nodig verder op smaak met wat Worchestersaus en tabasco.\nKook ondertussen de aardappels gaar en giet ze af. Stamp ze fijn en roer de puree glad met wat melk en een klont roomboter.\nDe puree moet niet te dik zijn, anders kun je hem zometeen moeilijk uitstrijken. Breng goed op smaak met zout en peper.\nVerwarm de oven voor op 200 graden. Vet de ovenschaal in. Verdeel het gehaktmengsel over de ovenschaal en dek af met de aardappelpuree. Bestrooi met de geraspte kaas.\nBak de ovenschotel circa 30 tot 40 minuten in de oven, of totdat de bovenkant goudbruin is.\nLaat de schotel voor serveren iets afkoelen.\nEnjoy!\nThema recepten, Wereldgerecht, Hongaarse recepten, Mannenvoer, Hoofdgerecht, Ovenrecepten, Vleesrecepten",
self.harvester_class.instructions(),
)

def test_description(self):
self.assertEqual(
"Een heerlijk gerecht geïnspireerd op de Hongaarse keuken: stroganoff-gehaktschotel!",
self.harvester_class.description(),
)
jaapio marked this conversation as resolved.
Show resolved Hide resolved