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 grimgrains.com #846

Merged
merged 6 commits into from
Sep 15, 2023
Merged
Show file tree
Hide file tree
Changes from 5 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 @@ -172,6 +172,7 @@ Scrapers available for:
- `https://gonnawantseconds.com/ <https://gonnawantseconds.com>`_
- `https://gousto.co.uk/ <https://gousto.co.uk>`_
- `https://greatbritishchefs.com/ <https://greatbritishchefs.com>`_
- `https://grimgrains.com/ <https://grimgrains.com>`_
- `https://halfbakedharvest.com/ <https://www.halfbakedharvest.com/>`_
- `https://handletheheat.com/ <https://handletheheat.com/>`_
- `https://www.hassanchef.com/ <https://www.hassanchef.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 @@ -97,6 +97,7 @@
from .goodfooddiscoveries import GoodFoodDiscoveries
from .goustojson import GoustoJson
from .greatbritishchefs import GreatBritishChefs
from .grimgrains import GrimGrains
from .halfbakedharvest import HalfBakedHarvest
from .handletheheat import HandleTheHeat
from .hassanchef import HassanChef
Expand Down Expand Up @@ -366,6 +367,7 @@
GoodFoodDiscoveries.host(): GoodFoodDiscoveries,
GoustoJson.host(): GoustoJson,
GreatBritishChefs.host(): GreatBritishChefs,
GrimGrains.host(): GrimGrains,
HEB.host(): HEB,
HalfBakedHarvest.host(): HalfBakedHarvest,
HandleTheHeat.host(): HandleTheHeat,
Expand Down
42 changes: 42 additions & 0 deletions recipe_scrapers/grimgrains.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# mypy: allow-untyped-defs

from itertools import chain
from urllib.parse import urljoin

from ._abstract import AbstractScraper
from ._utils import get_minutes, get_yields


class GrimGrains(AbstractScraper):
@classmethod
def host(cls):
return "grimgrains.com"

def author(self):
return "Hundred Rabbits"

def title(self):
return self.soup.main.h1.string

def total_time(self):
return get_minutes(self.soup.main.h2.string.split("—")[1])

def yields(self):
return get_yields(self.soup.main.h2.string.split("—")[0])

def image(self):
return urljoin(self.url, self.soup.main.find("img")["src"])

def ingredients(self):
return [
f"{i.b.text}: {i.u.text}"
for i in chain.from_iterable(self.soup.main("dl", "ingredients"))
if i.b
]

def instructions(self):
instructions = [i("li") for i in self.soup.main("ul", "instructions")]
return "\n".join([i.text for i in chain.from_iterable(instructions)])

def description(self):
return "\n".join([i.text for i in self.soup.find(class_="col2")("p")])
1 change: 1 addition & 0 deletions tests/test_data/grimgrains.testhtml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<!DOCTYPE html><html lang='en'><head><meta charset='utf-8'><meta name='description' content='Grim Grains is an illustrated food blog, it features plant-based (vegan) recipes.'><meta name='viewport' content='width=device-width, initial-scale=1.0'><meta name='twitter:card' content='summary'><meta name='twitter:site' content='@hundredrabbits'><meta name='twitter:title' content='Grimgrains'><meta name='twitter:description' content='An illustrated food blog.'><meta name='twitter:creator' content='@hundredrabbits'><meta name='twitter:image' content='https://grimgrains.com/media/services/icon.jpg'><meta property='og:title' content='Grimgrains'><meta property='og:type' content='article'><meta property='og:url' content='http://grimgrains.com/'><meta property='og:image' content='https://grimgrains.com/media/services/icon.jpg'><meta property='og:description' content='An illustrated food blog.'><meta property='og:site_name' content='Grimgrains'><link rel='icon' type='image/x-icon' href='../media/services/favicon.ico'><link rel='icon' type='image/png' href='../media/services/icon.jpg'><link rel='apple-touch-icon' href='../media/services/apple-touch-icon.png' /><title>GrimGrains — okonomiyaki</title><link rel='alternate' type='application/rss+xml' title='RSS Feed' href='../links/rss.xml' /><link rel='stylesheet' type='text/css' href='../links/main.css'></head><body class='recipe'><header><a id='logo' href='home.html'><img src='../media/interface/logo.png' alt='Grimgrains'></a></header><nav><ul><li class='home'><a href='home.html'>Home</a></li><li class='about'><a href='about.html'>About</a></li><li class='tools'><a href='tools.html'>Tools</a></li><li class='nutrition'><a href='nutrition.html'>Nutrition</a></li><li class='sprouting'><a href='sprouting.html'>Sprouting</a></li><li class='lactofermentation'><a href='lactofermentation.html'>Lacto-fermentation</a></li><li class='right'><a href='https://grimgrains.com/links/rss.xml'>RSS feed</a> | <a href='https://merveilles.town/@rek' target='_blank'>Mastodon</a></li></ul></nav><main class='recipe'><h1>okonomiyaki</h1><h2>4 servings — 20 minutes</h2><img src='../media/recipes/okonomiyaki.jpg'/><div class='col2'><p><b>Okonomiyaki</b> (meaning, 'grilled as you like it') is a Japanese dish, similar to the American omelette, but the main difference is the variation of ingredients. Typical okonomiyaki are made with eggs, and often include meat or fish. Making it without meat is simple enough, but without eggs? Back when we lived in Tokyo, our experience in cooking with plants was limited, but now we've been doing it long enough that we can think of alternatives with ease.</p><p>The key ingredient? <a href='chickpea_flour.html'>Chickpea flour</a>. We make chickpea pancakes, and <a href='scrambled_chickpea_flour.html'>scrambled chickpea flour</a> (resembles scrambled eggs) at home all the time. Chickpea flour is a staple on Pino, and works very well for okonomiyaki.</p><p><b>Nagaimo:</b> If you're in a place were nagaimo (or yamaimo) is available, we highly reccommend adding it to the dish. It makes a fluffier pancake. Although we've made okonomiyaki without nagaimo before, so if you can't find it know that it will work and be very delicious anyway. It imparts little flavor, all it does is add nutrition and texture. Nagaimo, unlike most potatoes, can be eaten raw. However, it is best to handle the nagaimo with gloves, or to soak the peeled tuber in a vinegar-water solution to neutralize irritant <b>oxalate crystals</b> found on their skin. Nagaimo are low-calorie, high in protein, and have potassium, zinc, vitamin C and more. The texture of grated nagaimo can be off-putting, it looks like a regular tuber when whole, but when grated it becomes slime, almost liquid. This sort of texture is well-liked in Japan and referred to as being 'neba neba' (slimy). This texture present in many other foods like okra and nattou. This texture makes it an ideal egg alternative, it can be used to make deserts when baking.</p><p><b>Aonori:</b> Aonori is another obscure ingredient, but again, it can be omitted, although it tastes really amazing with it. We made okonomiyaki without it when we were in Majuro, because it simply wasn't available, so we used finely cut nori instead. Obviously, this isn't a perfect substitution, because aonori is sweet and tastes nothing like nori. However, nori is still very delicious and pairs well enough with the okonomiyaki.</p><p><b class='head'>Okonomi sauce</b></p><p>In this recipe, we don't use true 'okonomi sauce'. Why? Because we don't use many pre-made sauces, we prefer to make my own. Okonomi sauce requires many ingredients, and honestly, the sauce we've made works really well in this recipe and makes a good okonomi sauce alternative.</p><p>If you want to make your own, you can mix 7g (1 1/2 tsp) <a href='#whole_can_sugar.html'>sugar</a>, 45g (3 tbsp) <b>ketchup</b> and 45g (3 tbsp) of vegan <b>worcestershire sauce</b>. If you are like us, and don't care to buy pre-made sauces but want to avoid buying both ketchup and worcestershire sauce, you can make these too:</p><p><b>Worcestershire sauce:</b> combine <a href='apple_cider_vinegar.html'>apple cider vinegar</a>, water, <a href='soy_sauce.html'>soy sauce</a>, <a href='#natural_brown_sugar.html'>sugar</a>, <a href='#mustard_seeds.html'>mustard powder</a>, onion powder, garlic powder, <a href='cinnamon.html'>cinnamon</a> and <a href='black_pepper.html'>black pepper</a> in pan, bring to a boil and cook for a minute, then let cool.</p><p><b>Ketchup:</b> Using some <b>fresh tomato sauce</b> may be enough, otherwise add a bit of <a href='#natural_brown_sugar.html'>sugar</a> and <a href='apple_cider_vinegar.html'>apple cider vinegar</a> to it.</p></div><dl class='ingredients'><h3>okonomiyaki</h3><dt><a href='nagaimo.html'><img src='../media/ingredients/nagaimo.png'/><b>nagaimo</b></a><u>160 g, grated</u></dt><dt><a href='green_cabbage.html'><img src='../media/ingredients/green_cabbage.png'/><b>green cabbage</b></a><u>500 g, minced</u></dt><dt><a href='chickpea_flour.html'><img src='../media/ingredients/chickpea_flour.png'/><b>chickpea flour</b></a><u>85 g</u></dt><dt><a href='nutritional_yeast.html'><img src='../media/ingredients/nutritional_yeast.png'/><b>nutritional yeast</b></a><u>15 g</u></dt><dt><a href='salt.html'><img src='../media/ingredients/salt.png'/><b>salt</b></a><u>1.25 g</u></dt><dt><a href='shiitake.html'><img src='../media/ingredients/shiitake.png'/><b>shiitake</b></a><u>6</u></dt><dt><a href='water.html'><img src='../media/ingredients/water.png'/><b>water</b></a><u>320 ml</u></dt><dt><a href='sesame_oil.html'><img src='../media/ingredients/sesame_oil.png'/><b>sesame oil</b></a><u>10 ml</u></dt></dl><ul class='instructions'><li>Peel the <a href='nagaimo.html'>nagaimo</a>, then soak it in a water and vinegar solution (doing this helps to remove irritants). Dry the nagaimo, then grate <i>160 g (6-8cm)</i> of <a href='nagaimo.html'>nagaimo</a> with a fine grater. Do this over a bowl, as the grated nagaimo is very slimy. Keep aside.</li><li>Mince <i>500 g (1 small head)</i> <a href='green_cabbage.html'>green cabbage</a>, keep aside.</li><li>In a bowl, mix <i>85 g (1 cup)</i> of <a href='chickpea_flour.html'>chickpea flour</a>, <i>15 g (1/4 cup)</i> <a href='nutritional_yeast.html'>nutritional yeast</a>, the grated <a href='nagaimo.html'>nagaimo</a>, the minced <a href='green_cabbage.html'>green cabbage</a> and <i>1.25g (1/4 tsp)</i> of <a href='salt.html'>salt</a>. Then, add <i>320 ml (1 1/4 cup)</i> of <a href='water.html'>water</a>, or <a href='shiitake.html'>shiitake</a> dashi. (for shiitake dashi, soak 5-6 shiitake in 320 ml of hot water for <u>15 minutes</u>, or <u>overnight</u> in cold water.)</li><li>Heat a non-stick pan at high heat, add <i>5 ml (1 tsp)</i> of <a href='sesame_oil.html'>sesame oil</a>. If you throw some water on and it starts to sizzle, the pan is hot and you can add <i>1/4</i> of the batter. Alternatively, you can add 1/2, although this makes two very large portions.</li><li>Let okonomiyaki cook for <u>5 minutes</u>, shaking the pan every now and then so the batter doesn't stick.</li><li>After <u>5 minutes</u>, it's time to give the other side some grilling time. Put a plate on top of the pancake, keep your hand on the plate and flip the pan so that the pancake ends up cooked side up on the plate. Then, slide the pancake back into the pan (cooked side up). You can also just flip it with a spatula, but we rather like the plate method :).</li><li>Cook for another <u>5 minutes</u>, then slide onto a plate, repeat process for the rest of the batter.</li></ul><dl class='ingredients'><h3>sauce</h3><dt><a href='soy_sauce.html'><img src='../media/ingredients/soy_sauce.png'/><b>soy sauce</b></a><u>60 ml</u></dt><dt><a href='mirin.html'><img src='../media/ingredients/mirin.png'/><b>mirin</b></a><u>60 ml</u></dt><dt><a href='granulated_sugar.html'><img src='../media/ingredients/granulated_sugar.png'/><b>granulated sugar</b></a><u>15 g</u></dt><dt><a href='arrowroot_starch.html'><img src='../media/ingredients/arrowroot_starch.png'/><b>arrowroot starch</b></a><u>15 g</u></dt></dl><ul class='instructions'><li>In a small bowl, mix <i>60 ml (4 tbsp)</i> of <a href='soy_sauce.html'>soy sauce</a>, <i>60 ml (4 tbsp)</i> of <a href='mirin.html'>mirin</a>, <i>15 g (1 tbsp)</i> of <a href='granulated_sugar.html'>sugar</a> and <i>15 g (1 tbsp)</i> of <a href='arrowroot_starch.html'>arrowroot starch</a>. Stir well.</li><li>Heat a pan at high heat, when hot, add sauce and cook for <u>2-3 minutes</u> until it thickens. Then, divide onto your okonomiyaki.</li></ul><dl class='ingredients'><h3>topping</h3><dt><a href='ao_nori.html'><img src='../media/ingredients/ao_nori.png'/><b>ao nori</b></a><u>30 g</u></dt><dt><a href='beni_shouga.html'><img src='../media/ingredients/beni_shouga.png'/><b>beni shouga</b></a><u>to taste</u></dt><dt><a href='scallions.html'><img src='../media/ingredients/scallions.png'/><b>scallions</b></a><u>4 stalks</u></dt></dl><ul class='instructions'><li>First, add about <i>15 g (1 tbsp)</i> (per okonomiyaki) of <a href='aonori.html'>aonori</a> on top of the sauce.</li><li>Then, add some <a href='beni_shouga.html'>beni shouga</a> (pickled red ginger).</li><li>Finally, top off with some finely chopped <a href='scallions.html'>scallions</a>.</li></ul></main><footer><a href='about.html'>Grimgrains</a> © 2014—2023 <a href='https://creativecommons.org/licenses/by-nc-sa/4.0/' target='_blank'> BY-NC-SA-4.0</a><br><a href='http://100r.co/' target='_blank'>Hundred Rabbits</a></footer></body></html>
81 changes: 81 additions & 0 deletions tests/test_grimgrains.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# mypy: allow-untyped-defs

from recipe_scrapers.grimgrains import GrimGrains
from tests import ScraperTest


class TestGrimGrainsScraper(ScraperTest):
scraper_class = GrimGrains

def test_host(self):
self.assertEqual("grimgrains.com", self.harvester_class.host())

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

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

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

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

def test_image(self):
self.harvester_class.url = "https://grimgrains.com/site/okonomiyaki.html"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note-to-future: this url attribute is set because we return a fully-qualified image URL, and when the tests run (on HTML, outside of a URL context) the base URL is test.example.com, which looks wrong.

self.assertEqual(
"https://grimgrains.com/media/recipes/okonomiyaki.jpg",
self.harvester_class.image(),
)

def test_ingredients(self):
self.assertEqual(
[
"nagaimo: 160 g, grated",
"green cabbage: 500 g, minced",
"chickpea flour: 85 g",
"nutritional yeast: 15 g",
"salt: 1.25 g",
"shiitake: 6",
"water: 320 ml",
"sesame oil: 10 ml",
"soy sauce: 60 ml",
"mirin: 60 ml",
"granulated sugar: 15 g",
"arrowroot starch: 15 g",
"ao nori: 30 g",
"beni shouga: to taste",
"scallions: 4 stalks",
],
self.harvester_class.ingredients(),
)

def test_instructions(self):
self.assertEqual(
(
"\n".join(
[
"Peel the nagaimo, then soak it in a water and vinegar solution (doing this helps to remove irritants). Dry the nagaimo, then grate 160 g (6-8cm) of nagaimo with a fine grater. Do this over a bowl, as the grated nagaimo is very slimy. Keep aside.",
"Mince 500 g (1 small head) green cabbage, keep aside.",
"In a bowl, mix 85 g (1 cup) of chickpea flour, 15 g (1/4 cup) nutritional yeast, the grated nagaimo, the minced green cabbage and 1.25g (1/4 tsp) of salt. Then, add 320 ml (1 1/4 cup) of water, or shiitake dashi. (for shiitake dashi, soak 5-6 shiitake in 320 ml of hot water for 15 minutes, or overnight in cold water.)",
"Heat a non-stick pan at high heat, add 5 ml (1 tsp) of sesame oil. If you throw some water on and it starts to sizzle, the pan is hot and you can add 1/4 of the batter. Alternatively, you can add 1/2, although this makes two very large portions.",
"Let okonomiyaki cook for 5 minutes, shaking the pan every now and then so the batter doesn't stick.",
"After 5 minutes, it's time to give the other side some grilling time. Put a plate on top of the pancake, keep your hand on the plate and flip the pan so that the pancake ends up cooked side up on the plate. Then, slide the pancake back into the pan (cooked side up). You can also just flip it with a spatula, but we rather like the plate method :).",
"Cook for another 5 minutes, then slide onto a plate, repeat process for the rest of the batter.",
"In a small bowl, mix 60 ml (4 tbsp) of soy sauce, 60 ml (4 tbsp) of mirin, 15 g (1 tbsp) of sugar and 15 g (1 tbsp) of arrowroot starch. Stir well.",
"Heat a pan at high heat, when hot, add sauce and cook for 2-3 minutes until it thickens. Then, divide onto your okonomiyaki.",
"First, add about 15 g (1 tbsp) (per okonomiyaki) of aonori on top of the sauce.",
"Then, add some beni shouga (pickled red ginger).",
"Finally, top off with some finely chopped scallions.",
]
)
),
self.harvester_class.instructions(),
)

def test_description(self):
self.assertEqual(
"Okonomiyaki (meaning, 'grilled as you like it') is a Japanese dish, similar to the American omelette, but the main difference is the variation of ingredients. Typical okonomiyaki are made with eggs, and often include meat or fish. Making it without meat is simple enough, but without eggs? Back when we lived in Tokyo, our experience in cooking with plants was limited, but now we've been doing it long enough that we can think of alternatives with ease.\nThe key ingredient? Chickpea flour. We make chickpea pancakes, and scrambled chickpea flour (resembles scrambled eggs) at home all the time. Chickpea flour is a staple on Pino, and works very well for okonomiyaki.\nNagaimo: If you're in a place were nagaimo (or yamaimo) is available, we highly reccommend adding it to the dish. It makes a fluffier pancake. Although we've made okonomiyaki without nagaimo before, so if you can't find it know that it will work and be very delicious anyway. It imparts little flavor, all it does is add nutrition and texture. Nagaimo, unlike most potatoes, can be eaten raw. However, it is best to handle the nagaimo with gloves, or to soak the peeled tuber in a vinegar-water solution to neutralize irritant oxalate crystals found on their skin. Nagaimo are low-calorie, high in protein, and have potassium, zinc, vitamin C and more. The texture of grated nagaimo can be off-putting, it looks like a regular tuber when whole, but when grated it becomes slime, almost liquid. This sort of texture is well-liked in Japan and referred to as being 'neba neba' (slimy). This texture present in many other foods like okra and nattou. This texture makes it an ideal egg alternative, it can be used to make deserts when baking.\nAonori: Aonori is another obscure ingredient, but again, it can be omitted, although it tastes really amazing with it. We made okonomiyaki without it when we were in Majuro, because it simply wasn't available, so we used finely cut nori instead. Obviously, this isn't a perfect substitution, because aonori is sweet and tastes nothing like nori. However, nori is still very delicious and pairs well enough with the okonomiyaki.\nOkonomi sauce\nIn this recipe, we don't use true 'okonomi sauce'. Why? Because we don't use many pre-made sauces, we prefer to make my own. Okonomi sauce requires many ingredients, and honestly, the sauce we've made works really well in this recipe and makes a good okonomi sauce alternative.\nIf you want to make your own, you can mix 7g (1 1/2 tsp) sugar, 45g (3 tbsp) ketchup and 45g (3 tbsp) of vegan worcestershire sauce. If you are like us, and don't care to buy pre-made sauces but want to avoid buying both ketchup and worcestershire sauce, you can make these too:\nWorcestershire sauce: combine apple cider vinegar, water, soy sauce, sugar, mustard powder, onion powder, garlic powder, cinnamon and black pepper in pan, bring to a boil and cook for a minute, then let cool.\nKetchup: Using some fresh tomato sauce may be enough, otherwise add a bit of sugar and apple cider vinegar to it.",
self.harvester_class.description(),
)