Skip to main content

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

Run the Sainsbury's Scraper Actor from Python to extract grocery shelf prices, Nectar card deals, promotions, and barcodes as clean JSON rows.

Updated 12 min readMarkdown
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 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, 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.


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.

Install the Apify Python client:

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 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 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:

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:

{
  "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:

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), 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). 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 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.


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. 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 to learn more about scheduling runs, configuring webhooks, and exporting datasets. Visit the Sainsbury's Scraper page 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.

About the author

  • Hi, I’m the engineer behind ReventaData. I build automated web scrapers, reverse-engineer undocumented APIs, and design data pipelines on Apify that turn complex, unstructured websites into clean, reliable feeds. Here on Hashnode, I share practical tutorials on modern web scraping with Python, bypassing aggressive anti-bot protections (Cloudflare, Akamai, PerimeterX), and data pipeline automation, reverse-engineering hidden endpoints, and automating data extraction. Explore our turnkey scrapers: https://apify.com/reventadata

No comments yet. Be the first to comment.

More from this blog