feat: structured ingredients with unit/food resolution
Scraper returns {quantity, unit, food, extra} dicts instead of flat
strings. UI shows 4-column ingredient editor. Mealie client resolves
unit/food IDs via API (creates missing ones automatically).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+21
-26
@@ -26,7 +26,7 @@ def scrape(url: str) -> dict:
|
||||
"title": str,
|
||||
"description": str,
|
||||
"image_url": str | None,
|
||||
"ingredients": [str, ...],
|
||||
"ingredients": [{"quantity": str, "unit": str, "food": str, "extra": str}, ...],
|
||||
"instructions": [str, ...],
|
||||
"original_url": str,
|
||||
}
|
||||
@@ -65,41 +65,33 @@ def _parse_mindmegette(soup: BeautifulSoup, url: str) -> dict:
|
||||
ing_container = soup.find("div", class_="ingredients")
|
||||
if ing_container:
|
||||
for row in ing_container.find_all("div", class_="ingredients-meta"):
|
||||
parts = []
|
||||
# Actual HTML: <strong>qty</strong> <span>unit</span>
|
||||
# <a class="ingredients-link">name</a>
|
||||
# <a class="ingredients-link">name</a> <small>(extra)</small>
|
||||
qty_el = row.find("strong")
|
||||
# Unit: first plain <span> (not one with a specific class like
|
||||
# "ingredients-checkbox" etc.)
|
||||
unit_el = None
|
||||
for sp in row.find_all("span"):
|
||||
if not sp.get("class"):
|
||||
unit_el = sp
|
||||
break
|
||||
name_el = row.find("a", class_="ingredients-link")
|
||||
# Extra info: <small>(darált)</small> or <span class="extra">
|
||||
extra_el = row.find("small") or row.find("span", class_="extra")
|
||||
|
||||
if qty_el:
|
||||
parts.append(_text(qty_el))
|
||||
if unit_el:
|
||||
parts.append(_text(unit_el))
|
||||
if name_el:
|
||||
parts.append(_text(name_el))
|
||||
if extra_el:
|
||||
extra = _text(extra_el)
|
||||
if extra:
|
||||
# Wrap in parens if not already
|
||||
if not extra.startswith("("):
|
||||
extra = f"({extra})"
|
||||
parts.append(extra)
|
||||
qty = _text(qty_el)
|
||||
unit = _text(unit_el)
|
||||
food = _text(name_el)
|
||||
extra = _text(extra_el).strip("() ")
|
||||
|
||||
line = " ".join(p for p in parts if p)
|
||||
if not line:
|
||||
# Fallback: grab whole row text with spaces between elements
|
||||
line = row.get_text(separator=" ", strip=True)
|
||||
if line:
|
||||
ingredients.append(line)
|
||||
if not food:
|
||||
# Fallback: grab whole row text
|
||||
food = row.get_text(separator=" ", strip=True)
|
||||
|
||||
if food:
|
||||
ingredients.append({
|
||||
"quantity": qty,
|
||||
"unit": unit,
|
||||
"food": food,
|
||||
"extra": extra,
|
||||
})
|
||||
|
||||
# --- Instructions ---
|
||||
instructions = []
|
||||
@@ -150,7 +142,10 @@ def _parse_generic(soup: BeautifulSoup, url: str) -> dict:
|
||||
if isinstance(data, list):
|
||||
data = data[0]
|
||||
if data.get("@type") == "Recipe":
|
||||
ingredients = data.get("recipeIngredient", [])
|
||||
for line in data.get("recipeIngredient", []):
|
||||
ingredients.append({
|
||||
"quantity": "", "unit": "", "food": line, "extra": "",
|
||||
})
|
||||
raw_instructions = data.get("recipeInstructions", [])
|
||||
for item in raw_instructions:
|
||||
if isinstance(item, str):
|
||||
|
||||
Reference in New Issue
Block a user