# Sainsbury's UK Scraper — Prices, Offers, Barcodes (`architekture/sainsburys-uk-scraper`) Actor

Search Sainsbury's UK products for prices, unit prices, offers and EAN barcodes. Optional product details, previews and continuation. Pay per result.

- **URL**: https://apify.com/architekture/sainsburys-uk-scraper.md
- **Developed by:** [Architekture Studio](https://apify.com/architekture) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.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

## Sainsbury's UK Scraper — Prices, Offers, Barcodes

![Sainsbury's UK product scraper — products, prices and promotions; 31 product fields in detail mode](https://raw.githubusercontent.com/aarchitek/assets/banners/banner-sainsburys-uk.png)

**A recorded preview returned 10 basic product rows from “milk” in 5 seconds.** Owner validation on 15 October 2026 (UTC), build `1.1.3`, run `vUhKpuc5yDPKsw7jW`. A separate enriched search returned two complete detail rows in 5 seconds; continuation returned two new detail rows without repeating them. These are small validation runs, not a throughput or uptime guarantee.

Search Sainsbury's UK groceries by keyword or look up product pages for prices, unit prices, published promotions, barcodes and product information. **31 product fields in detail mode; 23 in basic and preview mode.** These counts exclude source and status metadata. Values may be null or empty.

From Architekture, makers of the Woolworths AU, Coles AU and ALDI AU scrapers. Field names differ between retailers; map them explicitly when combining datasets.

### Why choose this actor?

- **Prices with units.** `price`, `unitPrice`, `unitMeasure` and `unitMeasureAmount` keep shelf and unit prices separate.
- **Published offers, preserved.** `promotions`, `labels` and `isOnOffer` expose offer signals supplied by the source. Promotion text may include Nectar offers; it is not a calculated member checkout price.
- **Product matching.** `productUid`, `ean` and `eans` support joins and deduplication. Missing barcodes remain missing.
- **Optional enrichment with honest fallback.** `includeDetails` requests `description`, `attributes`, `importantInformation` and `images`. Failed enrichment returns the available basic row with `detailStatus: "unavailable"` at the basic rate.
- **Visible collection outcomes.** `RUN_SUMMARY` distinguishes completed, limited, partial and failed collections; `RESUME_STATE` records delivered IDs for continuation.

### Worth knowing up front

- This actor supports keyword search and product URLs. Category browsing, a whole-catalogue export and store-specific requests are not implemented.
- `isAvailable` is a source availability flag, not a store stock quantity. No local-store price or stock guarantee is made.
- `price` is the source retail price. `promotions` may describe conditional offers; the actor does not calculate basket discounts, eligibility or Nectar checkout totals.
- The eight additional detail fields are `description`, `shortDescription`, `importantInformation`, `attributes`, `healthClassification`, `mainCategory`, `breadcrumbs` and `images`. Dedicated ingredients, allergens and nutrition-table fields are not implemented. Do not interpret missing data as an absence of allergens.
- A resumed collection scans current search results again. Rankings and availability can change; it is not a frozen snapshot. Start a fresh run for updated prices or another attempt at previously delivered fallback rows.
- The banner shows the product-field count, not a guarantee that every value is populated. The recorded checks used UK residential proxies. Runtime and source availability vary.

### Try the example

**20 lifetime free preview attempts per authenticated Apify account**: one search term, one page and up to 10 basic rows. Failed attempts count. Preview runs never switch to paid collection automatically. The preview makes no result-event charges; platform usage is included in the actor pricing. The first hosted preview and its persistent quota reservation were verified.

```json
{
  "preview": true,
  "searchTerms": [
    "milk"
  ],
  "maxItemsPerQuery": 10,
  "includeDetails": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "GB"
  }
}
```

For paid collection, explicitly set `preview: false`. Add `includeDetails: true` for enrichment, or use `productUrls` for individual detail lookups. Duplicate products across inputs are removed by `productUid`. The Console prefill is a paid, 10-row basic example; the free example explicitly enables preview. The recorded 10-row preview completed in 5 seconds; future runs may take longer.

### Input parameters

| Parameter | Type | Used by | Description |
|---|---|---|---|
| `searchTerms` | array of strings | Search | Keywords such as `milk`. Duplicate and blank terms removed. |
| `productUrls` | array of strings | Product lookup | Sainsbury's product URLs, `/gol-ui/product/` paths or bare slugs. Requests details regardless of `includeDetails`. Invalid URLs fail before collection. |
| `maxItemsPerQuery` | integer | Search | Default 100; Console prefill 10. Limits new eligible products per term, excluding previously delivered IDs. `0` removes the cap. Preview remains capped at 10. Does not cap explicit URL lookups. |
| `includeDetails` | boolean | Search | Default false. Requests enrichment; successful identified details use `product-detail`, fallback rows use `product`. Optional fields can remain empty. |
| `onlyAvailable` | boolean | Both | Default false. Skip products whose source availability flag is not true. Applies to explicit details too. |
| `sortOrder` | string | Search | `-relevance` (default), `price`, `-price`, `-customer rating`. Unknown values fail validation. |
| `proxyConfiguration` | object | Both | Apify proxy configuration. Console prefill uses residential proxies with a UK exit. Actual accessibility remains subject to upstream behavior. |
| `preview` | boolean | Search | Default false. One term, one page, up to 10 basic rows; no details, explicit URLs or continuation. 20 lifetime attempts. |
| `resumeState` | object | Paid runs | Copy the complete `RESUME_STATE` record from an earlier run. Keep queries/order, detail mode, availability filter and sort unchanged. |
| `feedback` | string | Both | Optional maintainer note, logged up to 4,000 characters without changing output. Do not include secrets or personal data. |

### Stable fields for automations

These top-level field names and meanings are the supported automation contract. A stable field name does not guarantee a nonempty value. Normalize `productUid` to a string when joining datasets: the source may encode it as a string or integer.

| Field | Meaning |
|---|---|
| `productUid` | Source product identifier; deduplication key. |
| `name` | Source product name, or null. |
| `price` | Retail price in GBP, number or null. |
| `unitPrice` | Source unit price, number or null. |
| `unitMeasure` / `unitMeasureAmount` | Unit label and numeric quantity used for the unit price, or null. |
| `ean` / `eans` | First supplied barcode and list of supplied barcodes; null/empty when absent. |
| `isOnOffer` | Boolean derived from promotion presence or a lower unit price than `originalUnitPrice`. Not a guaranteed saving at checkout. |
| `promotions` | Published promotion text when extractable; otherwise source promotion entries. Consumers must tolerate strings or objects. |
| `isAvailable` | Source availability flag; may be null. Not stock quantity. |
| `reviewCount` / `reviewRating` | Source review count and numeric rating where supplied. |
| `category` / `categoryId` | First source category name and ID, or null. |
| `image` / `url` | Source product image and page URL, where supplied. |
| `source` | Top-level string: `search` or `id`. |
| `searchTerm` / `productUrl` | Input context: the former on search rows, the latter on explicit lookups. |
| `detailStatus` | `complete` for an identified detail response; `unavailable` for a basic fallback. Absent on basic-only rows. |

Other fields—including nested `attributes`, `badges`, `images` and source classifications—are source-derived extras. Treat their contents as a bonus rather than a stable nested schema. New fields can be added; stable names will not be renamed. Ask through `feedback` if a further field is needed for a production workflow.

### Output examples

Abridged real output recorded on 15 October 2026 (UTC), build `1.1.3`. Basic row: run `vUhKpuc5yDPKsw7jW`; product URL row: run `Qc1cPZmzaqxBl1s2T`. Prices are observations from those runs, not current-price guarantees.

**Keyword search — basic row**

```json
{
  "productUid": "8172497",
  "name": "Sainsbury’s 1L Fresher For longer Filtered Semi Skimmed Milk",
  "price": 1.5,
  "unitPrice": 1.5,
  "unitMeasure": "ltr",
  "ean": "0000000514828",
  "promotions": [],
  "isAvailable": true,
  "source": "search",
  "searchTerm": "milk",
  "url": "https://www.sainsburys.co.uk/gol-ui/product/sainsbury-s-1l-fresher-for-longer-filtered-semi-skimmed-milk"
}
```

**Product URL — detail row**

```json
{
  "productUid": "8172497",
  "name": "Sainsbury’s 1L Fresher For longer Filtered Semi Skimmed Milk",
  "description": "Pasteurised homogenised filtered semi-skimmed milk",
  "attributes": {
    "brand": [
      "Sainsbury's"
    ],
    "new": [
      "Y"
    ]
  },
  "reviewRating": 5,
  "detailStatus": "complete",
  "source": "id",
  "productUrl": "https://www.sainsburys.co.uk/gol-ui/product/sainsbury-s-1l-fresher-for-longer-filtered-semi-skimmed-milk"
}
```

Search with `includeDetails: true` uses the same detail fields with `source: "search"`. Optional detail failure preserves the basic row with `detailStatus: "unavailable"`. An unavailable explicit lookup produces no row and no result event.

### Advanced usage

**Price and offer monitoring.** Schedule a fresh keyword run daily as a starting cadence, then adjust to your needs and budget. Join on string-normalized `productUid` and compare `price`, `unitPrice` and `promotions`. Store your own observation timestamp; the actor does not add one.

**Cross-retailer matching.** Use `ean` or individual `eans` values to find candidate matches in another retailer dataset. Compare pack size and unit information before treating prices as equivalent. Keep barcodes as text, preserving leading zeroes. A missing barcode is not a match.

**Selective enrichment.** Start with basic searches, inspect `url`, and submit selected URLs through `productUrls` in a paid run. This requests details only for the products you selected. An explicit lookup has no basic listing to fall back to.

**Continue a bounded collection.** Copy `RESUME_STATE` from the run's key-value store into the next run's `resumeState`. Retain the same terms and their order, product URLs, detail mode, availability filter and sort. You may change the item cap or budget. Searches replay from page one, skipping delivered IDs; newly delivered rows go into the new run's dataset. Combine datasets and deduplicate by string-normalized `productUid`. No previous-run lookup happens automatically. An interruption between delivery and checkpoint saving can produce an overlap; this is not an exactly-once guarantee.

#### Read the run summary

| `RUN_SUMMARY.status` | Meaning |
|---|---|
| `COMPLETE` | Requested traversal finished against the responses received; not a whole-catalogue guarantee. |
| `PARTIAL` | Rows delivered, but requested details were unavailable. Limits may also have applied. |
| `LIMITED` | Per-term cap or preview boundary stopped traversal. |
| `BUDGET_LIMIT` | SDK result budget stopped delivery. |
| `PREVIEW_LIMIT` | Lifetime preview allowance exhausted; no collection or paid fallback. |
| `FAILED` | Validation, collection or delivery failed; inspect any saved rows and logs. |

The summary also records `listRows`, `detailRows`, `unavailableDetails` and `preview`. Empty or malformed responses are retried; repeated pages and contradictory pagination fail visibly. An abrupt platform termination may prevent a final summary from being written: always check the Apify run status too.

### Integrations

Results are written to the run's default dataset for JSON, CSV or spreadsheet export. Export nested values as JSON when you need to preserve `eans`, `promotions` or `attributes`. Google Sheets, Make, Zapier and n8n workflows can consume the exported dataset or dataset API; bind to the stable fields above. Use Apify Scheduler for recurring runs and run-completion webhooks to start downstream processing. Check both the platform run status and `RUN_SUMMARY` before treating an export as complete. These are integration paths, not bundled or independently validated connectors.

### Pricing: pay per event

USD rates verified in Apify Console on 15 October 2026 (UTC). Platform usage, including the configured proxy, is included: the developer pays it. No startup event is configured.

| Event | Price | What it covers |
|---|---|---|
| Free preview | $0 | First 20 preview attempts per account, one page and at most 10 basic rows each. |
| `product` | $2 / 1,000 rows | One delivered basic search row, including an optional-detail fallback. |
| `product-detail` | $5 / 1,000 rows | One delivered, identified detail response from enriched search or an explicit URL lookup. |

Nothing is charged for failed requests by this actor's result-event logic. A usable basic fallback is a delivered result and uses `product`. A detail row uses `product-detail` alone, not both events. A successful detail response does not guarantee every optional field. A 10-row paid basic example costs $0.020; a two-row fully enriched example costs $0.010. Preview attempts have no paid fallback. Owner validation runs incur platform costs for the studio and do not represent customer revenue.

### FAQ

**What happens after 20 previews?** Preview runs stop with `PREVIEW_LIMIT`. Set `preview: false` explicitly to collect paid results. If allowance verification fails, collection stops without paid fallback. The quota stores retain a hashed account/actor identifier and reserved-slot markers, not search terms or product rows.

**Do I need a Sainsbury's account or cookies?** No customer login or supplied cookies are required by this implementation. It reads public product responses. Upstream access can still fail or change.

**Can I browse categories or download every product?** No category operation is implemented. Searches paginate within your caps and the source responses; complete catalogue coverage is not promised.

**Are these my local store's prices or stock counts?** No. There is no store selector or stock-quantity field. `isAvailable` reflects the response used by this run.

**Does `price` include Nectar discounts?** Not necessarily. Treat `price` as the source retail price and inspect `promotions` for published offer terms. The actor does not calculate personal eligibility or checkout totals.

**Why are detail fields empty, or marked unavailable?** Empty fields may simply be absent from a matched source record. `detailStatus: "unavailable"` means enrichment failed or could not identify the requested product; a basic search row is retained where possible. Explicit lookups with no usable detail produce no row.

**Does continuation refresh old prices or retry missing enrichment?** No. It skips delivered product IDs, including basic fallback rows. Omit `resumeState` for fresh monitoring or enrichment retries. Source changes and interrupted checkpoint writes can cause gaps or overlaps; deduplicate combined datasets.

**Why did a run return no matches or stop early?** Empty replies are retried before accepting no matches. Malformed/repeated pages fail; budgets and caps stop collection explicitly. Inspect run status, `RUN_SUMMARY` and logs before increasing limits or retrying.

### Copy to your AI assistant

Use Apify actor ID `dzgCkkXIDarLqqCgy` (Sainsbury's UK, Architekture). Python: `from apify_client import ApifyClient`; `client = ApifyClient("TOKEN")`; `run = client.actor("dzgCkkXIDarLqqCgy").call(run_input={"preview": True, "searchTerms": ["milk"], "maxItemsPerQuery": 10, "proxyConfiguration": {"useApifyProxy": True, "apifyProxyGroups": ["RESIDENTIAL"], "apifyProxyCountry": "GB"}})`; then `client.dataset(run["defaultDatasetId"]).list_items().items` reads the first dataset page; paginate or use `iterate_items()` for larger exports. Inspect the deployed build with authenticated `GET https://api.apify.com/v2/acts/dzgCkkXIDarLqqCgy/builds/default` for its `inputSchema`, `actorDefinition.storages.dataset` and `readme`; use the published default build for production, and confirm the build number before binding an automation. Obtain a token at https://console.apify.com/account/integrations and keep it secret. Read `RUN_SUMMARY` from the run's default key-value store before assuming completeness. `resumeState` accepts that store's `RESUME_STATE`; omit it for fresh price monitoring. Add `feedback` with what you needed and did not get; it is logged for human review and does not change output.

### More Architekture scrapers

Explore the studio’s current releases at [Architekture on Apify](https://apify.com/architekture). Planned products below are not promises of availability.

**Grocery — Australia & New Zealand**

- ALDI AU, Woolworths AU and Coles AU — previously published studio products.
- Woolworths NZ (Countdown) — in preparation.

**Grocery — United Kingdom**

- Sainsbury's UK — **you are here**.
- Tesco UK — in preparation.

**Research & regulatory**

- PubMed, ClinicalTrials.gov, arXiv and openFDA — in preparation.

**Startups & companies**

- Y Combinator, Product Hunt, SEC EDGAR, UK Companies House and Apollo.io — in preparation.

**Communities & publishing**

- Telegram, Meetup, Substack, Hacker News and Reddit — in preparation.

**Utilities**

- Screenshot & PDF Renderer — repository candidate; platform listing not verified.

**Catalogue operations**

- Price Sentinel, Catalog Price Checker and Catalog Release Guard — studio catalogue tools; see the profile for current availability.

### Support

Open an issue on the actor's page with the run ID and a small reproducible input, excluding credentials. A human reply within one day is our support commitment. For a field request or a note about a successful run, use `feedback`.

### Legal compliance

This actor is intended for publicly available product data and does not request customer account access. Architekture is independent of Sainsbury's; the branding identifies the target retailer and does not imply endorsement. You are responsible for your use of the output, applicable site terms, and GDPR/CCPA obligations where relevant. Do not put personal data or credentials in feedback.

### Changelog

- **2026-10-15 (release candidate)** — Recorded live examples, verified $2/$5 per 1,000 event pricing, preview quota reservation, 31 detail fields and continuation without overlap. Published the original banner in studio assets.

- **2026-10-14 (unreleased)** — Added the user-designed banner, ALDI-style documentation, stable-field contract, preview/continuation examples and explicit publication evidence gates.

- **2026-10-14 (unreleased)** — Added input and product-identity validation, malformed/repeated-page safeguards, SDK budget-aware delivery and completion summaries.

- **2026-10-14 (unreleased)** — Implemented 20 lifetime preview attempts with a 10-row/one-page limit and durable quota records, plus explicit continuation checkpoints. Restricted preview storage configured; preview, enriched search, direct lookup and continuation validated on build `1.1.3`.

- **2026-10-14 (unreleased)** — Added basic-price fallback for unavailable enrichment, retries for empty responses and feedback logging.

# Actor input Schema

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

Keywords to search. One result set per term.

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

Sainsbury's product page URLs, /gol-ui/product/ paths, or bare product slugs. Requests full details; unavailable products are reported rather than silently skipped.

## `maxItemsPerQuery` (type: `integer`):

Cap results per search term. 0 = no cap.

## `onlyAvailable` (type: `boolean`):

Skip products Sainsbury's reports as unavailable.

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

Request description, attributes and other detail fields for search results. Matched detail rows use product-detail pricing; unavailable enrichment retains the basic row at product pricing. Optional fields may be empty.

## `sortOrder` (type: `string`):

Sainsbury's search sort order

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

Proxy configuration. Prefill uses Apify residential proxies with a UK exit; accessibility depends on upstream responses.

## `feedback` (type: `string`):

Tell the maintainers what you needed and did not get. Logged for human review; does not change the output. Do not include credentials or private personal data.

## `preview` (type: `boolean`):

20 lifetime preview attempts per authenticated account. One search term, one page, at most 10 basic rows. Failed attempts count. No paid fallback.

## `resumeState` (type: `object`):

Paste RESUME_STATE from an earlier paid run. Keep queries, order, filters and details unchanged. Replays searches and skips previously delivered product IDs; maxItemsPerQuery applies to new eligible products per term. Source changes can affect coverage.

## Actor input object example

```json
{
  "searchTerms": [
    "milk"
  ],
  "maxItemsPerQuery": 10,
  "onlyAvailable": false,
  "includeDetails": false,
  "sortOrder": "-relevance",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "GB"
  },
  "preview": false
}
```

# Actor output Schema

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

Scraped items in the default dataset (one JSON object per row).

## `runSummary` (type: `string`):

No description

## `resumeState` (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 = {
    "searchTerms": [
        "milk"
    ],
    "maxItemsPerQuery": 10,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "GB"
    },
    "preview": false
};

// Run the Actor and wait for it to finish
const run = await client.actor("architekture/sainsburys-uk-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"],
    "maxItemsPerQuery": 10,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "GB",
    },
    "preview": False,
}

# Run the Actor and wait for it to finish
run = client.actor("architekture/sainsburys-uk-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"
  ],
  "maxItemsPerQuery": 10,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "GB"
  },
  "preview": false
}' |
apify call architekture/sainsburys-uk-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,architekture/sainsburys-uk-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/dzgCkkXIDarLqqCgy/builds/5AMWV8GQ28QJqXEWR/openapi.json
