Skip to content

Commit

Permalink
Add support for americastestkitchen.com (#1060)
Browse files Browse the repository at this point in the history
Co-authored-by: James Addison <james@reciperadar.com>
  • Loading branch information
smilerz and jayaddison committed Jul 28, 2024
1 parent d34b6a1 commit cff269f
Show file tree
Hide file tree
Showing 9 changed files with 11,454 additions and 0 deletions.
99 changes: 99 additions & 0 deletions recipe_scrapers/americastestkitchen.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import functools
import json

from recipe_scrapers._grouping_utils import IngredientGroup

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


class AmericasTestKitchen(AbstractScraper):

@classmethod
def host(cls):
return "americastestkitchen.com"

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

def site_name(self):
"""Self-titled website"""
return self.author()

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

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

def total_time(self):
return get_minutes(self._get_additional_details.get("recipeTimeNote"))

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

def ingredients(self):
ingredients = []
for group in self._get_additional_details.get("ingredientGroups"):
ingredients += group["fields"]["recipeIngredientItems"]
return [self._parse_ingredient_item(i) for i in ingredients]

def ingredient_groups(self):
ingredient_groups = []
for group in self._get_additional_details.get("ingredientGroups"):
ingredients = [
self._parse_ingredient_item(i)
for i in group["fields"]["recipeIngredientItems"]
]
purpose = group["fields"]["title"] or None
ingredient_groups.append(
IngredientGroup(ingredients=ingredients, purpose=purpose)
)
return ingredient_groups

def instructions(self):
if headnote := self._get_additional_details.get("headnote"):
# We could import HTMLTagStripperPlugin, but that would make it -- an optional plugin -- a dependency.
headnote = f"Note: {normalize_string(headnote)}"
else:
headnote = ""
return "\n".join(
[headnote]
+ [
normalize_string(instruction["fields"]["content"])
for instruction in self._get_additional_details.get("instructions")
]
).lstrip("\n")

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

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

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

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

@staticmethod
def _parse_ingredient_item(ingredient_item):
fields = ingredient_item["fields"]
fragments = (
fields["qty"] or "",
fields["measurement"] or "",
fields["ingredient"]["fields"]["title"] or "",
fields["postText"] or "",
)
return (
" ".join(fragment.rstrip() for fragment in fragments if fragment)
.rstrip()
.replace(" ,", ",")
)

@functools.cached_property
def _get_additional_details(self):
j = json.loads(self.soup.find(type="application/json").string)
name = list(j["props"]["initialState"]["content"]["documents"])[0]
return j["props"]["initialState"]["content"]["documents"][name]
12 changes: 12 additions & 0 deletions recipe_scrapers/cookscountry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from ._exceptions import StaticValueException
from .americastestkitchen import AmericasTestKitchen


class CooksCountry(AmericasTestKitchen):

@classmethod
def host(cls):
return "cookscountry.com"

def site_name(self):
raise StaticValueException(return_value="Cook's Country")
12 changes: 12 additions & 0 deletions recipe_scrapers/cooksillustrated.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from ._exceptions import StaticValueException
from .americastestkitchen import AmericasTestKitchen


class CooksIllustrated(AmericasTestKitchen):

@classmethod
def host(cls):
return "cooksillustrated.com"

def site_name(self):
raise StaticValueException(return_value="Cook's Illustrated")
83 changes: 83 additions & 0 deletions tests/test_data/americastestkitchen.com/americastestkitchen.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
{
"author": "America's Test Kitchen",
"canonical_url": "https://www.americastestkitchen.com/recipes/1629-lasagna-bolognese-simplified",
"site_name": "America's Test Kitchen",
"host": "americastestkitchen.com",
"language": "en",
"title": "Lasagna Bolognese, Simplified",
"ingredients": [
"1 medium carrot, peeled and roughly chopped",
"1 medium rib celery, roughly chopped",
"1/2 small onion, roughly chopped",
"1 (28 ounce) can whole tomatoes with juice",
"2 tablespoons unsalted butter",
"8 ounces ground beef, preferably 90 percent lean",
"8 ounces ground pork",
"8 ounces ground veal",
"1 1/2 cups whole milk",
"1 1/2 cups dry white wine",
"2 tablespoons tomato paste",
"1 teaspoon table salt",
"1/4 teaspoon ground black pepper",
"4 tablespoons unsalted butter",
"1/4 cup unbleached all-purpose flour",
"4 cups whole milk",
"3/4 teaspoon table salt",
"15 sheets no-boil lasagna noodle (9 ounces)",
"4 ounces Parmesan cheese, grated (2 cups)"
],
"ingredient_groups": [
{
"ingredients": [
"1 medium carrot, peeled and roughly chopped",
"1 medium rib celery, roughly chopped",
"1/2 small onion, roughly chopped",
"1 (28 ounce) can whole tomatoes with juice",
"2 tablespoons unsalted butter",
"8 ounces ground beef, preferably 90 percent lean",
"8 ounces ground pork",
"8 ounces ground veal",
"1 1/2 cups whole milk",
"1 1/2 cups dry white wine",
"2 tablespoons tomato paste",
"1 teaspoon table salt",
"1/4 teaspoon ground black pepper"
],
"purpose": "Meat Sauce (Ragù)"
},
{
"ingredients": [
"4 tablespoons unsalted butter",
"1/4 cup unbleached all-purpose flour",
"4 cups whole milk",
"3/4 teaspoon table salt"
],
"purpose": "Béchamel"
},
{
"ingredients": [
"15 sheets no-boil lasagna noodle (9 ounces)",
"4 ounces Parmesan cheese, grated (2 cups)"
],
"purpose": "Noodles and Cheese"
}
],
"instructions": "Note: For assembly, both the meat sauce and the bechamel should be just warm to the touch, not piping hot. Both sauces can be made, cooled, and refrigerated up to 2 days ahead, then gently reheated until warm. In terms of flavor and texture, we find that Barilla no-boil noodles are the closest to fresh, but this recipe will work with all major brands of no-boil noodles.\nFor the meat sauce: Process carrot, celery, and onion in food processor until finely chopped, about ten 1-second pulses, scraping down bowl as necessary; transfer mixture to small bowl. Wipe out food processor workbowl; process tomatoes and juice until finely chopped, six to eight 1-second pulses. Heat butter in heavy-bottomed Dutch oven over medium heat until foaming; add carrot, celery, and onion and cook, stirring occasionally, until softened but not browned, about 4 minutes. Add ground meats and cook, breaking meat into 1-inch pieces with wooden spoon, about 1 minute. Add milk and stir, breaking meat into 1/2-inch bits; bring to simmer and cook, stirring to break meat into small pieces, until almost all liquid has evaporated, 20 to 30 minutes. Using potato masher or wooden spoon, break up any remaining clumps of meat (no large pieces should remain). Add wine and bring to simmer; cook, stirring occasionally, until liquid has evaporated, 20 to 30 minutes. Stir in tomato paste until combined, about 1 minute; add chopped tomatoes, salt, and pepper. Bring to simmer, then reduce heat to medium-low and cook until sauce is slightly thickened, about 15 minutes. (You should have about 6 cups meat sauce.) Transfer meat sauce to bowl and cool until just warm to touch, about 30 minutes.\nFor the béchamel: While meat sauce simmers, melt butter in medium saucepan over medium heat until foaming; add flour and cook, whisking constantly, until thoroughly combined, about 1 1/2 minutes; mixture should not brown. Gradually whisk in milk; increase heat to medium-high and bring to full boil, whisking frequently. Add salt, reduce heat to medium-low, and simmer 10 minutes, stirring occasionally with heatproof rubber spatula or wooden spoon, making sure to scrape bottom and corners of saucepan. (You should have about 3 1/3 cups.) Transfer béchamel to bowl and cool until just warm to touch, about 30 minutes.\nTo assemble and bake: Adjust oven rack to middle position; heat oven to 425 degrees. Place noodles in 13- by 9-inch baking dish and cover with very hot tap water; soak 5 minutes, agitating noodles occasionally to prevent sticking. Remove noodles from water, place in single layer on kitchen towel, and pat dry. Wipe out baking dish and spray lightly with nonstick cooking spray. Stir béchamel to recombine; mix 3/4 cup warm béchamel into warm meat sauce until thoroughly combined.\nDistribute 1 cup béchamel-enriched meat sauce in baking dish. Place three noodles in single layer on top of sauce, arranging them close together, but not touching, at center of pan. Spread 1 1/4 cups béchamel-enriched meat sauce evenly over noodles, spreading sauce to edge of noodles but not to edge of dish (see illustration 1). Drizzle 1/3 cup béchamel evenly over meat sauce (illustration 2). Sprinkle 1/3 cup Parmesan evenly over béchamel. Repeat layering of noodles, béchamel-enriched meat sauce, bechamel, and cheese 3 more times. Place final 3 noodles on top and cover completely with remaining béchamel, spreading béchamel with rubber spatula and allowing it to spill over noodles (illustration 3). Sprinkle evenly with remaining Parmesan.\nSpray large sheet foil with nonstick cooking spray and cover lasagna; bake until bubbling, about 30 minutes. Remove foil, increase heat to 450 degrees, and continue to bake until surface is spotty brown, about 15 minutes. Cool 15 minutes; cut into pieces and serve.",
"instructions_list": [
"Note: For assembly, both the meat sauce and the bechamel should be just warm to the touch, not piping hot. Both sauces can be made, cooled, and refrigerated up to 2 days ahead, then gently reheated until warm. In terms of flavor and texture, we find that Barilla no-boil noodles are the closest to fresh, but this recipe will work with all major brands of no-boil noodles.",
"For the meat sauce: Process carrot, celery, and onion in food processor until finely chopped, about ten 1-second pulses, scraping down bowl as necessary; transfer mixture to small bowl. Wipe out food processor workbowl; process tomatoes and juice until finely chopped, six to eight 1-second pulses. Heat butter in heavy-bottomed Dutch oven over medium heat until foaming; add carrot, celery, and onion and cook, stirring occasionally, until softened but not browned, about 4 minutes. Add ground meats and cook, breaking meat into 1-inch pieces with wooden spoon, about 1 minute. Add milk and stir, breaking meat into 1/2-inch bits; bring to simmer and cook, stirring to break meat into small pieces, until almost all liquid has evaporated, 20 to 30 minutes. Using potato masher or wooden spoon, break up any remaining clumps of meat (no large pieces should remain). Add wine and bring to simmer; cook, stirring occasionally, until liquid has evaporated, 20 to 30 minutes. Stir in tomato paste until combined, about 1 minute; add chopped tomatoes, salt, and pepper. Bring to simmer, then reduce heat to medium-low and cook until sauce is slightly thickened, about 15 minutes. (You should have about 6 cups meat sauce.) Transfer meat sauce to bowl and cool until just warm to touch, about 30 minutes.",
"For the béchamel: While meat sauce simmers, melt butter in medium saucepan over medium heat until foaming; add flour and cook, whisking constantly, until thoroughly combined, about 1 1/2 minutes; mixture should not brown. Gradually whisk in milk; increase heat to medium-high and bring to full boil, whisking frequently. Add salt, reduce heat to medium-low, and simmer 10 minutes, stirring occasionally with heatproof rubber spatula or wooden spoon, making sure to scrape bottom and corners of saucepan. (You should have about 3 1/3 cups.) Transfer béchamel to bowl and cool until just warm to touch, about 30 minutes.",
"To assemble and bake: Adjust oven rack to middle position; heat oven to 425 degrees. Place noodles in 13- by 9-inch baking dish and cover with very hot tap water; soak 5 minutes, agitating noodles occasionally to prevent sticking. Remove noodles from water, place in single layer on kitchen towel, and pat dry. Wipe out baking dish and spray lightly with nonstick cooking spray. Stir béchamel to recombine; mix 3/4 cup warm béchamel into warm meat sauce until thoroughly combined.",
"Distribute 1 cup béchamel-enriched meat sauce in baking dish. Place three noodles in single layer on top of sauce, arranging them close together, but not touching, at center of pan. Spread 1 1/4 cups béchamel-enriched meat sauce evenly over noodles, spreading sauce to edge of noodles but not to edge of dish (see illustration 1). Drizzle 1/3 cup béchamel evenly over meat sauce (illustration 2). Sprinkle 1/3 cup Parmesan evenly over béchamel. Repeat layering of noodles, béchamel-enriched meat sauce, bechamel, and cheese 3 more times. Place final 3 noodles on top and cover completely with remaining béchamel, spreading béchamel with rubber spatula and allowing it to spill over noodles (illustration 3). Sprinkle evenly with remaining Parmesan.",
"Spray large sheet foil with nonstick cooking spray and cover lasagna; bake until bubbling, about 30 minutes. Remove foil, increase heat to 450 degrees, and continue to bake until surface is spotty brown, about 15 minutes. Cool 15 minutes; cut into pieces and serve."
],
"category": "Main Courses, Casseroles",
"yields": "8 servings",
"description": "Could we adapt and simplify this northern Italian classic for the American kitchen?",
"total_time": 210.0,
"ratings": 4.4,
"nutrients": {
"calories": "5764 calories"
},
"image": "https://res.cloudinary.com/hksqkdlah/image/upload/ar_1:1,c_fill,dpr_2.0,f_auto,fl_lossy.progressive.strip_profile,g_faces:auto,q_auto:low,w_150/3801_so04-lasagnabolognes-article"
}
Loading

0 comments on commit cff269f

Please sign in to comment.