# Tesco Scraper: Prices, Clubcard Offers & Products (UK) (`zaiq/tesco-scraper`) Actor

Scrape Tesco UK grocery products by search, category or product URL: price, unit price, Clubcard Price, promotions and multi-buys, stock, category, barcode (GTIN), ratings, and optional ingredients, allergens and nutrition. Fast JSON from Tesco's own data. Pay per product.

- **URL**: https://apify.com/zaiq/tesco-scraper.md
- **Developed by:** [Zaiq](https://apify.com/zaiq) (community)
- **Categories:** E-commerce, Automation
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 products

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

## Tesco Scraper: Prices, Clubcard Offers & Products (UK)

Get Tesco UK grocery products as clean data: price, unit price, Clubcard Price with its dates, multi-buys and other
promotions, stock, category, barcode (GTIN), rating and, if you ask for it, ingredients, allergens and nutrition.
Search like a shopper, list whole categories and aisles, or check a fixed list of products every day.

The Actor reads the same data service the tesco.com website uses, so results arrive as structured data in seconds,
without a browser and without logging in.

### What it does

- **Search terms**: up to 100 products per request, as many pages as you need.
- **Category, aisle and shelf pages**: paste the page address, for example
  `https://www.tesco.com/groceries/en-GB/shop/fresh-food/milk-butter-and-eggs/milk/all`.
- **Product URLs or product numbers**: TPNC (9 digits, at the end of a product URL) or TPNB (8 digits). Up to 20
  products per request. A product that does not exist comes back as a free row with an error.
- **Prices**: the shelf price everybody pays, the unit price (per kg, litre, each, 100 g...), and the **Clubcard
  Price** and Clubcard unit price with start and end dates.
- **Promotions**, classified: Clubcard Price, multi-buy ("Any 3 for £10 Clubcard Price", "Any 3 for 2"), meal deal,
  price cut. Each with its text, dates, quantity and multi-buy price.
- **Only products on offer**: one switch, applied by Tesco's own "Special Offers" filter.
- **Stock**: `inStock` and Tesco's own status (for example `AvailableForSale`).
- **Pack size** as written and as a number in grams, millilitres or items (`6 x 1L` becomes 6000 ml, pack of 6).
- **Category path** (super department, department, aisle, shelf), rating and number of reviews, dietary icons
  ("Suitable for Vegans"), labels (Aldi Price Match, Low Everyday Price, New, Sponsored).
- **Details on request**: ingredients, allergy advice, the allergens marked in bold in the ingredients (as a list),
  nutrition table, storage, origin and manufacturer.
- **Sort**: relevance, price low to high, price high to low (Tesco's own sorting).

The output has the same columns as our Sainsbury's, Asda and Morrisons scrapers, so the four can be compared row by
row.

### Who uses it

- **Brands and suppliers** checking their shelf price, promotions and Clubcard Prices at Tesco.
- **Retail and pricing analysts** tracking price changes, unit prices and promotion depth over time.
- **Deal and comparison sites** listing current Clubcard Prices and multi-buys.
- **Researchers** measuring grocery inflation with real basket prices.
- **Nutrition and allergen apps** that need ingredients and nutrition per product.

### Example

Test runs on 4 October 2026 through UK home connections:

| Run | Result |
|---|---|
| Six searches (milk, bread, coffee, pasta, eggs, bananas), 50 products each | 274 products in 4.1 seconds (bananas has 24); 94 on offer, 54 with a Clubcard Price |
| One search ("chocolate") to 650 products | 650 products from 10 requests in 21 seconds |
| Three category pages (white bread, semi-skimmed milk, cereals) | 336 products in 8.8 seconds |
| Offers only: two searches and the milk aisle | 138 products, all on offer; 98 with a Clubcard Price |
| Ten pasta products with details | 10 of 10 with ingredients, allergens and a nutrition table |
| Product numbers, one of them not on Tesco | the real products returned; the missing one as a free row |

No request was blocked in these runs.

### Input

```json
{
  "searchTerms": ["milk", "coffee"],
  "categoryUrls": [{ "url": "https://www.tesco.com/groceries/en-GB/shop/fresh-food/milk-butter-and-eggs/milk/all" }],
  "productIds": ["254656543"],
  "maxItems": 500,
  "maxItemsPerSource": 200,
  "offersOnly": false,
  "sort": "relevance",
  "includeDetails": false
}
```

| Field | What it does |
|---|---|
| `searchTerms` | Words to search for, as you would type them on tesco.com. |
| `categoryUrls` | Tesco category, aisle or shelf pages. |
| `productUrls` | Tesco product pages. |
| `productIds` | Tesco product numbers: TPNC (9 digits) or TPNB (8 digits). |
| `maxItems` | Stop after this many products in total (default 1000). |
| `maxItemsPerSource` | Stop each search or category after this many products (default 200). |
| `offersOnly` | Only products with a promotion or Clubcard Price. |
| `sort` | `relevance`, `price-low-high` or `price-high-low`. |
| `includeDetails` | Add ingredients, allergens, nutrition, storage and origin (charged separately). |
| `deduplicate` | Write a product found by two searches once (default on). Switch off to keep every search's full list, for example to track search positions. |
| `proxyConfiguration`, `maxConcurrency` | Advanced. UK residential connections are the default and are included in the price. |

### Output

One row per product. A row from a search for "tesco ground coffee" (offers only), with details:

```json
{
  "retailer": "Tesco",
  "productId": "253080263",
  "sku": "54332665",
  "gtin": null,
  "name": "Tesco Italian Inspired Blend Ground Coffee 227G",
  "brand": "TESCO",
  "url": "https://www.tesco.com/groceries/en-GB/products/253080263",
  "imageUrl": "https://digitalcontent.api.tesco.com/v2/media/ghs/1a6473be-f718-4997-b950-c53020eb78ff/96974bae-11de-4c6e-b5b4-fa99aa32dc32_896478251.jpeg",
  "price": 3.25,
  "currency": "GBP",
  "wasPrice": null,
  "unitPrice": 14.32,
  "unitPriceUnit": "kg",
  "loyaltyPrice": null,
  "loyaltyUnitPrice": null,
  "loyaltyScheme": "Clubcard",
  "loyaltyValidFrom": null,
  "loyaltyValidTo": null,
  "onOffer": true,
  "promotions": [
    {
      "id": "103559507",
      "text": "Any 3 for 2 Clubcard Price - Cheapest Product Free - Selected Coffee Pods Ground Beans",
      "type": "multibuy",
      "loyalty": true,
      "offerPrice": null,
      "offerUnitPrice": null,
      "quantity": 3,
      "multibuyPrice": null,
      "validFrom": "2026-09-28T23:00:00Z",
      "validTo": "2026-10-27T00:00:00Z"
    }
  ],
  "promotionText": "Any 3 for 2 Clubcard Price - Cheapest Product Free - Selected Coffee Pods Ground Beans",
  "size": "227G",
  "sizeValue": 227,
  "sizeUnit": "g",
  "packCount": null,
  "inStock": true,
  "availability": "AvailableForSale",
  "category": "Ground Coffee",
  "categoryPath": ["Drinks", "Coffee", "Ground Coffee"],
  "rating": 3.6,
  "reviewCount": 65,
  "dietary": [],
  "labels": [],
  "region": null,
  "storeId": null,
  "details": {
    "description": "Roast and ground coffee.",
    "storage": "Store in a cool dry place and once opened in an airtight container.",
    "origin": "more than one country, the U.K.",
    "manufacturer": "Tesco Stores Ltd., Welwyn Garden City AL7 1GA",
    "netContents": "227g e"
  },
  "sourceType": "search",
  "sourceInput": "tesco ground coffee",
  "position": 1,
  "scrapedAt": "2026-10-04T15:51:53Z",
  "error": null,
  "charged": true
}
```

A product with a single-item Clubcard Price has it in `loyaltyPrice` and `loyaltyUnitPrice`. In the same test, a
500 ml bottle had `price` 5.00 and `loyaltyPrice` 4.00 (`unitPrice` 10.00 and `loyaltyUnitPrice` 8.00 per litre),
with `loyaltyValidFrom` "2026-09-28T23:00:00Z" and `loyaltyValidTo` "2026-10-27T00:00:00Z".

With details on, food products also have `ingredients`, `allergens`, `allergenList` and a nutrition table, for
example (Tesco fusilli 3 kg):

```json
"ingredients": "INGREDIENTS: Durum Wheat Semolina.",
"allergenList": ["Wheat"],
"nutritionHeaders": ["Per 100g", "Per 170g**", "%RI*", "RI*"],
"nutrition": [
  { "nutrient": "Energy", "values": ["668kJ / 158kcal", "1136kJ / 268kcal", "14%", "8400kJ / 2000kcal"] },
  { "nutrient": "Fat", "values": ["0.4g", "0.7g", "1%", "70g"] }
]
```

Notes on the fields:

- `price` is what any shopper pays. `loyaltyPrice` is the Clubcard Price of one item. Multi-buys are in `promotions`
  (with `quantity` and, where stated, `multibuyPrice`).
- `gtin` is the barcode Tesco lists. Tesco's own-label products often carry an internal number instead; those are
  left empty rather than reported as barcodes.
- `position` is the product's place in Tesco's results for that search or category.
- The run's key-value store also holds `RUN-SUMMARY`: products written, what each input returned and why the run
  stopped.

### Use it from code, automations and AI agents

- **API**: run it and get the results in one call: `POST https://api.apify.com/v2/acts/zaiq~tesco-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN` with the input as JSON. The API tab on this page has ready-made Python, JavaScript and cURL examples.
- **Python** (`pip install apify-client`):

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_TOKEN")
run = client.actor("zaiq/tesco-scraper").call(run_input={'searchTerms': ['milk'], 'maxItems': 20})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)
```

- **No code**: connect it to Google Sheets, Zapier, Make or n8n from the Integrations tab, schedule runs in Apify Console, and get a webhook when a run finishes.
- **AI agents (MCP)**: add it as a tool to Claude, Cursor or any MCP client through Apify's MCP server: `https://mcp.apify.com?tools=zaiq/tesco-scraper`.

### Pricing

Pay per event:

| Event | Price |
|---|---|
| Product scraped (one row written) | $0.003 ($3 per 1,000 products) |
| Product details added (ingredients, allergens, nutrition; only when switched on) | $0.004 |
| Run start | $0.005 |

Free: products that cannot be found, requests that fail or are blocked, retries and duplicates. Your spending limit
for the run is respected: the run stops cleanly before going over it.

### Limits

- Prices are the ones Tesco shows on tesco.com to shoppers who are not signed in. Personalised Clubcard offers
  (Clubcard Challenges, coupons) are not included.
- Tesco Marketplace and F\&F clothing items that appear in search results are left out.
- Sorting is Tesco's own: price sorting uses the Clubcard Price where there is one.
- Product details need one extra request per 20 products, so they take a little longer.
- Up to 10 requests at once (4 by default), to keep the load on Tesco light.
- Check that your use of the data complies with Tesco's terms and with the law that applies to you.

### FAQ

**Do I need a Tesco account or Clubcard?** No. The Actor sees what any visitor to tesco.com sees, including the
advertised Clubcard Prices.

**Which prices are these?** The prices tesco.com shows to a visitor who has not signed in or chosen a delivery address. In-store prices can differ.

**How do I track prices every day?** Put your product numbers in `productIds` and schedule the Actor. Each run
returns the current price, Clubcard Price and stock.

**How do I get every product on offer in a category?** Add the category page under `categoryUrls`, switch on
`offersOnly` and raise `maxItemsPerSource`.

**What if Tesco blocks a request?** It is retried through another UK home connection. Blocked requests are free.

**Can I get the data for Sainsbury's, Asda or Morrisons too?** Yes, our Sainsbury's, Asda and Morrisons scrapers
return the same columns.

# Actor input Schema

## `searchTerms` (type: `array`):

Words to search Tesco for, one per line, the way you would type them on the site (for example: milk, coffee, pasta). Each term returns up to the per-search limit below.

## `categoryUrls` (type: `array`):

Tesco category, aisle or shelf pages to list every product from, for example https://www.tesco.com/groceries/en-GB/shop/fresh-food/milk-butter-and-eggs/milk/all

## `productUrls` (type: `array`):

Tesco product pages to check, for example https://www.tesco.com/groceries/en-GB/products/254656543. Products that cannot be found come back as a free row with an error.

## `productIds` (type: `array`):

Tesco product numbers: the 9-digit TPNC at the end of a product URL, or the 8-digit TPNB. Useful for checking a fixed list of products every day.

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

The run stops after writing this many products in total. You pay only for products written.

## `maxItemsPerSource` (type: `integer`):

Stop each search term or category after this many products. Raise it to list a whole category.

## `offersOnly` (type: `boolean`):

Only products with a promotion: Clubcard Prices, multi-buys, meal deals and other offers. Applies to searches, categories and product inputs.

## `sort` (type: `string`):

The order Tesco returns results in (its own sort options).

## `includeDetails` (type: `boolean`):

Read each product's detail page as well and add its ingredients, allergy advice, nutrition table, storage and origin under `details`. One extra request per product, charged as a separate product-details event.

## `deduplicate` (type: `boolean`):

When a product is found by two searches or categories, write it once (with the first one's search term or category). Switch off to get every search's full list, for example to track search rankings.

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

Tesco answers UK home (residential) connections reliably; that is the default and is included in the price.

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

At most this many requests to Tesco at the same time (kept low to be polite).

## Actor input object example

```json
{
  "searchTerms": [
    "milk"
  ],
  "maxItems": 20,
  "maxItemsPerSource": 200,
  "offersOnly": false,
  "sort": "relevance",
  "includeDetails": false,
  "deduplicate": true,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "GB"
  },
  "maxConcurrency": 4
}
```

# Actor output Schema

## `products` (type: `string`):

Dataset with one row per product (and a free row for each product input that could not be found).

## `summary` (type: `string`):

Products written and charged, what each search, category or product input returned, why the run stopped, requests and traffic.

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

// Run the Actor and wait for it to finish
const run = await client.actor("zaiq/tesco-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 = {
    "searchTerms": ["milk"],
    "maxItems": 20,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "GB",
    },
}

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

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,zaiq/tesco-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/lVeiglSwscX8UihRp/builds/iYn7D7WiRvkviQdlm/openapi.json
