# Morrisons Scraper — Full UK Grocery Catalog & Prices (`studio-amba/morrisons-scraper`) Actor

Scrape the complete Morrisons UK groceries catalog: product names, brands, GBP prices, price per unit, categories, images, ratings, promotions and availability. Optional nutrition, ingredients and allergens. No login or cookies required.

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

## Pricing

from $4.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/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

## Morrisons Scraper

Scrape the complete Morrisons UK groceries catalog — every product, with prices,
categories, images, ratings and availability. No login, no cookies, no account
required.

Morrisons (groceries.morrisons.com) runs on the Ocado Smart Platform. This actor
talks directly to the site's public product API, so it returns clean structured
data fast and reliably — not brittle HTML scraping.

### Why use this actor?

Grocery buyers, price-comparison sites, market researchers and brand teams need a
full, current view of what a major UK supermarket sells and at what price. This
actor pulls the entire Morrisons catalog (around 30,000 products) in a single run,
deduplicated across every department, so you get one clean product feed instead of
a partial sample.

- **Complete catalog, provably.** The actor walks every department and cross-checks
  its unique product count against the Morrisons product sitemap, so you can verify
  coverage rather than trust it.
- **No cookies, no login.** The data comes from the site's open API. Nothing to
  authenticate, no session to keep alive.
- **GBP prices and price-per-unit.** Every product carries its current sell price
  and a normalised unit price (per each, per kg, per litre, etc.).

### How to scrape Morrisons data

1. Add the actor to your Apify account.
2. Leave every field on its default to pull a quick 100-product sample, or set
   **Max products** to `0` to scrape the **complete catalog** (~30,000 products).
3. Optionally set a **Category filter** to scope the run to one department (for
   example `Fruit & Veg` or `Beer, Wines & Spirits`), or set a **Search query**
   (for example `heinz baked beans`) to search the catalog by keyword instead
   of browsing categories. Search query takes priority over the category filter.
4. Optionally tick **Fetch nutrition, ingredients & allergens** for deeper
   per-product detail (slower — one extra request per product).
5. Run the actor. Results stream into the dataset and can be exported as JSON,
   CSV, Excel or fed to an API.

The Morrisons API is open and needs no proxy, but a UK residential proxy is
prefilled and recommended for large runs.

### Input

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `maxProducts` | Integer | No | Max products to return. `0` = the complete catalog (~30,000). Default `100`. |
| `category` | String | No | Limit to one department or sub-category (e.g. `Fruit & Veg`) or a Morrisons categoryId. Empty = all departments. Ignored when `searchQuery` is set. |
| `categories` | Array | No | Several departments in one run (e.g. `["Food Cupboard", "Fresh Foods"]`). Products listed in two departments are returned once. A department name finds the top-level department first. |
| `searchQuery` | String | No | Search the catalog for a keyword (e.g. `heinz baked beans`) instead of browsing categories. Overrides `category` when set. |
| `enrichDetails` | Boolean | No | Fetch nutrition, ingredients, allergens and country of origin per product. Default `false`. |
| `promoDatesOnly` | Boolean | No | Fill `promoValidUntil` with one extra request per product on promotion only (about one in four). Faster than `enrichDetails` on large runs. Default `false`. |
| `proxyConfiguration` | Object | No | Proxy settings. GB residential prefilled. Optional for small runs. |

#### Get the complete catalog

```json
{
    "maxProducts": 0,
    "enrichDetails": false,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "GB"
    }
}
```

#### Search by keyword

```json
{
    "searchQuery": "heinz baked beans",
    "maxProducts": 50
}
```

### Output

Each result contains:

| Field | Type | Example |
|-------|------|---------|
| `name` | String | `"Morrisons British Strawberries 350g"` |
| `brand` | String | `"Morrisons"` |
| `price` | Number | `2.49` |
| `originalPrice` | Number or null | `3.75` (the was-price when Morrisons has a "Now £3, Was £3.75" price cut; `price` is then the price you pay today) |
| `currency` | String | `"GBP"` |
| `pricePerUnit` | String | `"£7.11/kg"` |
| `productId` | String | `"108413093"` |
| `url` | String | Full product page URL |
| `availability` | String | `"in stock"` |
| `inStock` | Boolean | `true` |
| `category` | String | `"Cucumber"` |
| `categories` | Array | `["Fruit & Veg", "Salads", "Cucumber"]` |
| `imageUrl` | String | Primary product image URL |
| `imageUrls` | Array | All product image URLs |
| `packSize` | String or null | `"350g"` (net pack size; for tins and jars sold in liquid the net weight, for example `"400g"` for a 400g tin of chickpeas) |
| `drainedWeight` | String or null | `"240g"` for food sold in liquid, as Morrisons publishes it. Morrisons' `pricePerUnit` is per drained kg for these products. `null` for everything else |
| `alcohol` | Boolean | `false` |
| `isNew` | Boolean | `false` |
| `promotion` | String | `"Price Match"` |
| `moreCardPrice` | Number | `2.75` |
| `moreCardPromotion` | String | `"£2.75 - More Card Price"` |
| `promoValidFrom` | String or null | `null` |
| `promoValidUntil` | String or null | `"2026-10-25"` (enrichDetails or promoDatesOnly) |
| `rating` | Number | `3.6` |
| `reviewCount` | Number | `25` |
| `productDescription` | String | Only when enrichment is enabled |
| `ingredients` | String | Only when enrichment is enabled |
| `nutrition` | String | Only when enrichment is enabled |
| `allergens` | String | Only when enrichment is enabled |
| `countryOfOrigin` | String | Only when enrichment is enabled |
| `scrapedAt` | String | ISO 8601 timestamp |

### Example output

```json
{
    "name": "Morrisons Whole Cucumber",
    "brand": "Morrisons",
    "price": 0.99,
    "currency": "GBP",
    "pricePerUnit": "£0.99/each",
    "productId": "108413093",
    "url": "https://groceries.morrisons.com/products/morrisons-whole-cucumber/108413093",
    "availability": "in stock",
    "inStock": true,
    "category": "Cucumber",
    "categories": ["Fruit & Veg", "Salads", "Cucumber"],
    "imageUrl": "https://groceries.morrisons.com/images-v3/.../300x300.jpg",
    "imageUrls": ["https://groceries.morrisons.com/images-v3/.../500x500.jpg"],
    "packSize": null,
    "drainedWeight": null,
    "alcohol": false,
    "isNew": false,
    "promotion": "Price Match",
    "moreCardPrice": null,
    "moreCardPromotion": null,
    "rating": 3.6,
    "reviewCount": 25,
    "scrapedAt": "2026-07-03T12:00:00.000Z"
}
```

### Cost estimate

A full catalog run (around 30,000 products) needs roughly 200 listing requests and
finishes in a couple of minutes of compute — the API returns 200 fully populated
products per request. Enabling nutrition/ingredients enrichment adds one request
per product, so a full enriched run is much heavier. For price and catalog data,
leave enrichment off.

### Completeness

The actor deduplicates products by their Morrisons product ID across every
department, then compares the unique count to the Morrisons product sitemap and
writes a `COMPLETENESS_REPORT` to the run's key-value store. That gives you a
verifiable coverage figure for the run instead of an unprovable claim.

### Limitations

- **No EAN / GTIN barcodes.** Morrisons does not expose barcodes anywhere in its
  data — only its own numeric `productId`. If you need barcodes, this site can't
  supply them.
- Prices are the online groceries prices. Morrisons runs a single national online
  price, but in-store prices can differ.
- Promotions are captured as a description (e.g. "Price Match", "Buy 3 for £5")
- `moreCardPrice` is the price for one unit with a Morrisons More Card. It is null when the product has no More Card offer, or when the only More Card offer is a multi-buy or meal deal.
- `moreCardPromotion` holds the text of every More Card offer on the product, including multi-buys and meal deals (e.g. "Buy 6 Save 25% - More Card Price"). It is null when there is none.
  rather than an explicit was-price.
- Data is scraped from the public website and may change without notice.
- Respect the website's terms of service and use responsibly.

### Compare prices across UK grocers

The [UK Grocery Price Matrix](https://apify.com/studio-amba/uk-grocery-price-matrix) runs this scraper alongside Tesco, Sainsbury's, Asda, Waitrose and Aldi UK, matches products by EAN barcode, and returns one row per product per retailer with the cheapest one flagged.

# Actor input Schema

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

Maximum number of products to return. Set to 0 for the COMPLETE catalog (~30,000 products). Leave the default for a quick sample.

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

Limit the run to one department or sub-category. Use a department name exactly as shown on the site (e.g. 'Fruit & Veg', 'Food Cupboard', 'Beer, Wines & Spirits') or a Morrisons categoryId. Leave empty to scrape all departments. Ignored when Search query is set.

## `categories` (type: `array`):

Several departments in one run, matched the same way as Category (department name as shown on the site, e.g. 'Food Cupboard', 'Fresh Foods', 'Meat & Fish'). Products listed in more than one department are returned once. Leave empty to use Category or the full catalog.

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

Search the Morrisons catalog for a keyword (e.g. 'heinz baked beans') instead of browsing by category. When set, this overrides the Category filter. Leave empty for the normal category-browse behaviour.

## `enrichDetails` (type: `boolean`):

For each product, make an extra request to fetch nutrition, ingredients, allergens, country of origin and a description. Much slower on a full run (one extra request per product). Leave off for price and catalog data only.

## `promoDatesOnly` (type: `boolean`):

Fill promoValidUntil without the full detail enrichment: one extra request per product that is on promotion (about one in four), nothing for the rest. Much faster than Enrich details on a large run. Ignored when Enrich details is on, which already fills promoValidUntil. Off by default: without it (or Enrich details) promoValidUntil is null.

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

Proxy settings. The Morrisons JSON API is open and needs no proxy. A GB residential proxy is prefilled so prices resolve as UK retail; the scraper rotates the proxy session automatically if an exit is blocked, so a few bad exits never drop a department. Leave empty to run direct (fastest).

## Actor input object example

```json
{
  "maxProducts": 20,
  "enrichDetails": false,
  "promoDatesOnly": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "GB"
  }
}
```

# Actor output Schema

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

No description

# 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 = {
    "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/morrisons-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 = {
    "maxProducts": 20,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "GB",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("studio-amba/morrisons-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 '{
  "maxProducts": 20,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "GB"
  }
}' |
apify call studio-amba/morrisons-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,studio-amba/morrisons-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/TQlSbTsaOtEgFaKe9/builds/Hc5fjrfegt7C913Yw/openapi.json
