# Complete Guide: How to Scrape Sainsbury's Groceries Using Python

This complete guide to Sainsbury's Scraper shows how to extract grocery products, shelf prices, Nectar promotions, unit pricing, and barcodes from [sainsburys.co.uk](http://sainsburys.co.uk) using Python and the Apify API. Rather than copying product details manually, you pass a search term and receive structured rows: product name, shelf price in pounds sterling, unit measure, regular price before discounts, Nectar flags, customer ratings, review counts, categories, and EAN barcodes.

A grocery query such as "coffee" returns items mixing standard pricing, Nectar deals, multibuy promotions, and related items. The Actor applies filters directly to the extracted items, so your dataset matches your criteria. Here the target is specific: coffee items currently on promotion, restricted to exact search matches.

You will install the Apify client, configure parameters, run the Actor, and read the rows. You need Python, a terminal, and an Apify API token. Estimated time to complete: About 30 minutes. The benchmark run cited throughout this tutorial (started on 2026-10-23 with `searchQueries: ["coffee"]`, `maxItems: 60`, `maxPages: 3`, `onlyPromotions: true`, `exactMatchesOnly: true`) returned 50 structured rows in 10.6 seconds.

---

## TL;DR

In this tutorial, you'll learn to run the Sainsbury's Scraper Actor from Python, extract grocery items from [sainsburys.co.uk](http://sainsburys.co.uk), and filter by promotions and exact search relevance. Key steps: install `apify-client`, configure your API token, define `run_input` with search queries and promotion filters, call the Actor, and iterate over the default dataset items.

Expected outcome: 50 typed rows for the cited run, each with product name, price, currency, unit price, Nectar promotion details, customer rating, and product URL, ready for spreadsheets, database ingestion, or grocery price comparisons.

The Actor is available at [apify.com/reventadata/sainsburys-scraper](https://apify.com/reventadata/sainsburys-scraper).

---

## Prerequisites and setup

To follow this tutorial, you need three prerequisites:

1. Python 3 on your local machine.
2. The official Apify API client library for Python, installed via pip.
3. An active Apify account with a personal API token.

Apify Console is the web interface where your Actors, tasks, runs, datasets, and integration tokens are managed. Your personal API token is located on the API & Integrations tab in Apify Console. Official platform documentation is available at [docs.apify.com](https://docs.apify.com/).

Install the Apify Python client:

```bash
pip install "apify-client~=3.0"
```

Keep your personal API token secure and out of version control. In the demonstration scripts below, replace the placeholder `<YOUR_APIFY_TOKEN>` with your real API token or load it from an environment variable in your production configuration.

---

## Understanding Sainsbury's Scraper

Sainsbury's Scraper (`reventadata/sainsburys-scraper`) searches [sainsburys.co.uk](http://sainsburys.co.uk) for any grocery keyword and returns each matching product as one structured record. Behind the scenes, the Sainsbury's search page is a Next.js streamed application that embeds data in streamed push chunks. The Actor parses these chunks, extracts organic product objects tagged with `source == "SEARCH_RESULTS"`, and formats them into clean dataset rows.

Key capabilities:

- **Filter organic items:** Excludes sponsored placements and recommendations carousels by filtering on search results.
- **Price and Nectar parsing:** Separates shelf prices from regular prices before discount, and flags promotions requiring a Nectar card.
- **Unit economics:** Extracts price per unit measure (`ltr`, `kg`, `ea`) for direct price comparison.
- **Exact match relevance:** Drops lower-ranked related items (`relevance: 2`) when `exactMatchesOnly` is set.
- **EAN barcode identification:** Captures barcodes for cross-retailer product matching.

Limitation to note: the input schema allows at most 20 for `maxPages`.

---

## Step-by-step walkthrough

### Step 1: Accessing the Actor

Open the [Sainsbury's Scraper](https://apify.com/reventadata/sainsburys-scraper) page on the Apify Store. The store listing details input options, output schemas, and pricing. You can run the Actor directly from the Apify Console web interface, but this tutorial drives it from Python so that the extraction logic can easily integrate into scheduled jobs or data pipelines.

### Step 2: Configuring input parameters

The Actor accepts a JSON input object. The core fields configured in this guide include:

| Field | Type | Description |
| --- | --- | --- |
| `searchQueries` | array | Grocery search terms, one search per term (e.g. `["coffee"]`). |
| `maxItems` | integer | Maximum products to collect across all queries (default: 100). |
| `maxPages` | integer | Maximum pages to walk per query, up to 60 products per page (default: 6). |
| `sort` | string | Sort order: `RELEVANCE`, `PRICE_ASC`, `PRICE_DESC`, or `RATING`. |
| `onlyPromotions` | boolean | Retain only products currently on promotion. |
| `exactMatchesOnly` | boolean | Drop lower-ranked related items (`relevance: 2`). |
| `proxyConfiguration` | object | Proxy settings; requires Apify Unblocker. |

Three example configurations:

- **Discounted coffee items** (used in this guide): `{"searchQueries": ["coffee"], "maxItems": 60, "maxPages": 3, "onlyPromotions": true, "exactMatchesOnly": true}`
- **Broad grocery staples by lowest price**: `{"searchQueries": ["milk", "butter"], "maxItems": 100, "sort": "PRICE_ASC"}`
- **Premium olive oil with minimum rating**: `{"searchQueries": ["olive oil"], "minPrice": 5.0, "minRating": 4.0, "maxItems": 40}`

### Step 3: Advanced configuration

Beyond basic searches, the Actor provides parameters for fine-grained filtering:

- `minPrice` and `maxPrice`: Numeric price bounds in pounds sterling.
- `minRating`: Minimum average customer rating (0 to 5); unrated products are excluded when this is set.
- `includeKeywords`: List of words that must appear in the product name or categories.
- `excludeKeywords`: List of words that must not appear in the product name or categories.

All keyword, price, rating, and promotional filters execute after each page is fetched, ensuring consistent behaviour across all categories.

### Step 4: Optional promotion and relevance filters

Sainsbury's search results often include related items with `relevance: 2` after exact query matches. Enabling `exactMatchesOnly: true` discards these secondary recommendations. Similarly, setting `onlyPromotions: true` filters the extracted items so that only products with active promotions (Nectar prices, multibuy offers, or fixed price drops) are retained.

### Step 5: Running the Actor

The following Python script initializes the client, configures the input used in our benchmark run, triggers the Actor, and prints the extracted items:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")

run_input = {
    "searchQueries": ["coffee"],
    "maxItems": 60,
    "maxPages": 3,
    "onlyPromotions": True,
    "exactMatchesOnly": True,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["UNBLOCKER"],
    },
}

run = client.actor("reventadata/sainsburys-scraper").call(run_input=run_input)

for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item["name"], item["price"], item["currency"], item["unitPrice"], item["url"])
```

Calling `.call()` starts the run and waits for completion. The cited benchmark run finished with status `SUCCEEDED` in 10.6 seconds and pushed 50 rows.

### Step 6: Examining the results

The dataset items can be inspected directly in Apify Console or consumed programmatically via `iterate_items()`. The first record from our benchmark run is shown below:

```json
{
  "id": "7620344",
  "name": "Nescafé Azera Americano Instant Coffee 90g",
  "url": "https://www.sainsburys.co.uk/groceries/product/nescaf-azera-americano-instant-coffee-90g",
  "price": 3.5,
  "currency": "GBP",
  "unitPrice": 38.89,
  "unitMeasure": "kg",
  "regularPrice": 7.25,
  "priceIsNectar": true,
  "onPromotion": true,
  "promotions": [
    {
      "type": "SIMPLE_FIXED_PRICE",
      "text": "Better than Half Price with Nectar",
      "isNectar": true,
      "regularPrice": 7.25,
      "startsAt": "2026-10-14T23:00:00Z",
      "endsAt": "2026-11-03T00:00:00Z"
    }
  ],
  "rating": 4.61,
  "reviewCount": 191,
  "categories": [
    "Instant coffee"
  ],
  "image": "https://assets.sainsburys-groceries.co.uk/gol/7620344/image.jpg",
  "eans": [
    "7613034065520",
    "8445290044822"
  ],
  "isAvailable": true,
  "relevance": 1,
  "matchedKeyword": "coffee",
  "page": 1,
  "scrapedAt": "2026-10-23T19:42:19.809562+00:00"
}
```

In this record, `price` is 3.50 GBP with `priceIsNectar: true`, while `regularPrice` shows the pre-discount price of 7.25 GBP. The `unitPrice` is 38.89 per kilogram, and the `promotions` array details the promotion duration and terms.

### Step 7: Downloading and exporting data

The dataset can be exported from Apify Console or downloaded programmatically in JSON, CSV, Excel or JSONL formats. JSON preserves nested arrays such as `promotions`, `categories`, and `eans`, while CSV and Excel formats flatten top-level columns for spreadsheet analysis.

---

## Working with the API

The Python script above uses the synchronous `.call()` method from the `apify-client` package, which blocks until the run completes and then returns a Run object. For automated data pipelines or microservices, you may prefer an asynchronous execution model where you trigger the Actor and disconnect immediately:

```python
run = client.actor("reventadata/sainsburys-scraper").start(run_input=run_input)
print(f"Started run with ID: {run.id}, status: {run.status}")
```

Once started, you can store `run.id` and fetch the dataset at a later time. To retrieve results from a completed run, access the dataset through `client.dataset(run.default_dataset_id)`. You can stream items sequentially using `iterate_items()` or fetch a specific page of items using `list_items(offset=0, limit=100)`.

Authentication is managed via your API token provided to the `ApifyClient` constructor. For event-driven architectures, you can configure an Apify webhook to listen for the `ACTOR.RUN.SUCCEEDED` event. The webhook delivers the run resource payload containing `defaultDatasetId` directly to your HTTP endpoint ([docs.apify.com/platform/integrations/webhooks/actions](https://docs.apify.com/platform/integrations/webhooks/actions)), allowing your backend to download dataset records without polling.

Error handling is critical in production pipelines: always verify that `run.status == "SUCCEEDED"` before consuming dataset records. If Sainsbury's Scraper pushes zero rows—for example, if a search term returns no listings or an overly aggressive filter excludes every card—the Actor raises a RuntimeError and fails the run loudly rather than reporting a silent, empty success.

---

## Advanced usage patterns

### 1\. Batch queries across grocery categories

You can pass multiple grocery search terms in `searchQueries`, such as `["milk", "oat milk", "butter"]`. The Actor walks each query sequentially in the order provided, pushing all extracted products to a single default dataset. Each row includes the `matchedKeyword` field indicating which search term produced the item. An item ID is marked seen once kept, preventing duplicate product records across multiple search terms within the same run.

### 2\. Price bands and keyword exclusions

Combine `minPrice` and `maxPrice` with `includeKeywords` and `excludeKeywords` to isolate specific product tiers. For instance, you can search for coffee while excluding decaf items by passing `excludeKeywords: ["decaf", "decaffeinated"]`. The keyword filters inspect both the product title and its categories, performing case-insensitive and accent-insensitive matching.

### 3\. Scheduled competitor price tracking

To monitor price adjustments and promotions over time, run the Actor on a recurring schedule. Apify schedules allow you to trigger Actors automatically using standard cron expressions ([docs.apify.com/platform/schedules](https://docs.apify.com/platform/schedules)). By comparing sequential runs by `id`, you can detect when a product enters a promotional cycle, changes its shelf price, or introduces a Nectar discount.

### 4\. Isolating promotional deals

Set `onlyPromotions: true` to ingest only discounted products, meal deals, or multibuy promotions. This eliminates non-promotional catalog noise, reducing downstream storage and processing requirements for price-drop alert systems.

---

## Understanding your data

Each dataset row contains the core attributes of a Sainsbury's grocery product:

- `id`, `name`, `url`: Unique product identifier, product name, and canonical URL.
- `price`, `currency`, `unitPrice`, `unitMeasure`: Shelf price in GBP, price per unit measure, and unit (`kg`, `ltr`, or `ea`).
- `regularPrice`, `priceIsNectar`, `onPromotion`, `promotions`: Standard shelf price before discount, Nectar requirement flag, and promotion array.
- `rating`, `reviewCount`: Average customer rating out of 5 and review count.
- `categories`, `eans`, `isAvailable`, `relevance`: Category list, EAN barcodes, availability, and search relevance.

The cited run on 2026-10-23 extracted 50 products across 3 result pages (16 from page 1, 13 from page 2, 21 from page 3). All 50 had numeric prices ranging from 1.75 to 16.50 GBP (median 4.80, mean 5.36 GBP). All 50 products were on promotion: 47 featured fixed-price Nectar deals with `priceIsNectar: true` and regular prices recorded, while 3 featured multibuy Nectar deals where single items were un-discounted. Customer ratings appeared on 48 products, ranging from 3.37 to 5.0 (median 4.53), summing to 2,897 reviews. All 50 products were available, exact matches (`relevance: 1`), with 72 EAN barcodes across 15 categories.

---

## Troubleshooting guide

- **The run fails with "0 rows for queries":** If all retrieved listings are excluded by filters or if a query yields no items, the Actor raises a RuntimeError and halts execution so that downstream automated pipelines and monitoring systems detect the failure immediately. Loosen restrictive filters such as `minRating`, `onlyPromotions`, or `exactMatchesOnly`, and verify on [sainsburys.co.uk](http://sainsburys.co.uk) that the term returns items.
- **Input rejected prior to execution:** `sort` is validated against supported values (`RELEVANCE`, `PRICE_ASC`, `PRICE_DESC`, `RATING`). An unsupported sort value is refused before the run starts. An empty `searchQueries` list makes the Actor raise a ValueError before page processing begins.
- **Access Denied errors or blocked requests:** Sainsbury's employs anti-bot protection that blocks requests from plain and datacenter connections. Ensure `proxyConfiguration` uses Apify Unblocker (`apifyProxyGroups: ["UNBLOCKER"]`).
- **Fewer rows than** `maxItems`**:** The search term may have fewer products than your requested maximum, or the query may have exhausted available pages within the `maxPages` ceiling.
- **Reading run logs:** Inspect the detailed execution log in Apify Console. At completion, the Actor outputs a breakdown of rejected rows grouped by filter reason (such as `no-promotion`, `price-band`, or `rating`).

---

## Best practices

1. **Always set** `maxItems` **and** `maxPages`**.** Set explicit caps to prevent over-scraping broad queries; each result page yields up to 60 products.
2. **Use Apify Unblocker proxy configuration.** Standard datacenter proxies and unproxied requests receive Access Denied responses from Sainsbury's; maintain the default Unblocker proxy configuration.
3. **Filter promotions at the Actor level.** Enable `onlyPromotions` directly in `run_input` rather than post-processing, so non-promotional rows never reach your dataset.
4. **Target exact matches.** Use `exactMatchesOnly: true` to discard lower-ranked related items (`relevance: 2`) appended by the site's search engine.
5. **Cross-reference using EAN barcodes.** Utilize the `eans` array to match products accurately against pricing data from other supermarkets.
6. **Automate on a schedule.** Deploy scheduled runs with Apify schedules to capture weekly pricing updates and new promotional cycles.

---

## Pricing

For current pricing and details, see the [Store page](https://apify.com/reventadata/sainsburys-scraper).

---

## Conclusion and next steps

You now have a functional Python script to extract structured grocery products, pricing, Nectar deals, and barcodes from [sainsburys.co.uk](http://sainsburys.co.uk). By supplying a search term, tuning promotion and relevance filters, and iterating over the dataset, you can integrate fresh supermarket data into price tracking applications or analytics workflows.

Explore the [Apify documentation](https://docs.apify.com/) to learn more about scheduling runs, configuring webhooks, and exporting datasets. Visit the [Sainsbury's Scraper page](https://apify.com/reventadata/sainsburys-scraper) to review the input schema and test different grocery categories.

Try Sainsbury's Scraper yourself and let me know what you build!

---

*I build and maintain marketplace scrapers under the reventadata name on the Apify Store.*
