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 americastestkitchen.com #1060

Merged
merged 10 commits into from
Apr 22, 2024
4 changes: 4 additions & 0 deletions recipe_scrapers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
from .altonbrown import AltonBrown
from .amazingribs import AmazingRibs
from .ambitiouskitchen import AmbitiousKitchen
from .americastestkitchen import AmericasTestKitchen
from .archanaskitchen import ArchanasKitchen
from .argiro import Argiro
from .arla import Arla
Expand Down Expand Up @@ -332,6 +333,9 @@
AltonBrown.host(): AltonBrown,
AmazingRibs.host(): AmazingRibs,
AmbitiousKitchen.host(): AmbitiousKitchen,
AmericasTestKitchen.host(): AmericasTestKitchen,
AmericasTestKitchen.host(domain="cooksillustrated.com"): AmericasTestKitchen,
AmericasTestKitchen.host(domain="cookscountry.com"): AmericasTestKitchen,
jayaddison marked this conversation as resolved.
Show resolved Hide resolved
ArchanasKitchen.host(): ArchanasKitchen,
Argiro.host(): Argiro,
Arla.host(): Arla,
Expand Down
106 changes: 106 additions & 0 deletions recipe_scrapers/americastestkitchen.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# mypy: allow-untyped-defs

import functools
import json
import re

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, domain="americastestkitchen.com"):
return domain

def author(self):
return self.schema.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"]
ingredient_groups.append(
IngredientGroup(ingredients=ingredients, purpose=purpose)
)
return ingredient_groups

def instructions(self): # add headnote
if headnote := self._get_additional_details.get("headnote"):
# Ideally this would use HTMLTagStripperPlugin, but I'm not sure how to invoke it here
headnote = f"Note: {normalize_string(re.sub(r'<.*?>', '', headnote))}\n"
else:
headnote = ""
return headnote + self.schema.instructions()

def instructions_group(self):
if headnote := self._get_additional_details.get("headnote"):
# Ideally this would use HTMLTagStripperPlugin, but I'm not sure how to invoke it here
headnote = f"Note: {normalize_string(re.sub(r'<.*?>', '', headnote))}\n"
jayaddison marked this conversation as resolved.
Show resolved Hide resolved
else:
headnote = ""
return "\n".join(
[headnote]
+ [
self._get_additional_details.get("instruction")["fields"]["content"]
for instruction in self._get_additional_details.get("instruction")
]
)

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)
smilerz marked this conversation as resolved.
Show resolved Hide resolved
.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]
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",
"category": "Main Courses, Casseroles",
"description": "Could we adapt and simplify this northern Italian classic for the American kitchen?",
"host": "americastestkitchen.com",
"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",
"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"
}
],
"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)"
],
"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."
],
"language": "en",
"nutrients": {
"calories": "5764 calories"
},
"ratings": 4.4,
"site_name": null,
"title": "Lasagna Bolognese, Simplified",
"total_time": 210.0,
"yields": "8 servings"
}
Loading
Loading