Repository: hhursev/recipe-scrapers
Pull request #503 (2022-02-28 10:57:28)

Add support for kptncook recipes
# Add support for kptncook recipes
**example:** http://mobile.kptncook.com/recipe/pinterest/Low-Carb-Tarte-Flamb%C3%A9e-with-Serrano-Ham-%26-Cream-Cheese/315c3c32?lang=en

On the kptncook website the recipes are only provided in a redacted form (most of the instructions are missing) so that users are incentivized to install the app. 
However after analysing the app I found some API endpoints from which the whole recipe data can be requested as json. 
(My writedown of the results can be found here: https://github.com/gloriousDan/kptncook-api-reverse-engineering)

I hope that this is still in the scope of this scraping framework, since my implementation goes quite a long way from simple html scraping.

Since this is my first PR ever I'd love to get feedback on my implementation and the merging process :)


Patch:
diff --git a/recipe_scrapers/__init__.py b/recipe_scrapers/__init__.py
index 310d46b8..b61e9e30 100644
--- a/recipe_scrapers/__init__.py
+++ b/recipe_scrapers/__init__.py
@@ -86,6 +86,7 @@ from .kennymcgovern import KennyMcGovern
 from .kingarthur import KingArthur
 from .kochbar import Kochbar
 from .koket import Koket
+from .kptncook import KptnCook
 from .kuchniadomowa import KuchniaDomowa
 from .kwestiasmaku import KwestiaSmaku
 from .lecremedelacrumb import LeCremeDeLaCrumb
@@ -271,6 +272,8 @@ SCRAPERS = {
     KingArthur.host(): KingArthur,
     Kochbar.host(): Kochbar,
     Koket.host(): Koket,
+    KptnCook.host(): KptnCook,
+    KptnCook.host(subdomain="sharing"): KptnCook,
     KuchniaDomowa.host(): KuchniaDomowa,
     KwestiaSmaku.host(): KwestiaSmaku,
     LeCremeDeLaCrumb.host(): LeCremeDeLaCrumb,
diff --git a/recipe_scrapers/kptncook.py b/recipe_scrapers/kptncook.py
new file mode 100644
index 00000000..3e3f87f7
--- /dev/null
+++ b/recipe_scrapers/kptncook.py
@@ -0,0 +1,121 @@
+import json
+from urllib.parse import parse_qs, urlparse
+
+import requests
+
+from recipe_scrapers.settings import settings
+
+from ._abstract import AbstractScraper
+
+HEADERS = {
+    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0"
+}
+# These languages use measurements like cups and spoons rather than grams and liters
+IMPERIAL_LANGUAGES = ["en"]
+KPTN_DEFAULT_LANGUAGE = "en"
+
+
+class KptnCook(AbstractScraper):
+    def __init__(self, url, *args, **kwargs):
+        if settings.TEST_MODE:
+            self.recipe_json = json.loads(url.read())[0]
+            self.lang = KPTN_DEFAULT_LANGUAGE
+            self.final_url = "https://mobile.kptncook.com/recipe/pinterest/Low-Carb-Tarte-Flamb%C3%A9e-with-Serrano-Ham-%26-Cream-Cheese/315c3c32?lang=en"
+        else:
+            if urlparse(url).hostname == "mobile.kptncook.com":
+                parsed_url = urlparse(url)
+            else:
+                # If it's a sharing.kptncook.com/* link we first need to follow the http forwards to get the recipe ID
+                recipe_request = requests.get(url, headers=HEADERS)
+                parsed_url = urlparse(recipe_request.url)
+            # Extract language from URL
+            query = parse_qs(parsed_url.query)
+            self.lang = query["lang"][0] if "lang" in query else KPTN_DEFAULT_LANGUAGE
+
+            # Build final recipe url (of type mobile.kptncook.com/*)
+            self.final_url = "".join(
+                ["https://", parsed_url.hostname, parsed_url.path, f"?lang={self.lang}"]
+            )
+            # Extract recipe id from the url path
+            recipe_uid = parsed_url.path.split("/")[-1]
+
+            # Request the final recipe json from the kptncook api
+            api_url = f"https://mobile.kptncook.com/recipes/search?kptnkey=6q7QNKy-oIgk-IMuWisJ-jfN7s6&lang={self.lang}"
+            json_request_body = [{"uid": recipe_uid}]
+            self.recipe_json = json.loads(
+                requests.post(api_url, headers=HEADERS, json=json_request_body).content
+            )[0]
+
+    @classmethod
+    def host(self, subdomain="mobile"):
+        return f"{subdomain}.kptncook.com"
+
+    def author(self):
+        author = self.recipe_json["authors"][0]
+        return f"{author['name']} ({author['link']})"
+
+    def title(self):
+        return self.recipe_json["title"]
+
+    def category(self):
+        return self.recipe_json["rtype"]
+
+    def total_time(self):
+        return self.recipe_json["cookingTime"] + self.recipe_json["preparationTime"]
+
+    def cook_time(self):
+        return self.recipe_json["cookingTime"]
+
+    def prep_time(self):
+        return self.recipe_json["preparationTime"]
+
+    def yields(self):
+        return 1
+
+    def nutrients(self):
+        return self.recipe_json["recipeNutrition"]
+
+    def canonical_url(self):
+        return self.final_url
+
+    def image(self):
+        return f"{self.recipe_json['imageList'][0]['url']}?kptnkey=6q7QNKy-oIgk-IMuWisJ-jfN7s6"
+
+    def ingredients(self):
+        return [
+            " ".join(
+                str(x)
+                for x in
+                # The filter is needed because "measure" and "quantity" fields are not always provided
+                filter(
+                    None,
+                    [
+                        ingredient.get("quantity"),
+                        ingredient.get("measure"),
+                        ingredient["ingredient"]["title"],
+                    ]
+                    if self.lang not in IMPERIAL_LANGUAGES
+                    else [
+                        ingredient.get("quantityUS"),
+                        ingredient.get("measureUS"),
+                        ingredient["ingredient"]["title"],
+                    ],
+                )
+            )
+            for ingredient in self.recipe_json["ingredients"]
+        ]
+
+    def instructions(self):
+        return "\n".join(step["title"] for step in self.recipe_json["steps"])
+
+    def ratings(self):
+        return ""
+
+    def cuisine(self):
+        return ""
+
+    def description(self):
+        return self.recipe_json["authorComment"]
+
+    def language(self):
+        return self.lang


Test patch:
diff --git a/tests/test_data/kptncook.testhtml b/tests/test_data/kptncook.testhtml
new file mode 100644
index 00000000..ded03a6a
--- /dev/null
+++ b/tests/test_data/kptncook.testhtml
@@ -0,0 +1,811 @@
+[
+    {
+        "_id": {
+            "$oid": "59b68c8d2c00000b0535125a"
+        },
+        "title": "Low Carb Tarte Flambée with Serrano Ham & Cream Cheese",
+        "rtype": "Pork",
+        "gdocs": "1046",
+        "authorComment": "Flourless but so delicious!",
+        "uid": "315c3c32",
+        "country": "us/de/ww",
+        "otherIngred": "each: 1/4 tsp chili flakes, 1/2 tsp Italian seasoning, salt, pepper",
+        "preparationTime": 20,
+        "cookingTime": 5,
+        "recipeNutrition": {
+            "calories": 368,
+            "protein": 34,
+            "fat": 22,
+            "carbohydrate": 9
+        },
+        "activeTags": [
+            "main_ingredient_meat",
+            "main_dish",
+            "italian",
+            "diet_low_carb",
+            "baked",
+            "calories_under_400",
+            "calories_under_500",
+            "calories_under_600",
+            "budget_above_15_us",
+            "budget_under_10_de",
+            "cooking_time_under_20"
+        ],
+        "steps": [
+            {
+                "title": "All set?",
+                "image": {
+                    "name": "REZ_146_01.jpg",
+                    "url": "https://d2am1qai33sroc.cloudfront.net/image/59b8e8d5950000fc1649a085"
+                }
+            },
+            {
+                "title": "Preheat oven to 360°F (conventional, recommended) or 325°F (fan).",
+                "image": {
+                    "name": "REZ_1046_02.jpg",
+                    "url": "https://d2am1qai33sroc.cloudfront.net/image/617a6f76380000320017b5f1"
+                }
+            },
+            {
+                "title": "Mix curd, eggs, chili flakes, herbes de Provence, salt, and pepper to a bowl and mix.",
+                "ingredients": [
+                    {
+                        "ingredientId": "52f2c5cc3c44e47a103dbc1b",
+                        "title": "cream cheese / curd cheese",
+                        "numberTitle": {
+                            "singular": "cream cheese / curd cheese",
+                            "plural": "cream cheese / curd cheese"
+                        }
+                    },
+                    {
+                        "ingredientId": "52bc0f4f91217a1c01a8ad4e",
+                        "title": "egg",
+                        "numberTitle": {
+                            "singular": "egg",
+                            "plural": "eggs"
+                        }
+                    },
+                    {
+                        "unit": {
+                            "quantity": 0.25,
+                            "measure": "tsp"
+                        },
+                        "ingredientId": "55364246490000711350a3d2",
+                        "title": "chili flake",
+                        "numberTitle": {
+                            "singular": "chili flake",
+                            "plural": "chili flakes"
+                        }
+                    },
+                    {
+                        "unit": {
+                            "quantity": 0.5,
+                            "measure": "tsp"
+                        },
+                        "ingredientId": "573c58c503010014069aeb26",
+                        "title": "herbes de Provence, dried",
+                        "numberTitle": {
+                            "singular": "herbes de Provence, dried",
+                            "plural": "herbes de Provence, dried"
+                        }
+                    },
+                    {
+                        "ingredientId": "553652364900001b1550a3ea",
+                        "title": "salt",
+                        "numberTitle": {
+                            "singular": "salt",
+                            "plural": "salt"
+                        }
+                    },
+                    {
+                        "ingredientId": "55365112490000f41450a3e6",
+                        "title": "pepper",
+                        "numberTitle": {
+                            "singular": "pepper",
+                            "plural": "pepper"
+                        }
+                    }
+                ],
+                "image": {
+                    "name": "REZ_146_03.jpg",
+                    "url": "https://d2am1qai33sroc.cloudfront.net/image/59b8e8f69500000e1749a087"
+                }
+            },
+            {
+                "title": "Place the mass on a lined baking sheet, spread thinly (make sure the layer is very thin) and bake for 10-15 min.",
+                "image": {
+                    "name": "REZ_146_04.jpg",
+                    "url": "https://d2am1qai33sroc.cloudfront.net/image/59b8e9019500001f1749a088"
+                }
+            },
+            {
+                "title": "Wash and slice cherry tomatoes.",
+                "ingredients": [
+                    {
+                        "unit": {
+                            "quantity": 0.5,
+                            "measure": "cup(s)"
+                        },
+                        "ingredientId": "53380b3985ff3b0413d0aa38",
+                        "title": "cherry tomato",
+                        "numberTitle": {
+                            "singular": "cherry tomato",
+                            "plural": "cherry tomatoes"
+                        }
+                    }
+                ],
+                "image": {
+                    "name": "REZ_1046_05.jpg",
+                    "url": "https://d2am1qai33sroc.cloudfront.net/image/61aa1f254a0000de07d9d05f"
+                }
+            },
+            {
+                "title": "Wash and drain arugula.",
+                "ingredients": [
+                    {
+                        "unit": {
+                            "quantity": 0.5,
+                            "measure": "cup(s)"
+                        },
+                        "ingredientId": "52bc2f8991217a5903a8adb1",
+                        "title": "arugula",
+                        "numberTitle": {
+                            "singular": "arugula",
+                            "plural": "arugula"
+                        }
+                    }
+                ],
+                "image": {
+                    "name": "REZ_1046_06.jpg",
+                    "url": "https://d2am1qai33sroc.cloudfront.net/image/60768f7f6c00008d00f17e45"
+                }
+            },
+            {
+                "title": "Remove the base from the oven and spread with cream cheese. Top with tomatoes and Serrano ham, and bake for another 5 min.",
+                "ingredients": [
+                    {
+                        "unit": {
+                            "quantity": 2.0,
+                            "measure": "tbsp"
+                        },
+                        "ingredientId": "52fbdfc4a4999b810faeebb7",
+                        "title": "cream cheese",
+                        "numberTitle": {
+                            "singular": "cream cheese",
+                            "plural": "cream cheese"
+                        }
+                    },
+                    {
+                        "unit": {
+                            "quantity": 2.0,
+                            "measure": "slice(s)"
+                        },
+                        "ingredientId": "52bc0f1791217a0b01a8ad4c",
+                        "title": "serrano ham",
+                        "numberTitle": {
+                            "singular": "serrano ham",
+                            "plural": "serrano ham"
+                        }
+                    }
+                ],
+                "image": {
+                    "name": "REZ_146_07.jpg",
+                    "url": "https://d2am1qai33sroc.cloudfront.net/image/59b8e922950000f51649a08b"
+                }
+            },
+            {
+                "title": "Remove the tarte flambée from the oven, top with arugula, and enjoy.",
+                "image": {
+                    "name": "REZ_146_08.jpg",
+                    "url": "https://d2am1qai33sroc.cloudfront.net/image/59b8e92b950000261749a08c"
+                }
+            }
+        ],
+        "authors": [
+            {
+                "_id": {
+                    "$oid": "56e684d215000061074f263c"
+                },
+                "name": "Laura",
+                "link": "http://lauraleanskitchen.com/de/",
+                "title": "LauraLean's Kitchen",
+                "description": "Creative fitness foodie who turns everyday recipes into healthy delicacies!",
+                "facebook": "https://www.facebook.com/lauraleanskitchen",
+                "instagram": "https://www.instagram.com/lauralean_/",
+                "twitter": "http://www.twitter.com/lauralean_",
+                "sponsor": "no",
+                "authorImage": {
+                    "name": "Author_Laura_LauraLeansKitchen.png",
+                    "url": "https://d2am1qai33sroc.cloudfront.net/image/56e9a3111500007d0f78af0c"
+                },
+                "creationDate": {
+                    "$date": 1457947858251
+                },
+                "updateDate": {
+                    "$date": 1641942974501
+                }
+            }
+        ],
+        "retailers": [],
+        "ingredients": [
+            {
+                "quantity": 1.0,
+                "metricQuantity": 1.0,
+                "quantityUS": 1.0,
+                "imperialQuantity": 1.0,
+                "quantityUSProd": 1.0,
+                "imperialProductQuantity": 1.0,
+                "ingredient": {
+                    "_id": {
+                        "$oid": "52bc0f4f91217a1c01a8ad4e"
+                    },
+                    "typ": "regular",
+                    "title": "egg",
+                    "numberTitle": {
+                        "singular": "egg",
+                        "plural": "eggs"
+                    },
+                    "category": "DairyEggs",
+                    "key": "egg(s)Ei(er)",
+                    "desc": "egg(s)Ei(er)",
+                    "image": {
+                        "name": "dairy_egg_s.jpg",
+                        "url": "https://d2am1qai33sroc.cloudfront.net/image/5bb635752800001f1cc51d75"
+                    },
+                    "products": [],
+                    "isSponsored": false,
+                    "measures": {
+                        "de": [
+                            "1x",
+                            "2x",
+                            "3x",
+                            "6x",
+                            "10x"
+                        ],
+                        "us": [
+                            "1x",
+                            "2x",
+                            "3x",
+                            "6x",
+                            "12x",
+                            "18x"
+                        ]
+                    },
+                    "synonym": "egg, eggs",
+                    "brands": [],
+                    "creationDate": {
+                        "$date": 1388056399540
+                    },
+                    "updateDate": {
+                        "$date": 1642022354207
+                    }
+                }
+            },
+            {
+                "quantity": 100.0,
+                "measure": "g",
+                "metricQuantity": 100.0,
+                "metricMeasure": "g",
+                "quantityUS": 0.5,
+                "measureUS": "cup(s)",
+                "imperialQuantity": 0.5,
+                "imperialMeasure": "cup(s)",
+                "quantityUSProd": 3.5,
+                "measureUSProd": "oz",
+                "imperialProductQuantity": 3.5,
+                "imperialProductMeasure": "oz",
+                "ingredient": {
+                    "_id": {
+                        "$oid": "52f2c5cc3c44e47a103dbc1b"
+                    },
+                    "typ": "regular",
+                    "title": "cream cheese / curd cheese",
+                    "numberTitle": {
+                        "singular": "cream cheese / curd cheese",
+                        "plural": "cream cheese / curd cheese"
+                    },
+                    "category": "DairyEggs",
+                    "key": "cream_cheese_/_curd_cheesequarkcream_cheese_/_curd_cheesequark",
+                    "desc": "cream_cheese_/_curd_cheesequarkcream_cheese_/_curd_cheesequark",
+                    "image": {
+                        "name": "Dairy_cheese-soft_s.jpg",
+                        "url": "https://d2am1qai33sroc.cloudfront.net/image/5bb6311a2800004d1bc51d63"
+                    },
+                    "products": [],
+                    "isSponsored": false,
+                    "measures": {
+                        "de": [
+                            "1x",
+                            "2x",
+                            "3x",
+                            "250g",
+                            "500g"
+                        ],
+                        "us": [
+                            "1x",
+                            "2x",
+                            "3x",
+                            "8 oz",
+                            "16 oz",
+                            "32 oz"
+                        ]
+                    },
+                    "brands": [],
+                    "creationDate": {
+                        "$date": 1391642060771
+                    },
+                    "updateDate": {
+                        "$date": 1643020417751
+                    }
+                }
+            },
+            {
+                "ingredient": {
+                    "_id": {
+                        "$oid": "553652364900001b1550a3ea"
+                    },
+                    "typ": "basic",
+                    "title": "salt",
+                    "numberTitle": {
+                        "singular": "salt",
+                        "plural": "salt"
+                    },
+                    "category": "SpicesSeasoning",
+                    "key": "saltSalz",
+                    "desc": "saltSalz",
+                    "image": {
+                        "name": "spices_salt_s.jpg",
+                        "url": "https://d2am1qai33sroc.cloudfront.net/image/5bb7819a27000039048a8f79"
+                    },
+                    "products": [],
+                    "isSponsored": false,
+                    "measures": {
+                        "de": [
+                            "1x",
+                            "2x",
+                            "3x"
+                        ],
+                        "us": [
+                            "1x",
+                            "2x",
+                            "3x"
+                        ]
+                    },
+                    "brands": [],
+                    "creationDate": {
+                        "$date": 1429623350034
+                    },
+                    "updateDate": {
+                        "$date": 1643130701517
+                    }
+                }
+            },
+            {
+                "ingredient": {
+                    "_id": {
+                        "$oid": "55365112490000f41450a3e6"
+                    },
+                    "typ": "basic",
+                    "title": "pepper",
+                    "numberTitle": {
+                        "singular": "pepper",
+                        "plural": "pepper"
+                    },
+                    "category": "SpicesSeasoning",
+                    "key": "pepperPfeffer",
+                    "desc": "pepperPfeffer",
+                    "image": {
+                        "name": "spices_pepper_s.jpg",
+                        "url": "https://d2am1qai33sroc.cloudfront.net/image/5bbb78e82800009a00888456"
+                    },
+                    "products": [],
+                    "isSponsored": false,
+                    "measures": {
+                        "de": [
+                            "1x",
+                            "2x",
+                            "3x",
+                            "50g"
+                        ],
+                        "us": [
+                            "1x",
+                            "2x",
+                            "3x"
+                        ]
+                    },
+                    "brands": [],
+                    "creationDate": {
+                        "$date": 1429623058725
+                    },
+                    "updateDate": {
+                        "$date": 1643130135274
+                    }
+                }
+            },
+            {
+                "quantity": 40.0,
+                "measure": "g",
+                "metricQuantity": 40.0,
+                "metricMeasure": "g",
+                "quantityUS": 2.0,
+                "measureUS": "tbsp",
+                "imperialQuantity": 2.0,
+                "imperialMeasure": "tbsp",
+                "quantityUSProd": 1.4,
+                "measureUSProd": "oz",
+                "imperialProductQuantity": 1.4,
+                "imperialProductMeasure": "oz",
+                "ingredient": {
+                    "_id": {
+                        "$oid": "52fbdfc4a4999b810faeebb7"
+                    },
+                    "typ": "regular",
+                    "title": "cream cheese",
+                    "numberTitle": {
+                        "singular": "cream cheese",
+                        "plural": "cream cheese"
+                    },
+                    "category": "DairyEggs",
+                    "key": "cream_cheesefrischkäsecream_cheesefrischkäse",
+                    "desc": "cream_cheesefrischkäsecream_cheesefrischkäse",
+                    "image": {
+                        "name": "dairy_cheese-creme_s.jpg",
+                        "url": "https://d2am1qai33sroc.cloudfront.net/image/5bbb66f72800002123a3e27b"
+                    },
+                    "products": [],
+                    "isSponsored": false,
+                    "measures": {
+                        "de": [
+                            "1x",
+                            "2x",
+                            "3x",
+                            "150g",
+                            "200g",
+                            "400g"
+                        ],
+                        "us": [
+                            "1x",
+                            "2x",
+                            "3x",
+                            "8 oz",
+                            "16 oz",
+                            "32 oz"
+                        ]
+                    },
+                    "brands": [],
+                    "creationDate": {
+                        "$date": 1392238532572
+                    },
+                    "updateDate": {
+                        "$date": 1643038212699
+                    }
+                }
+            },
+            {
+                "quantity": 30.0,
+                "measure": "g",
+                "metricQuantity": 30.0,
+                "metricMeasure": "g",
+                "quantityUS": 2.0,
+                "measureUS": "slice(s)",
+                "imperialQuantity": 2.0,
+                "imperialMeasure": "slice(s)",
+                "quantityUSProd": 1.0,
+                "measureUSProd": "oz",
+                "imperialProductQuantity": 1.0,
+                "imperialProductMeasure": "oz",
+                "ingredient": {
+                    "_id": {
+                        "$oid": "52bc0f1791217a0b01a8ad4c"
+                    },
+                    "typ": "regular",
+                    "title": "serrano ham",
+                    "numberTitle": {
+                        "singular": "serrano ham",
+                        "plural": "serrano ham"
+                    },
+                    "category": "Meat",
+                    "key": "serranohamserrano_schinkenserranohamserrano_schinken",
+                    "desc": "serranohamserrano_schinkenserranohamserrano_schinken",
+                    "image": {
+                        "name": "meat_ham-serrano_s.jpg",
+                        "url": "https://d2am1qai33sroc.cloudfront.net/image/5bb77655270000eb018a8f4b"
+                    },
+                    "products": [],
+                    "isSponsored": false,
+                    "measures": {
+                        "de": [
+                            "1x",
+                            "2x",
+                            "3x",
+                            "100g",
+                            "200g"
+                        ],
+                        "us": [
+                            "1x",
+                            "2x",
+                            "3x",
+                            "4 oz",
+                            "8 oz",
+                            "12 oz"
+                        ]
+                    },
+                    "brands": [],
+                    "creationDate": {
+                        "$date": 1388056343862
+                    },
+                    "updateDate": {
+                        "$date": 1643037237574
+                    }
+                }
+            },
+            {
+                "quantity": 10.0,
+                "measure": "g",
+                "metricQuantity": 10.0,
+                "metricMeasure": "g",
+                "quantityUS": 0.5,
+                "measureUS": "cup(s)",
+                "imperialQuantity": 0.5,
+                "imperialMeasure": "cup(s)",
+                "quantityUSProd": 0.4,
+                "measureUSProd": "oz",
+                "imperialProductQuantity": 0.4,
+                "imperialProductMeasure": "oz",
+                "ingredient": {
+                    "_id": {
+                        "$oid": "52bc2f8991217a5903a8adb1"
+                    },
+                    "typ": "regular",
+                    "title": "arugula",
+                    "numberTitle": {
+                        "singular": "arugula",
+                        "plural": "arugula"
+                    },
+                    "category": "FruitVegetables",
+                    "key": "arugulaRucola",
+                    "desc": "arugulaRucola",
+                    "image": {
+                        "name": "veg_arugula_s.jpg",
+                        "url": "https://d2am1qai33sroc.cloudfront.net/image/5bb610a42800007915c51cdb"
+                    },
+                    "products": [],
+                    "isSponsored": false,
+                    "measures": {
+                        "de": [
+                            "1x",
+                            "2x",
+                            "100g",
+                            "125g",
+                            "200g",
+                            "250g"
+                        ],
+                        "us": [
+                            "1x",
+                            "2x",
+                            "6 oz",
+                            "8 oz"
+                        ]
+                    },
+                    "brands": [],
+                    "creationDate": {
+                        "$date": 1388064649534
+                    },
+                    "updateDate": {
+                        "$date": 1643020448911
+                    }
+                }
+            },
+            {
+                "quantity": 80.0,
+                "measure": "g",
+                "metricQuantity": 80.0,
+                "metricMeasure": "g",
+                "quantityUS": 0.5,
+                "measureUS": "cup(s)",
+                "imperialQuantity": 0.5,
+                "imperialMeasure": "cup(s)",
+                "quantityUSProd": 3.0,
+                "measureUSProd": "oz",
+                "imperialProductQuantity": 3.0,
+                "imperialProductMeasure": "oz",
+                "ingredient": {
+                    "_id": {
+                        "$oid": "53380b3985ff3b0413d0aa38"
+                    },
+                    "typ": "regular",
+                    "title": "cherry tomato",
+                    "numberTitle": {
+                        "singular": "cherry tomato",
+                        "plural": "cherry tomatoes"
+                    },
+                    "category": "FruitVegetables",
+                    "key": "cherry_tomatocherrytomatecherry_tomatoescherrytomaten",
+                    "desc": "cherry_tomatocherrytomatecherry_tomatoescherrytomaten",
+                    "image": {
+                        "name": "veg_cherry-tomatoes_s.jpg",
+                        "url": "https://d2am1qai33sroc.cloudfront.net/image/5bb62302280000f018c51d2d"
+                    },
+                    "products": [],
+                    "isSponsored": false,
+                    "measures": {
+                        "de": [
+                            "1x",
+                            "2x",
+                            "100g",
+                            "150g",
+                            "250g"
+                        ],
+                        "us": [
+                            "1x",
+                            "2x",
+                            "6 oz",
+                            "12 oz",
+                            "16 oz"
+                        ]
+                    },
+                    "brands": [],
+                    "creationDate": {
+                        "$date": 1396181817373
+                    },
+                    "updateDate": {
+                        "$date": 1644407488098
+                    }
+                }
+            },
+            {
+                "quantity": 0.5,
+                "measure": "TL",
+                "metricQuantity": 0.5,
+                "metricMeasure": "tsp",
+                "quantityUS": 0.5,
+                "measureUS": "tsp",
+                "imperialQuantity": 0.5,
+                "imperialMeasure": "tsp",
+                "quantityUSProd": 0.5,
+                "imperialProductQuantity": 0.5,
+                "ingredient": {
+                    "_id": {
+                        "$oid": "573c58c503010014069aeb26"
+                    },
+                    "typ": "basic",
+                    "title": "herbes de Provence, dried",
+                    "numberTitle": {
+                        "singular": "herbes de Provence, dried",
+                        "plural": "herbes de Provence, dried"
+                    },
+                    "category": "SpicesSeasoning",
+                    "key": "herbes_de_provence,driedkräuter_der_provence,getrocknetherbes_de_provence,driedkräuter_der_provence,getrocknet",
+                    "desc": "herbes_de_provence,driedkräuter_der_provence,getrocknetherbes_de_provence,driedkräuter_der_provence,getrocknet",
+                    "image": {
+                        "name": "spices_herbes-de-provence-dried_s.jpg",
+                        "url": "https://d2am1qai33sroc.cloudfront.net/image/5bb754e6290000b7126fb1f6"
+                    },
+                    "products": [],
+                    "isSponsored": false,
+                    "measures": {
+                        "de": [
+                            "1x",
+                            "2x",
+                            "3x"
+                        ],
+                        "us": [
+                            "1x",
+                            "2x",
+                            "3x"
+                        ]
+                    },
+                    "brands": [],
+                    "creationDate": {
+                        "$date": 1463572677900
+                    },
+                    "updateDate": {
+                        "$date": 1642022153802
+                    }
+                }
+            },
+            {
+                "quantity": 0.25,
+                "measure": "TL",
+                "metricQuantity": 0.25,
+                "metricMeasure": "tsp",
+                "quantityUS": 0.25,
+                "measureUS": "tsp",
+                "imperialQuantity": 0.25,
+                "imperialMeasure": "tsp",
+                "quantityUSProd": 0.25,
+                "imperialProductQuantity": 0.25,
+                "ingredient": {
+                    "_id": {
+                        "$oid": "55364246490000711350a3d2"
+                    },
+                    "typ": "basic",
+                    "title": "chili flake",
+                    "numberTitle": {
+                        "singular": "chili flake",
+                        "plural": "chili flakes"
+                    },
+                    "category": "SpicesSeasoning",
+                    "key": "chiliflakeschiliflockenchiliflakeschiliflocken",
+                    "desc": "chiliflakeschiliflockenchiliflakeschiliflocken",
+                    "image": {
+                        "name": "spices_chili-flakes_s.jpg",
+                        "url": "https://d2am1qai33sroc.cloudfront.net/image/5bb625b02800007619c51d35"
+                    },
+                    "products": [],
+                    "isSponsored": false,
+                    "measures": {
+                        "de": [
+                            "1x",
+                            "2x",
+                            "50g"
+                        ],
+                        "us": [
+                            "1x",
+                            "2x",
+                            "3x"
+                        ]
+                    },
+                    "brands": [],
+                    "creationDate": {
+                        "$date": 1429619270586
+                    },
+                    "updateDate": {
+                        "$date": 1642022214316
+                    }
+                }
+            }
+        ],
+        "imageList": [
+            {
+                "name": "REZ_1046_Cover.jpg",
+                "url": "https://d2am1qai33sroc.cloudfront.net/image/59b8e8a6950000a90949a083",
+                "type": "cover"
+            },
+            {
+                "name": "REZ_1046_Fav.jpg",
+                "url": "https://d2am1qai33sroc.cloudfront.net/image/59b8e8b1950000fd1649a084",
+                "type": "favorite"
+            },
+            {
+                "name": "REZ_146_09.jpg",
+                "url": "https://d2am1qai33sroc.cloudfront.net/image/59b8e934950000231749a08d",
+                "type": "blurred"
+            }
+        ],
+        "localizedPublishDate": {
+            "en": {
+                "$date": 1640736000000
+            },
+            "de": {
+                "$date": 1640736000000
+            }
+        },
+        "trackingMode": "no",
+        "feature": "none",
+        "publishDuration": {
+            "en": 1,
+            "de": 1
+        },
+        "ingredientTags": "Chiliflocken,chili flakes,Ei(er),egg(s),Quark,cream cheese / curd cheese,Kräuter der Provence, getrocknet,Italian seasoning, dried,Salz,salt,Pfeffer,pepper,Frischkäse,cream cheese,Serrano Schinken,Prosciutto,Cherrytomaten,cherry tomatoes,Rucola,arugula",
+        "favoriteCount": 97529,
+        "publishDates": {
+            "en": [
+                {
+                    "$date": 1640743200000
+                }
+            ],
+            "de": [
+                {
+                    "$date": 1640743200000
+                }
+            ]
+        },
+        "creationDate": {
+            "$date": 1505135757925
+        },
+        "updateDate": {
+            "$date": 1645950888626
+        }
+    }
+]
\ No newline at end of file
diff --git a/tests/test_kptncook.py b/tests/test_kptncook.py
new file mode 100644
index 00000000..e260326e
--- /dev/null
+++ b/tests/test_kptncook.py
@@ -0,0 +1,87 @@
+from recipe_scrapers.kptncook import KptnCook
+from tests import ScraperTest
+
+
+class TestKptnCookScraper(ScraperTest):
+
+    scraper_class = KptnCook
+
+    def test_host(self):
+        self.assertEqual("mobile.kptncook.com", self.harvester_class.host())
+
+    def test_author(self):
+        self.assertEqual(
+            "Laura (http://lauraleanskitchen.com/de/)", self.harvester_class.author()
+        )
+
+    def test_title(self):
+        self.assertEqual(
+            "Low Carb Tarte Flambée with Serrano Ham & Cream Cheese",
+            self.harvester_class.title(),
+        )
+
+    def test_category(self):
+        self.assertEqual("Pork", self.harvester_class.category())
+
+    def test_total_time(self):
+        self.assertEqual(25, self.harvester_class.total_time())
+
+    def test_prep_time(self):
+        self.assertEqual(20, self.harvester_class.prep_time())
+
+    def test_cook_time(self):
+        self.assertEqual(5, self.harvester_class.cook_time())
+
+    def test_yields(self):
+        self.assertEqual(1, self.harvester_class.yields())
+
+    def test_image(self):
+        self.assertEqual(
+            "https://d2am1qai33sroc.cloudfront.net/image/59b8e8a6950000a90949a083?kptnkey=6q7QNKy-oIgk-IMuWisJ-jfN7s6",
+            self.harvester_class.image(),
+        )
+
+    def test_ingredients(self):
+        ingredients = [
+            "1.0 egg",
+            "0.5 cup(s) cream cheese / curd cheese",
+            "salt",
+            "pepper",
+            "2.0 tbsp cream cheese",
+            "2.0 slice(s) serrano ham",
+            "0.5 cup(s) arugula",
+            "0.5 cup(s) cherry tomato",
+            "0.5 tsp herbes de Provence, dried",
+            "0.25 tsp chili flake",
+        ]
+        self.assertEqual(ingredients, self.harvester_class.ingredients())
+
+    def test_instructions(self):
+        instructions = "All set?\nPreheat oven to 360°F (conventional, recommended) or 325°F (fan).\nMix curd, eggs, chili flakes, herbes de Provence, salt, and pepper to a bowl and mix.\nPlace the mass on a lined baking sheet, spread thinly (make sure the layer is very thin) and bake for 10-15 min.\nWash and slice cherry tomatoes.\nWash and drain arugula.\nRemove the base from the oven and spread with cream cheese. Top with tomatoes and Serrano ham, and bake for another 5 min.\nRemove the tarte flambée from the oven, top with arugula, and enjoy."
+        self.assertEqual(instructions, self.harvester_class.instructions())
+
+    def test_ratings(self):
+        self.assertEqual("", self.harvester_class.ratings())
+
+    def test_cuisine(self):
+        self.assertEqual("", self.harvester_class.cuisine())
+
+    def test_description(self):
+        self.assertEqual(
+            "Flourless but so delicious!", self.harvester_class.description()
+        )
+
+    def test_language(self):
+        self.assertEqual("en", self.harvester_class.language())
+
+    def test_nutrients(self):
+        self.assertEqual(
+            {"calories": 368, "protein": 34, "fat": 22, "carbohydrate": 9},
+            self.harvester_class.nutrients(),
+        )
+
+    def test_canonical_url(self):
+        self.assertEqual(
+            "https://mobile.kptncook.com/recipe/pinterest/Low-Carb-Tarte-Flamb%C3%A9e-with-Serrano-Ham-%26-Cream-Cheese/315c3c32?lang=en",
+            self.harvester_class.canonical_url(),
+        )