-
Notifications
You must be signed in to change notification settings - Fork 542
/
kptncook.py
115 lines (91 loc) · 3.57 KB
/
kptncook.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
from urllib.parse import parse_qs, urlparse
import requests
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, proxies=None, timeout=None, *args, **kwargs):
super().__init__(url, proxies=proxies, timeout=timeout, *args, **kwargs)
parsed_url = urlparse(self.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 = requests.post(
api_url,
headers=HEADERS,
proxies=proxies,
timeout=timeout,
json=json_request_body,
).json()[0]
@classmethod
def host(cls, 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 None
def cuisine(self):
return None
def description(self):
return self.recipe_json["authorComment"]
def language(self):
return self.lang