# Waitrose Scraper - UK Grocery Products & Prices (`studio-amba/waitrose-scraper`) Actor

Scrape the full Waitrose UK grocery catalogue: names, brands, prices, price-per-unit, promotions, ratings, and images. Full category-tree walk with exact per-category counts. No login or cookies required.

- **URL**: https://apify.com/studio-amba/waitrose-scraper.md
- **Developed by:** [Studio Amba](https://apify.com/studio-amba) (community)
- **Categories:** E-commerce
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.00 / 1,000 result scrapeds

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Waitrose Scraper

Scrape the full Waitrose UK grocery catalogue — product names, brands, prices,
price-per-unit, promotions, ratings and images — with verified per-category
counts. No login, no cookies.

### Why use this actor?

Waitrose is one of the UK's premium supermarkets, and its online grocery range
(roughly 25,000 SKUs) is a clean reference set for price monitoring, competitor
analysis, product cataloguing, and market research. Waitrose blocks non-UK
traffic and runs Akamai anti-bot, so the data is hard to reach with a normal
scraper. This actor solves both and walks the entire category tree, so you get
the whole catalogue, not just the first page of a search.

Buyers who use this: pricing analysts, grocery and FMCG brands tracking their
own and competitor shelf prices, market researchers, and data teams building
UK grocery datasets.

### No cookies, no login

This actor reads only public catalogue pages. It never logs in, never needs a
Waitrose account, and never touches personal data. Every request is routed
through a Bright Data Web Unlocker UK exit, which handles the geo-block and the
Akamai Bot Manager challenge for you.

### How it works

Waitrose category pages are server-side rendered. Each page embeds a JSON blob
containing the full product objects for that category, the exact product count
(`totalMatches`), and the list of child categories. This actor:

1. Discovers the top-level grocery departments from the site navigation.
2. Fetches each category page through Bright Data (UK exit) and parses the
   embedded product data.
3. Recurses down the category tree to the leaf shelves.
4. De-duplicates every product by its Waitrose line number.

Because each category reports its exact `totalMatches`, coverage is verifiable:
the run stores a `CATEGORY_TOTALS` record in the key-value store so you can
check the count captured against the count Waitrose reports.

### How to scrape Waitrose data

1. Get a Bright Data account and a Web Unlocker zone (`web_unlocker1`). Copy your
   API key.
2. Paste the key into the **Bright Data API Key** input field (or set the
   `BRIGHT_DATA_API_KEY` environment variable).
3. Leave **Category** empty to scrape the whole catalogue, or set it to a single
   department slug (for example `fresh_and_chilled`, `food_cupboard`, `frozen`)
   to scope the run. Or set **Search Query** to search by keyword instead (see
   below) — the two are mutually exclusive; a search query always wins.
4. Set **Max Products** — a small number (20) for a quick test, a large number
   (30000) for the full catalogue.
5. Run the actor. Results stream into the dataset as they are found.

The category slug is the part of the browse URL after `.../browse/groceries/`.
For example `https://www.waitrose.com/ecom/shop/browse/groceries/bakery` has the
slug `bakery`, and a nested shelf uses a path such as
`fresh_and_chilled/cheese`.

### Keyword search

Set **Search Query** (for example `heinz baked beans` or `oat milk`) to search
the whole catalogue by keyword instead of walking a category. This replaces
the category walk entirely and is bounded by **Max Products**.

Waitrose's own search page and search API aren't reachable for scraping — its
robots.txt disallows the search results URL and the token endpoint needed to
call the search API directly, and Bright Data enforces robots.txt. Instead
this actor matches your query against Waitrose's public product sitemap
(~18,700 products), then fetches each matching product's own page for full
data — name, brand, price, rating, images, and barcode. All words in the
query must appear in the product's name; unlike a category run, coverage
against Waitrose's own totals is not tracked for search results, and the
`fetchBarcodes` option is not needed for search — barcodes are included
automatically since each match already requires its own product page fetch.

### Input

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `brightDataApiKey` | String | Yes\* | Bright Data Web Unlocker API key. \*Or set `BRIGHT_DATA_API_KEY`. |
| `category` | String | No | Grocery department/shelf slug to scope the run. Empty = full catalogue. |
| `searchQuery` | String | No | Keyword search across the catalogue, e.g. `heinz baked beans`. When set, replaces the category walk entirely. |
| `maxProducts` | Integer | No | Maximum unique products to return (default 20). |
| `sortBy` | String | No | Listing order hint (cosmetic). |
| `fetchBarcodes` | Boolean | No | Fetch the EAN/GTIN barcode per product (default `false`). Adds one extra Bright Data request per product — see below. |
| `proxyConfiguration` | Object | No | Not used — Bright Data handles the UK exit. |

### Output

Each result contains:

| Field | Type | Example |
|-------|------|---------|
| `name` | String | `"Waitrose Italian Summer Salad Kit"` |
| `brand` | String | `"Waitrose Ltd"` |
| `price` | Number | `2.50` |
| `currency` | String | `"GBP"` |
| `originalPrice` | Number | `3.00` |
| `pricePerUnit` | String | `"£20.83/kg"` |
| `discount` | String | `"Any 3 for 2 Cheapest Item Free Mix & Match"` |
| `productId` | String | `"939428"` |
| `ean` | String | `"5063210100002"` (with `fetchBarcodes`) |
| `barcodes` | Array | `["5063210100002"]` (with `fetchBarcodes`) |
| `rating` | Number | `4` |
| `reviewCount` | Number | `24` |
| `imageUrl` | String | Primary product image URL |
| `imageUrls` | Array | All image URLs |
| `description` | String | `"Pack size: 120g"` |
| `category` | String | `"301135"` |
| `categories` | Array | `["Groceries", "Fresh & Chilled", ...]` |
| `url` | String | Full product page URL |
| `scrapedAt` | String | ISO 8601 timestamp |

#### Stable product identifier

`productId` is the Waitrose **line number**. It is the identifier Waitrose
itself uses for the product across its site and stays the same across
re-scrapes, price changes and category moves — use it as the join key when
comparing runs over time.

#### EAN barcodes (`fetchBarcodes`)

Waitrose only exposes barcodes on product detail pages, not on category
listings. With `fetchBarcodes` enabled the actor fetches each product's detail
page and adds two fields:

- `ean` — the primary retail EAN/GTIN, checksum-validated where possible.
- `barcodes` — every code Waitrose lists (some products carry an internal or
  variable-weight code alongside the retail EAN).

This costs **one extra Bright Data request per product** on top of the normal
catalogue walk. On a full ~16,000-product catalogue that roughly doubles the
Bright Data spend and run time, so it is off by default. It is most useful for
cross-store price matching, where the EAN is the join key between retailers.

### Example output

```json
{
    "name": "Waitrose Italian Summer Salad Kit",
    "brand": "Waitrose Ltd",
    "price": 2.50,
    "currency": "GBP",
    "pricePerUnit": "£20.83/kg",
    "productId": "939428",
    "rating": 4,
    "reviewCount": 24,
    "imageUrl": "https://ecom-su-static-prod.wtrecom.com/images/products/11/LN_939428_BP_11.jpg",
    "description": "Pack size: 120g",
    "category": "301135",
    "categories": ["Groceries", "Fresh & Chilled", "Fresh Salad & Herbs", "Salad Bags"],
    "url": "https://www.waitrose.com/ecom/products/waitrose-italian-summer-salad-kit/939428-1-2",
    "scrapedAt": "2026-07-03T14:30:00.000Z"
}
```

### Cost estimate

A full-catalogue run visits every grocery category page once (roughly
1,500–2,500 pages) through Bright Data Web Unlocker to cover ~25,000 products.
Scoping to a single department is far cheaper — a department such as
`fresh_and_chilled` (~3,600 products) walks a few hundred category pages. Test
runs with a small `maxProducts` cost a handful of requests.

### Limitations

- Requires a Bright Data Web Unlocker key with a GB exit. Waitrose drops non-UK
  connections, so this is mandatory.
- Stock/availability is delivery-slot and postcode dependent on Waitrose, so it
  is not reported per product. The catalogue, pricing, promotions and ratings
  are.
- Each category page returns its top products; the catalogue is completed by
  walking the tree to leaf level. A small number of very large leaf shelves may
  not expose their full tail — check the `CATEGORY_TOTALS` record for coverage.
- Data is scraped from the public website and may change without notice. Respect
  the website's terms of service and use responsibly.

### Need this data on a schedule, or a custom version?

We run this scraper as a managed service for businesses: scheduled runs,
deduplication, delta detection, and delivery to your inbox, Google Sheets,
or API — maintenance included. We can also build a custom version with your
exact fields and filters, or combine multiple sources into one feed.

See [studioamba.dev/services](https://studioamba.dev/services/) or email
<hello@studioamba.dev> for a free data sample.
We maintain 300+ European web scrapers and answer within one business day.

# Actor input Schema

## `category` (type: `string`):

Scope the run to a single Waitrose grocery department or shelf. Use the slug from the browse URL, e.g. 'fresh\_and\_chilled', 'food\_cupboard/tins\_cans\_and\_packets', or 'frozen'. Leave empty to walk the entire grocery catalogue.

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

Keyword search across the whole Waitrose catalogue, e.g. 'heinz baked beans' or 'oat milk'. When set, this replaces the category walk entirely: only products whose name matches every word of the query are fetched, bounded by Max Products, and Category / full-catalogue mode is skipped. Leave empty to use Category mode instead.

## `maxProducts` (type: `integer`):

Maximum number of unique products to return. Use a small value (e.g. 20) for a quick test, or a large value (e.g. 30000) for the full catalogue.

## `sortBy` (type: `string`):

Ordering hint for the listing pages (Waitrose default is RECOMMENDED). Cosmetic — the catalogue walk returns the same set regardless.

## `fetchBarcodes` (type: `boolean`):

Fetch the EAN/GTIN barcode for every product (adds 'ean' and 'barcodes' output fields). Barcodes only exist on Waitrose product detail pages, so this costs ONE EXTRA Bright Data request per product on top of the normal catalogue walk — on a full ~16k-product catalogue that roughly doubles run time and Bright Data spend. Leave off for the fast price-only crawl.

## `brightDataApiKey` (type: `string`):

Your Bright Data API key for the Web Unlocker zone. Required to bypass Waitrose's UK geo-block and Akamai Bot Manager. Get one at https://brightdata.com. Can also be supplied via the BRIGHT\_DATA\_API\_KEY environment variable.

## `proxyConfiguration` (type: `object`):

Not used — this actor routes all traffic through Bright Data Web Unlocker (GB), not Apify proxies.

## Actor input object example

```json
{
  "category": "fresh_and_chilled",
  "maxProducts": 20,
  "sortBy": "RECOMMENDED",
  "fetchBarcodes": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "GB"
  }
}
```

# 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 = {
    "category": "fresh_and_chilled",
    "maxProducts": 20,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "GB"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("studio-amba/waitrose-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 = {
    "category": "fresh_and_chilled",
    "maxProducts": 20,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "GB",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("studio-amba/waitrose-scraper").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{
  "category": "fresh_and_chilled",
  "maxProducts": 20,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "GB"
  }
}' |
apify call studio-amba/waitrose-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=studio-amba/waitrose-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/vaAJyF3MKm2J2bA5O/builds/4bLe0pheImGwJ7HSp/openapi.json
