# Deliveroo Restaurant & Menu Scraper (`teodor_banea/deliveroo-restaurant-menu-scraper`) Actor

Scrape Deliveroo restaurants and full menus with prices (UK, France, UAE and more): ratings, fees, minimum order, ETA, promotions, address and coordinates, plus every item with price, discount, calories, dietary tags and modifiers. Paste a listing or menu URL, or a lat,lng.

- **URL**: https://apify.com/teodor_banea/deliveroo-restaurant-menu-scraper.md
- **Developed by:** [Banea Teodor](https://apify.com/teodor_banea) (community)
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## Deliveroo Restaurant & Menu Scraper

### What does Deliveroo Restaurant & Menu Scraper do?

This Actor extracts restaurants and **full menus with prices** from **[Deliveroo](https://deliveroo.co.uk)** in the UK, France, the UAE and Deliveroo's other markets. Paste a neighbourhood listing URL or a restaurant menu URL, or just enter a latitude,longitude, and get clean JSON: restaurant ratings, ETAs, distances, promotions, delivery fees, minimum orders, addresses with coordinates and hygiene ratings, or complete menus with item prices, discounts, calories, dietary tags and modifier options (sizes, sides, extras) with their prices.

It reads Deliveroo's server-rendered pages directly. There is no browser and no login, so runs are fast and cheap.

### Why use this Actor?

- **Full menus with prices**: every orderable item with numeric price, discounted price, currency, calories, dietary tags, popular and availability flags, image, and modifier groups with option prices. Each row carries its restaurant context.
- **Rich restaurant records**: rating with label and count, ETA, distance, delivery fee and minimum order as both text and number, free-delivery flag, promotions and restaurant-wide offers, street address, postcode, latitude/longitude, phone (FR/AE), UK FSA hygiene rating, cuisine tags (FR/AE).
- **Search by location**: give `"51.5155,-0.1419"` and the Actor opens the listing Deliveroo shows for that address.
- **Multi-market**: deliveroo.co.uk, .fr, .ae verified; .ie, .be, .it, .com.kw, .com.qa use the same site. Prices come back in local currency, including 3-decimal currencies such as KWD.
- **Built for pipelines**: every row always has every field (`null` when Deliveroo doesn't show it), numbers are numbers, timestamps are ISO 8601, and restaurants are deduplicated across listings.

Pairs with our [Glovo](https://apify.com/store?search=glovo%20restaurant%20menu), [Wolt](https://apify.com/store?search=wolt%20restaurant%20menu) and [Bolt Food](https://apify.com/store?search=bolt%20food%20restaurant) restaurant & menu scrapers, which use the same field names, for cross-platform delivery price comparison.

### How much does it cost?

You pay per row returned, plus a small start fee per run.

| Run | Rows | Typical cost |
|---|---|---|
| One listing, restaurant list | ~100–120 restaurants | ~$0.25 |
| 10 restaurants, full menus | ~1,000–2,000 items | ~$2–4 |
| 1 menu URL, full menu | 50–250 items | ~$0.10–0.50 |

Use `maxRestaurants` and `maxItemsTotal` to bound any run. The Actor logs a cost estimate before it starts when a run could return more than ~5,000 rows.

### Input

| Field | Type | Description | Example |
|---|---|---|---|
| `startUrls` | array | Deliveroo neighbourhood listings (`/restaurants/<city>/<neighbourhood>`) or menu pages (`/menu/<city>/<neighbourhood>/<restaurant>`), any Deliveroo domain. | `https://deliveroo.co.uk/restaurants/london/fitzrovia?geohash=gcpvhep7zzx1` |
| `locations` | array of strings | Delivery locations as `"latitude,longitude"`. | `["51.5155,-0.1419"]` |
| `countryDomain` | string | Deliveroo country for `locations`. | `deliveroo.fr` |
| `mode` | string | `restaurants` (one row per restaurant, default) or `menus` (one row per menu item). | `"menus"` |
| `includeModifiers` | boolean | Menus mode: attach modifier groups with option prices (default on). | `true` |
| `maxRestaurants` | integer | Cap on restaurants scraped / menus opened (0 = no cap). | `10` |
| `maxItemsTotal` | integer | Hard ceiling on billed rows (0 = none). The crawler stops as soon as it is reached. | `1000` |
| `maxItemsPerRestaurant` | integer | Menus mode: keep only the first N items of each menu, in menu order (0 = complete menus). | `25` |
| `maxConcurrency`, `maxRequestsPerMinute` | integer | Request pacing. | `3`, `60` |

Notes:

- A Deliveroo listing shows its **top ~100–120 restaurants** for that location (Deliveroo does not paginate further). Add more locations or neighbourhood URLs to widen coverage.
- **Restaurant rows from a listing** carry what the listing card shows (rating, ratings count, ETA, distance, promotions, sponsored flag, image). Fees, minimum order, address, coordinates, hygiene rating, cuisine and open state live on the menu page: use `mode: menus` (the full records are stored under the `RESTAURANTS` key, free) or pass menu URLs in `restaurants` mode to get them in the dataset.
- In `restaurants` mode a menu URL without a delivery location is fetched once more from the restaurant's own location, so fees, minimum order and open state are filled. In `menus` mode item rows do not depend on the location, so bare menu URLs are fetched once and their free `RESTAURANTS` record has no fee or minimum (use a listing or a URL with `?geohash=` to get them).
- **National brands** (KFC, Pizza Hut, Subway, Papa John's, Popeyes, …) sit in a compact carousel on listings that carries only the name and ETA, so their listing rows have no rating, ratings count or distance. Their menu pages have everything.
- `maxRestaurants` and `maxItemsTotal` are shared by all listings and locations in a run: with two locations and `maxRestaurants: 20` the first listing to answer fills the cap. Run one location per run (or raise the cap) when you need every area covered.
- UK menu pages show no ETA and no cuisine tags; the ETA comes from the listing card when the menu was found through a listing.

### Output

Menus mode: one row per menu item.

```json
{
    "storeId": "516734",
    "storeName": "Shake Shack - Burgers & Shakes - Oxford Street",
    "storeSlug": "shake-shack-oxford-street",
    "storeUrl": "https://deliveroo.co.uk/menu/london/soho/shake-shack-oxford-street",
    "city": "london",
    "countryCode": "GB",
    "categoryId": "956305007",
    "categoryName": "Limited Time Only!",
    "categoryPosition": 1,
    "productId": "2849629908",
    "name": "The Big Shack",
    "description": "Made with two 100% Aberdeen Angus cheeseburgers made to order, topped with lettuce, pickles, onions, tomatoes, and secret sauce on a three-layer toasted potato bun.",
    "price": 16.15,
    "priceDiscounted": null,
    "currency": "GBP",
    "priceText": "£16.15",
    "calories": 1008,
    "dietaryTags": [],
    "imageUrl": "https://rs-menus-api.roocdn.com/images/3f25b128-5496-49ae-8382-84b11d49f8c1/image.jpeg?width=800&height=600&auto=webp&format=jpg&fit=crop",
    "isPopular": false,
    "isAvailable": true,
    "offerText": null,
    "modifierGroups": [
        {
            "id": "2922055379",
            "name": "NEW LTO Big Shack",
            "minSelection": 0,
            "maxSelection": 6,
            "multiselect": false,
            "options": [
                { "id": "2849629897", "name": "No Cheese", "price": 0, "priceDiscounted": null, "isAvailable": true, "modifierGroupIds": [] }
            ]
        }
    ],
    "itemRating": null,
    "stockCount": null,
    "isSponsored": false,
    "offerTag": null,
    "strikeThroughPrice": null,
    "position": 1,
    "sourceUrl": "https://deliveroo.co.uk/menu/London/soho/shake-shack-oxford-street?day=today&geohash=gcpvhep7zzx1&time=ASAP",
    "scrapedAt": "2026-10-15T20:17:09.842Z",
    "runId": "AbC123dEf456"
}
```

Restaurants mode: one row per restaurant (this one came from a menu URL, so the menu-page fields are filled).

```json
{
    "storeId": "135495",
    "name": "Burger King - Paris Lescot",
    "slug": "burger-king-paris-lescot",
    "url": "https://deliveroo.fr/menu/paris/paris-1-louvre/burger-king-paris-lescot",
    "sourceUrl": "https://deliveroo.fr/en/menu/paris/paris-1-louvre/burger-king-paris-lescot?day=today&time=ASAP&geohash=u09tvqvszqwd",
    "city": "paris",
    "neighborhood": "paris-1-louvre",
    "countryCode": "FR",
    "currency": "EUR",
    "rating": 4,
    "ratingText": "Good",
    "ratingCount": "376",
    "cuisine": ["Burgers", "Vegetarian", "Dessert"],
    "eta": null,
    "distance": null,
    "deliveryFeeText": "€1.79 delivery",
    "deliveryFee": 1.79,
    "minimumOrderText": "€10.00 minimum",
    "minimumOrder": 10,
    "isFreeDelivery": false,
    "promotions": [],
    "offers": ["Spend €10.00 to get free delivery"],
    "isSponsored": null,
    "isOpen": true,
    "closingText": "Closes at 23:45",
    "address": "2 RUE PIERRE LESCOT, Paris, 75001",
    "postcode": "75001",
    "latitude": 48.8611717,
    "longitude": 2.347961,
    "phone": "+33142601653",
    "hygieneRating": null,
    "branchType": "RESTAURANT",
    "description": "Serving up amazing food, Burger King - Paris Lescot sits in the heart of Paris 1 - Louvre. With dishes you're guaranteed to love, order now for delivery within 32 minutes",
    "imageUrl": "https://rs-menus-api.roocdn.com/images/e3f51063-2fb7-4c04-a33d-a7f7a17876e9/image.jpeg?width=800&height=600&auto=webp&format=jpg&fit=crop",
    "scrapedAt": "2026-10-15T20:16:28.065Z",
    "runId": "AbC123dEf456"
}
```

A run produces only one of the two shapes (per the mode), so exports are always uniform. Menus mode additionally stores one full restaurant record per menu under the `RESTAURANTS` key of the run's key-value store (join on `storeId`). Pages that could not be scraped are listed under `ERRORS` with the reason (`blocked`, `notFound`, `invalid`, `requestFailed`, `unexpectedResponse`).

### Tips and tricks

- **Price monitoring**: schedule a menus run on the same menu URLs and diff by `storeId` + `productId`; `runId` and `scrapedAt` keep runs apart after export.
- **Area overview**: restaurants mode over a handful of `locations` maps who delivers where, with ratings, ETAs and promotions, in one cheap run.
- **Fees and minimum orders**: these depend on the delivery address. Listing-driven menus use the listing's location; menu URLs without one use the restaurant's own location.
- **Cost control in menus mode**: `maxItemsTotal` is a hard ceiling (the last menu may be cut short) and stops the crawler; `maxRestaurants` bounds how many menus are opened; `maxItemsPerRestaurant` keeps the first N items of every menu in page order.
- **Closed restaurants** still count toward `maxRestaurants`: a menu that is closed at scrape time has no orderable items (0 item rows, a `RESTAURANTS` record with `closingText` like "Opens at 10:00"). In `restaurants` mode a closed restaurant reached through a bare menu URL has no fee or minimum order either. Run during opening hours for complete data.
- **Grocery pages** (supermarkets, convenience stores) are shelf previews: only the first items of each aisle are on the page, so grocery "menus" are partial.
- **Discounted prices** reflect store-wide promotions active at scrape time (`priceDiscounted` on every item when the restaurant runs "25% off").
- **Modifiers**: switch `includeModifiers` off for flat, CSV-friendly item rows.

### Integrations

The dataset works with every Apify integration: export to **JSON, CSV, Excel**, push to **Google Sheets**, trigger **webhooks** on finish, or query via the **Apify API** from Python/Node, for example a weekly menu-price snapshot feeding a pricing dashboard.

# Changelog

This Actor's version history is a separate document: https://apify.com/teodor_banea/deliveroo-restaurant-menu-scraper/changelog.md

# Actor input Schema

## `startUrls` (type: `array`):

Enter Deliveroo URLs: neighbourhood listing pages (https://deliveroo.co.uk/restaurants/london/fitzrovia?geohash=gcpvhep7zzx1) or restaurant menu pages (https://deliveroo.co.uk/menu/london/leicester-square/wingstop-uk). Any Deliveroo domain works (.co.uk, .fr, .ae, .ie, .be, .it, .com.kw, .com.qa), with or without the /en/ prefix.

## `locations` (type: `array`):

Enter delivery locations as "latitude,longitude" (e.g. "51.5155,-0.1419"). Each one opens the restaurant listing Deliveroo shows for that address in the country picked below.

## `countryDomain` (type: `string`):

Pick the Deliveroo country the locations above belong to. Start URLs carry their own domain and ignore this. UK, France and UAE are verified; Ireland, Belgium, Italy, Kuwait and Qatar use the same site but are untested. Hong Kong and Singapore have closed.

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

"Restaurant list" returns one row per restaurant (rating, ETA, distance, promotions; menu URLs also add fees, minimum order, address, coordinates). "Full menus with prices" opens every restaurant found and returns one row per menu item — a typical restaurant has 50–250 items, so menus multiply the result count.

## `includeModifiers` (type: `boolean`):

In menus mode, attach each item's modifier groups (sizes, sides, extras) with option prices to its row.

## `maxRestaurants` (type: `integer`):

Cap on restaurants scraped (and menus fetched in menus mode). Set to 0 for no cap. The cap is shared by every listing and location in the run; a Deliveroo listing shows its top ~100–120 restaurants.

## `maxItemsTotal` (type: `integer`):

Set a hard ceiling on billed results (restaurants, or menu items in menus mode). Leave at 0 for no limit. The crawler stops as soon as the ceiling is reached, so this bounds both the bill and the run time.

## `maxItemsPerRestaurant` (type: `integer`):

In menus mode, keep only the first N menu items of each restaurant (in menu order). Leave at 0 for complete menus. Useful for a cheap overview of many restaurants.

## `maxConcurrency` (type: `integer`):

Set how many pages are fetched in parallel. Menu pages are large (up to 13 MB), so the default is deliberately low.

## `maxRequestsPerMinute` (type: `integer`):

Throttle the overall request rate.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://deliveroo.co.uk/restaurants/london/fitzrovia?geohash=gcpvhep7zzx1"
    }
  ],
  "locations": [
    "51.5155,-0.1419"
  ],
  "countryDomain": "deliveroo.co.uk",
  "mode": "restaurants",
  "includeModifiers": true,
  "maxRestaurants": 10,
  "maxItemsTotal": 0,
  "maxItemsPerRestaurant": 0,
  "maxConcurrency": 3,
  "maxRequestsPerMinute": 60
}
```

# Actor output Schema

## `results` (type: `string`):

Restaurant records with ratings, fees, ETAs, promotions and location — or menu items with prices, discounts, calories and modifier options, each carrying its restaurant context.

## `restaurants` (type: `string`):

Menus mode also stores one full restaurant record per scraped menu (free, for joins on storeId) under the RESTAURANTS key.

## `errors` (type: `string`):

Pages that could not be scraped, with the reason, under the ERRORS key (absent when nothing failed).

# 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 = {
    "startUrls": [
        {
            "url": "https://deliveroo.co.uk/restaurants/london/fitzrovia?geohash=gcpvhep7zzx1"
        }
    ],
    "maxRestaurants": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("teodor_banea/deliveroo-restaurant-menu-scraper").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 = {
    "startUrls": [{ "url": "https://deliveroo.co.uk/restaurants/london/fitzrovia?geohash=gcpvhep7zzx1" }],
    "maxRestaurants": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("teodor_banea/deliveroo-restaurant-menu-scraper").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 '{
  "startUrls": [
    {
      "url": "https://deliveroo.co.uk/restaurants/london/fitzrovia?geohash=gcpvhep7zzx1"
    }
  ],
  "maxRestaurants": 10
}' |
apify call teodor_banea/deliveroo-restaurant-menu-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,teodor_banea/deliveroo-restaurant-menu-scraper"
        }
    }
}
```

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/IihX0YlXf9A7kS5A5/builds/8lqOCCctOGM6jOBen/openapi.json
