# Multi-Site Recipe & Nutrition Data Extractor (`vexaloom-labs/multi-site-recipe-and-nutrition-data-extractor`) Actor

Get ingredients, step-by-step instructions, nutrition, ratings, prep/cook time, yield, and images from recipe pages on Epicurious, BBC Good Food, Tasty, NYT Cooking, Serious Eats, Food Network, and thousands of food blogs.

- **URL**: https://apify.com/vexaloom-labs/multi-site-recipe-and-nutrition-data-extractor.md
- **Developed by:** [vexaloom-labs](https://apify.com/vexaloom-labs) (community)
- **Categories:** Automation, Developer tools, Other
- **Features:** Limited permissions
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.49 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Multi-Site Recipe & Nutrition Data Extractor

Recipe sites and food blogs publish a machine-readable `schema.org/Recipe` record on every recipe page — the same record search engines use for star ratings and cooking times. This actor reads that record (and the common WordPress recipe formats when a site has no JSON-LD), so one input works across major recipe sites and thousands of food blogs.

### What it does

- **Search by keyword** across BBC Good Food, Epicurious, Tasty, Food Network, NYT Cooking, Bon Appétit, Delish, Budget Bytes, Pinch of Yum, Minimalist Baker, Smitten Kitchen, Love and Lemons, and Skinnytaste.
- **Scrape direct recipe URLs** from any supported site or food blog.
- **Crawl category, tag, or search-result pages** and follow their pagination.
- **Walk sitemaps**, including one level of nested sitemap indexes.
- **Parse ingredients, instructions, and nutrition** from JSON-LD, WP Recipe Maker, Tasty Recipes, Jetpack Recipe, or schema.org microdata.
- **Filter** by rating, rating count, total time, and a required keyword.

### Output per recipe (`recordType: "recipe"`)

- `url`, `id` — canonical recipe URL (`id` mirrors `url`)
- `platform` — site slug, e.g. `epicurious`, `tasty`, `bbcgoodfood`, `smittenkitchen`
- `name`, `description` — title and short blurb, HTML stripped
- `image` — hero image URL
- `author` — `{name, url}`
- `ratingValue`, `ratingCount`, `reviewCount` — average rating (0–5), number of ratings, number of written reviews
- `prepTimeMinutes`, `cookTimeMinutes`, `totalTimeMinutes` — ISO-8601 durations and human durations such as `1 hr 15 min` are both supported; `totalTimeMinutes` falls back to prep + cook
- `recipeYield` — servings or pieces, e.g. `"8 slices"`
- `recipeCategory`, `recipeCuisine` — arrays
- `keywords` — keyword tags
- `recipeIngredient` — ingredient lines
- `recipeInstructions` — step strings, HTML stripped, including `HowToSection` groups
- `nutrition` — calories, protein, carbs, fat, saturated fat, cholesterol, sodium, fiber, sugar, serving size when published
- `video` — `{url, name, thumbnailUrl}` when the recipe has one
- `datePublished`, `dateModified` — dates as published by the site
- `scrapedAt` — ISO 8601 UTC timestamp

Empty fields are omitted at every depth — never `null`, `""`, `[]`, or `{}`.

### Output per failed direct URL (`recordType: "url_failed"`)

Direct URLs that cannot be read produce a typed row instead of disappearing:

- `reason` — `unsupported_site` (AllRecipes or another explicitly blocked host), `not_found`, `anti_bot_block`, `no_recipe_jsonld`, `filtered_out`, or `fetch_error`
- `url`, `platform`, `scrapedAt`

If every direct URL fails, or a search/listing yields candidates but none are recipes, the dataset also gets one `recordType: "scrape_summary"` row with the dominant reason and a plain-language message. A search that finds no candidate URLs at all reports it in the run status message.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `mode` | select | `search` | `search`, `byUrls`, `byListing`, `byTag` (legacy alias for `byListing`), or `bySitemap` |
| `searchQuery` | string | `banana bread` | Keyword for `mode=search` |
| `site` | select | `all` | One supported site, or `all` |
| `recipeUrls` | array | `["https://www.epicurious.com/recipes/food/views/banana-bread"]` | Direct recipe URLs for `mode=byUrls` |
| `listingUrls` | array | – | Category/tag/search pages for `mode=byListing` |
| `sitemapUrls` | array | – | Sitemap URLs for `mode=bySitemap` |
| `maxItems` | integer | `50` | Hard cap on recipe records (1–1000) |
| `maxPages` | integer | `2` | Pages per site or listing URL (1–10) |
| `minRating` | integer | – | Minimum rating on a 0–500 scale (400 = 4.0/5) |
| `minRatingCount` | integer | – | Minimum number of ratings |
| `maxTotalTimeMinutes` | integer | – | Maximum prep + cook time |
| `keywordIncludes` | string | – | Required keyword in name or description |
| `proxy` | object | `{"useApifyProxy": false}` | Optional Apify Proxy configuration |

#### Examples

Search Epicurious for banana bread:

```json
{
  "mode": "search",
  "searchQuery": "banana bread",
  "site": "epicurious",
  "maxItems": 20
}
```

Search every supported site:

```json
{
  "mode": "search",
  "searchQuery": "vegan dinner",
  "site": "all",
  "maxItems": 50,
  "maxPages": 2
}
```

Scrape direct URLs:

```json
{
  "mode": "byUrls",
  "recipeUrls": [
    "https://www.epicurious.com/recipes/food/views/banana-bread",
    "https://tasty.co/recipe/banana-bread",
    "https://www.bbcgoodfood.com/recipes/vegan-banana-bread"
  ]
}
```

Crawl a category page and its next pages:

```json
{
  "mode": "byListing",
  "listingUrls": ["https://www.epicurious.com/recipes/category/dessert"],
  "maxPages": 3,
  "maxItems": 40
}
```

Walk a sitemap:

```json
{
  "mode": "bySitemap",
  "sitemapUrls": ["https://www.bbcgoodfood.com/sitemap.xml"],
  "maxItems": 100
}
```

### Supported search sites

BBC Good Food, Epicurious, Tasty, Food Network, NYT Cooking, Bon Appétit, Delish, Budget Bytes, Pinch of Yum, Minimalist Baker, Smitten Kitchen, Love and Lemons, and Skinnytaste. Direct URL and sitemap modes work on any site that publishes recipe JSON-LD or one of the supported WordPress recipe formats.

### Compatibility with older inputs

Old saved tasks keep working because legacy keys are mapped in code:

- `mode=byTag` and `tagUrls` → `byListing` and `listingUrls`
- `query`, `searchTerm`, `keyword`, `q` → `searchQuery`
- `urls`, `recipeUrl`, `url` → `recipeUrls`
- `sitemapUrl`, `sitemaps` → `sitemapUrls`
- `maxResults` → `maxItems`
- `maxPagesPerSite`, `pages` → `maxPages`
- `siteScope`, `searchSites`, `sites` → `site`
- `minRatingScore` (0–5) → `minRating` (0–500)
- `mustContain`, `keywordFilter` → `keywordIncludes`

Only fields the actor actually reads are visible in the input schema. Unknown legacy keys are accepted by Apify and mapped inside the actor.

### Limitations

- **AllRecipes** is protected by Akamai Bot Manager and is rejected as `unsupported_site`.
- **Serious Eats, Simply Recipes, Food & Wine, EatingWell, and Martha Stewart** currently return HTTP 403 to cookieless datacenter requests. They work in `byUrls` only if your proxy configuration can reach them; otherwise they appear as `anti_bot_block`.
- Search pagination depends on the site. Tasty, NYT Cooking and Delish expose the first page only; BBC Good Food, Epicurious, Food Network, Bon Appétit and the WordPress sites support deeper pagination.
- Search results are returned in the site's own relevance order. A result can occasionally be a round-up or article that has no recipe record; those candidates are skipped.
- **Budget Bytes and Smitten Kitchen** return HTTP 403 from Apify cloud IPs for both search and direct recipe pages; both work from a local/residential connection. On cloud they surface as `anti_bot_block`; the other WordPress sites in the search list are unaffected.
- Recipe data is only as complete as the source page. Missing fields are omitted instead of guessed.

### Notes

No cookies, logins, or API keys are required. The actor fetches pages with a Chrome TLS fingerprint. Enable the optional `proxy` field only if you want to supply Apify Proxy groups; no proxy is needed for the supported sites' default paths.

# Actor input Schema

## `mode` (type: `string`):

How to find recipes. Search by keyword is the simplest; URL and sitemap modes are best when you already know the pages.

## `searchQuery` (type: `string`):

What to search for, e.g. `banana bread`, `vegan dinner`, or `chicken curry`.

## `site` (type: `string`):

Pick one site, or `all` to search every supported site. Search coverage varies by site; unsupported sites still work in byUrls/bySitemap modes.

## `recipeUrls` (type: `array`):

Direct recipe-page URLs from any supported site or food blog. AllRecipes URLs are rejected as unsupported.

## `listingUrls` (type: `array`):

Category, tag, or search-result pages. The actor extracts recipe links and follows page 2, 3, … where the site exposes a page parameter.

## `sitemapUrls` (type: `array`):

Sitemap.xml URLs. The actor walks sitemap indexes and keeps recipe-looking URLs.

## `maxItems` (type: `integer`):

Hard cap on emitted recipe records.

## `maxPages` (type: `integer`):

For search and listing modes, how many result pages to read per site or listing URL.

## `minRating` (type: `integer`):

Drop recipes with a rating below this value on a 0–500 scale (400 = 4.0/5).

## `minRatingCount` (type: `integer`):

Drop recipes with fewer ratings than this.

## `maxTotalTimeMinutes` (type: `integer`):

Drop recipes whose prep + cook time exceeds this many minutes.

## `keywordIncludes` (type: `string`):

Drop recipes whose name and description do not contain this keyword.

## `proxy` (type: `object`):

No proxy is needed for the supported sites. Enable Apify Proxy only if you want to route requests through your own Apify proxy groups.

## Actor input object example

```json
{
  "mode": "byUrls",
  "searchQuery": "banana bread",
  "site": "epicurious",
  "recipeUrls": [
    "https://www.epicurious.com/recipes/food/views/banana-bread"
  ],
  "listingUrls": [],
  "sitemapUrls": [],
  "maxItems": 1,
  "maxPages": 1,
  "proxy": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `records` (type: `string`):

Dataset of all scraped recipes.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "mode": "byUrls",
    "searchQuery": "banana bread",
    "site": "epicurious",
    "recipeUrls": [
        "https://www.epicurious.com/recipes/food/views/banana-bread"
    ],
    "maxItems": 1,
    "maxPages": 1
};

// Run the Actor and wait for it to finish
const run = await client.actor("vexaloom-labs/multi-site-recipe-and-nutrition-data-extractor").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "mode": "byUrls",
    "searchQuery": "banana bread",
    "site": "epicurious",
    "recipeUrls": ["https://www.epicurious.com/recipes/food/views/banana-bread"],
    "maxItems": 1,
    "maxPages": 1,
}

# Run the Actor and wait for it to finish
run = client.actor("vexaloom-labs/multi-site-recipe-and-nutrition-data-extractor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "mode": "byUrls",
  "searchQuery": "banana bread",
  "site": "epicurious",
  "recipeUrls": [
    "https://www.epicurious.com/recipes/food/views/banana-bread"
  ],
  "maxItems": 1,
  "maxPages": 1
}' |
apify call vexaloom-labs/multi-site-recipe-and-nutrition-data-extractor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,vexaloom-labs/multi-site-recipe-and-nutrition-data-extractor"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/ph3cwfPJgeysFit9N/builds/EVA9PME5KWbFjUYD4/openapi.json
