-
Notifications
You must be signed in to change notification settings - Fork 525
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for americastestkitchen.com (#1060)
Co-authored-by: James Addison <james@reciperadar.com>
- Loading branch information
1 parent
d34b6a1
commit cff269f
Showing
9 changed files
with
11,454 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
83
tests/test_data/americastestkitchen.com/americastestkitchen.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} |
Oops, something went wrong.