munch-ease-backend/recipes/scraping.py

92 lines
2.5 KiB
Python
Raw Permalink Normal View History

2024-01-13 03:21:38 +00:00
import json
2025-10-18 03:26:42 +00:00
from typing import Optional
import httpx
from bs4 import BeautifulSoup
2024-01-13 03:21:38 +00:00
2024-10-01 10:02:32 +00:00
HEADERS = {
2025-10-18 03:26:42 +00:00
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.5",
"DNT": "1",
"Sec-GPC": "1",
"Connection": "keep-alive",
"Upgrade-Insecure-Requests": "1",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Sec-Fetch-User": "?1",
"Priority": "u=1",
"Pragma": "no-cache",
"Cache-Control": "no-cache",
2024-10-01 10:02:32 +00:00
}
2025-10-18 03:26:42 +00:00
def _is_recipe_ldata(ldata_node) -> bool:
if "@type" in ldata_node:
typ = ldata_node["@type"]
2024-01-13 05:40:10 +00:00
if isinstance(typ, list):
typ = typ[0]
2024-01-13 03:21:38 +00:00
2025-10-18 03:26:42 +00:00
if isinstance(typ, str) and typ.lower() == "recipe":
2024-01-13 05:40:10 +00:00
return True
2025-10-18 03:26:42 +00:00
return False
async def scrape_recipe_ldata(url: str) -> Optional[dict]:
2024-01-13 03:21:38 +00:00
# Load the requested URL with headers
async with httpx.AsyncClient() as client:
2024-10-01 10:02:32 +00:00
response = await client.get(url, headers=HEADERS, follow_redirects=True)
if response.status_code >= 300:
return None
2024-01-13 03:21:38 +00:00
# Extract the recipe ld+json data
2025-10-18 03:26:42 +00:00
soup = BeautifulSoup(response.text, "html.parser")
for ld in soup.find_all("script", type="application/ld+json"):
2024-01-13 03:21:38 +00:00
try:
data = json.loads(ld.text)
2025-10-18 03:26:42 +00:00
# _dump_json_data_to_log(data)
2024-01-13 05:40:10 +00:00
if _is_recipe_ldata(data):
2024-01-13 03:21:38 +00:00
return data
2025-10-18 03:26:42 +00:00
if "@graph" in data:
for item in data["@graph"]:
2024-01-13 05:40:10 +00:00
if _is_recipe_ldata(item):
return item
2025-10-18 03:26:42 +00:00
2024-01-13 05:40:10 +00:00
if isinstance(data, list):
for item in data:
if _is_recipe_ldata(item):
2024-01-13 03:21:38 +00:00
return item
except (json.decoder.JSONDecodeError, KeyError):
pass
2025-10-18 03:26:42 +00:00
2024-01-13 03:21:38 +00:00
return None
2024-01-13 05:40:10 +00:00
2025-10-18 03:26:42 +00:00
# Fallback return to satisfy static analysis
return None
2024-01-13 03:21:38 +00:00
def _dump_json_data_to_log(data: dict) -> str:
2025-10-18 03:26:42 +00:00
import os
import re
dir = "./data/dump"
2024-01-13 03:21:38 +00:00
if not os.path.exists(dir):
os.makedirs(dir)
2025-10-18 03:26:42 +00:00
prefix = "ldata_"
suffix = ".json"
file_ids = [
int(re.findall(r"\d+", f)[0])
for f in os.listdir(dir)
if re.match(prefix + r"\d+" + suffix, f)
]
2024-01-13 03:21:38 +00:00
id = max(file_ids) + 1 if file_ids else 0
2025-10-18 03:26:42 +00:00
filename = f"{prefix}{id}{suffix}"
full_path = os.path.join(dir, filename)
with open(full_path, "w") as f:
json.dump(data, f, indent=4)
return full_path