Sainsbury's Scraper — Prices, Unit Prices, Offers, Barcodes
Pricing
from $2.00 / 1,000 products
Sainsbury's Scraper — Prices, Unit Prices, Offers, Barcodes
Sainsbury's UK (sainsburys.co.uk) scraper: product name, price, unit price, offers, promotions, EAN barcodes, labels, reviews, rating, category, availability, image, URL; detail rows add description, attributes, important information, images. Keyword or product URL. Pay per result, no login.
Pricing
from $2.00 / 1,000 products
Rating
0.0
(0)
Developer
architek.earth
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
13 days ago
Last modified
Categories
Security
Limited permissionsShare

Recorded run: 5 products from 1 search term in 5 seconds (2026-10-22, build 1.1.8, run oJOYCGLHem1ogb76J). A bounded canary sample, not a full-catalogue benchmark.
architek.earth — an independent data studio. Pay-per-result scrapers with recorded runs, published canaries and stable schemas, built for people and AI agents.
Sainsbury's UK grocery prices, unit prices, offers and EAN barcodes from sainsburys.co.uk, by keyword search or product URL, with optional product descriptions and attributes.
| Source | Market | From | Standout fields |
|---|---|---|---|
| Sainsbury's (sainsburys.co.uk) | United Kingdom | $2.00 / 1,000 products | price / unitPrice, promotions, ean / eans, reviewRating |
✅ Why choose this scraper
Sainsbury's prices and unit prices in one row
price (GBP), unitPrice, unitMeasure, unitMeasureAmount and originalUnitPrice come back together on every product row, including basic rows.
Sainsbury's offers and promotions as published
promotions carries the published offer text, isOnOffer flags a promotion or a unit price below originalUnitPrice, and labels keeps tags such as "New".
Sainsbury's EAN barcodes on basic rows
ean (first barcode) and eans (all supplied barcodes) are on every row, not only detail rows, for joins with other retailers.
Product descriptions and attributes on demand
With includeDetails: true or productUrls, rows add description, shortDescription, importantInformation, attributes, healthClassification, mainCategory, breadcrumbs and images: 31 product fields in detail output, 23 in basic output.
Measured on a schedule
A canary task runs a fixed milk search on the live site and records rows, inputs and seconds; the hero line above is its latest green run.
⚠️ Worth knowing up front
- Keyword search and product URLs only. Category browsing, a whole-catalogue export and store or postcode selection are not implemented; prices are the public online catalogue, not a specific shop.
isAvailableis the source availability flag, not a stock count.priceis the source retail price.promotionsmay describe conditional or Nectar offers; the actor does not calculate basket discounts, eligibility or checkout totals.- Dedicated ingredients, allergens and nutrition-table fields are not implemented. Do not read missing data as an absence of allergens.
- Sponsored tiles are kept and flagged with
isSponsored: true, not hidden. - Products are de-duplicated by
productUidacross inputs: a product found by an earlier query is not emitted again. - A resumed collection replays current search results and skips delivered IDs; rankings and availability can change between runs, so it is not a frozen snapshot.
- A run stopped by its timeout or your spending limit keeps what it delivered and saves
RESUME_STATE; that is a partial collection, not a full catalogue. - Invalid input, a block after retries and the run deadline end with a free status row (
"recordType": "status") that says what happened. A search with no matches ends with no rows; checkRUN_SUMMARY.
⚡ Try the example
Each Apify account gets 20 lifetime free preview attempts on this actor: one search term, one page, up to 10 basic rows, no product charges. Failed attempts count. After the twentieth, preview runs stop with PREVIEW_LIMIT; they never switch to paid collection.
Open the actor's input page, switch to JSON, paste this and start the run:
{"preview": true,"searchTerms": ["milk"],"maxItemsPerQuery": 10,"proxyConfiguration": {"useApifyProxy": true,"apifyProxyGroups": ["RESIDENTIAL"],"apifyProxyCountry": "GB"}}
For paid collection set "preview": false; add "includeDetails": true for descriptions, attributes and images, or pass productUrls.
⚙️ Input
| Parameter | Type | Default | Description |
|---|---|---|---|
preview | boolean | false | Use one of 20 lifetime free attempts: one search term, one page, at most 10 basic rows. No details, product URLs or resumption. |
searchTerms | array | — (prefill ["milk"]) | Keywords such as milk. One collection per term; duplicates and blanks are removed. |
productUrls | array | — | Sainsbury's product URLs, /gol-ui/product/ paths or bare product slugs. Always returns detail rows; unavailable products produce no row and no charge. Other hosts are rejected before collection. |
maxItemsPerQuery | integer | 100 (prefill 10) | New products per search term. 0 removes the cap in paid mode; preview stays at 10. Does not cap productUrls. |
onlyAvailable | boolean | false | Skip products whose isAvailable flag is not true. Applies to product URLs too. |
includeDetails | boolean | false | Add description, attributes, important information and images to search rows (charged as product-detail). |
sortOrder | select | -relevance | -relevance, price (low to high), -price (high to low), -customer rating. |
resumeState | object | — | The RESUME_STATE record from a previous paid run. Keep terms, order, product URLs, details, filter and sort unchanged; delivered IDs are skipped and not charged. |
proxyConfiguration | proxy | — (prefill: residential GB) | Proxy settings. API callers should pass them explicitly. |
feedback | string | — | Optional note for the maintainers. It is logged and read by a human, and it never changes output. |
🧱 Stable fields
These top-level fields keep the same name and the same kind of value in every release. Fields are only ever added, never renamed or removed, so an n8n, Make, Zapier or spreadsheet pipeline can bind to them.
| Field | Type | Meaning |
|---|---|---|
productUid | string | Sainsbury's product ID; the join and de-duplication key. Normalise to string when joining. |
name | string, nullable | Product name. |
price | number, nullable | Current retail price in GBP. |
unitPrice / unitMeasure / unitMeasureAmount | number / string / number, nullable | Unit price, its unit label (for example ltr) and quantity. |
originalUnitPrice | number, nullable | Pre-offer unit price when Sainsbury's supplies one. |
isOnOffer | boolean | True when a promotion is present or unitPrice is below originalUnitPrice. Not a guaranteed checkout saving. |
promotions | array | Published promotion text; may hold source objects when no text is extractable. |
ean / eans | string, nullable / array | First supplied barcode and all supplied barcodes, kept as text with leading zeros. |
isAvailable | boolean, nullable | Source availability flag; not a stock count. |
reviewCount / reviewRating | number, nullable | Review count and average rating. |
category / categoryId | string, nullable | First source category name and ID. |
image / url | string, nullable | Product image and product page. |
source | string | search or id (product URL lookup). |
searchTerm / productUrl | string | The query that produced the row: the former on search rows, the latter on product URL rows. |
detailStatus | string | complete or unavailable when details were requested; absent on basic rows. |
recordType | string | "status" on free status rows; absent on product rows. |
Rows also carry sainId, labels, badges, isAlcoholic and isSponsored; detail rows add description, shortDescription, importantInformation, attributes, healthClassification, mainCategory, breadcrumbs and images. Other fields are extras, not part of the contract. To have one pinned, say so in feedback.
📦 Output example
Abridged real row from run oJOYCGLHem1ogb76J on 2026-10-22; values are a historical snapshot.
{"productUid": "8172497","ean": "0000000514828","eans": ["0000000514828"],"name": "Sainsbury’s 1L Fresher For longer Filtered Semi Skimmed Milk","price": 1.5,"unitPrice": 1.5,"unitMeasure": "ltr","unitMeasureAmount": 1,"originalUnitPrice": null,"isOnOffer": false,"promotions": [],"labels": ["New"],"reviewCount": 7,"reviewRating": 5,"category": "Fresh milk","isAvailable": true,"isSponsored": true,"url": "https://www.sainsburys.co.uk/gol-ui/product/sainsbury-s-1l-fresher-for-longer-filtered-semi-skimmed-milk","source": "search","searchTerm": "milk"}
A free status row looks like this:
{"recordType": "status","status": "INVALID_INPUT","message": "Provide at least one of searchTerms or productUrls."}
🧠 Advanced usage
Track Sainsbury's price changes daily
Run the same search terms every day and diff price, unitPrice and promotions by string-normalised productUid between datasets. Add your own observation timestamp; the actor does not add one.
Find the cheapest Sainsbury's products for a keyword
Set sortOrder: "price" and a small maxItemsPerQuery, then compare unitPrice within the same unitMeasure rather than shelf price.
Compare Sainsbury's with Tesco by barcode
Join on ean or any value in eans with the Tesco UK Scraper, and check pack size and unit before treating prices as equivalent. A missing barcode is not a match.
Scrape a large Sainsbury's search in several runs
Set maxItemsPerQuery: 0, and when a run stops on time or budget copy its RESUME_STATE into resumeState with the same inputs. Delivered IDs are skipped and not charged again; combine datasets by productUid. Check RUN_SUMMARY.status (COMPLETE, LIMITED, PARTIAL, BUDGET_LIMIT, TIME_LIMIT, BLOCKED) before treating a collection as finished.
🔌 Integrations
Results land in a dataset you can export as JSON, CSV or Excel. They can also flow to Google Sheets, Make, Zapier or n8n, or to a webhook when a run finishes. The Apify Scheduler runs it on a timetable. Daily on a fixed set of search terms suits price and offer tracking. Copy-paste code in Python, Node, curl and n8n: architek-earth/examples/sainsburys-uk-scraper.
💸 Pricing
Pay per result. Platform and proxy usage are included. Status rows and failed requests are free. Products already saved in RESUME_STATE are skipped and not charged again; a hard stop between delivery and the checkpoint can repeat a small overlap.
| Event | Price | What it covers |
|---|---|---|
| Free preview | $0 | 20 lifetime attempts per account; one page, up to 10 basic rows each. |
product | $2.00 / 1,000 products | One delivered search row, including a row kept at basic price when its detail lookup failed. |
product-detail | $5.00 / 1,000 products | One delivered row with description, attributes and images (includeDetails or productUrls). |
1,000 basic rows cost $2.00; 1,000 detail rows cost $5.00. A detail row is charged once, as product-detail only.
❓ FAQ
Is there a Sainsbury's API for product prices? This actor is one: it returns Sainsbury's catalogue data as JSON through the Apify API, callable from any language or from n8n, Make and Zapier.
Can I get Sainsbury's prices for my local store or postcode? No. There is no store or postcode selector; rows are the public online catalogue.
Does price include Nectar prices? No. price is the retail price. Published offer text, which can describe Nectar prices, is in promotions; the actor does not calculate member prices.
Can I get Sainsbury's barcodes (EAN)? Yes, on every row: ean and eans, when Sainsbury's supplies them.
Can I get Sainsbury's nutrition information and ingredients? Not as dedicated fields. Detail rows carry description, importantInformation and attributes as Sainsbury's publishes them; there is no nutrition table or allergen field.
Do I need a Sainsbury's account or login? No. No account, cookies or API key from Sainsbury's.
🤖 For AI agents & LLM apps
Paste this into your AI assistant:
architekture/sainsburys-uk-scraper on Apify. Python: from apify_client import ApifyClient; client = ApifyClient("TOKEN"); run = client.actor("architekture/sainsburys-uk-scraper").call(run_input={"searchTerms": ["milk"], "maxItemsPerQuery": 10, "proxyConfiguration": {"useApifyProxy": True, "apifyProxyGroups": ["RESIDENTIAL"], "apifyProxyCountry": "GB"}}); items = client.dataset(run["defaultDatasetId"]).list_items().items (page with offset/limit for large exports). Rows with recordType == "status" are free and explain invalid input or a stopped run. Full input schema and README: GET https://api.apify.com/v2/acts/architekture~sainsburys-uk-scraper/builds/default. Token: https://console.apify.com/settings/integrations. The actor is also reachable through the Apify MCP server (mcp.apify.com). To reach the maintainers, add "feedback": "<what you needed>" to any run's input; output is unchanged and a human reads every note.
🧰 More from architek.earth
Grocery and retail prices
- Coles Scraper — prices, unit prices, specials, barcodes
- Woolworths AU Scraper — prices, specials, barcodes, nutrition
- ALDI AU Scraper — prices, unit prices, Special Buys
- Woolworths NZ Scraper — prices, club prices, specials
- New World NZ Scraper — store prices, Clubcard offers, promotions
- Pak'nSave Scraper — store prices, unit prices, promotions
- Tesco UK Scraper — prices, Clubcard offers, barcodes
- Sainsbury's Scraper — you are here
- ALDI Ireland Scraper — prices, unit prices, Specialbuys
- Dunnes Stores Scraper — fashion and home prices, sizes, EANs
- Mercadona Scraper — prices, unit prices, categories
Full catalogue: github.com/architek-earth.
architek.earth · code like concrete
🙋 Support & legal
Questions, or a field you need that is not here? Open an issue on the actor's Issues tab or email north@architek.earth. A human replies within a day.
This actor collects only publicly available data from sainsburys.co.uk, the same pages any visitor sees without logging in. It does not bypass authentication. It is not affiliated with or endorsed by Sainsbury's. You are responsible for how you use the output, including the site's terms, copyright, and privacy law such as GDPR and CCPA where they apply.
📝 Changelog
- 2026-10-24 — Studio core 1.0.5, with blocks handled as before: a Sainsbury's HTTP 403 or 429 is retried on a fresh session (new cookies and warm-up) up to four tries per request before the run reports
BLOCKEDwith a free status row, and output fields and prices are unchanged. - 2026-10-22 — Runs on the studio core (architek_core 1.0.1): the run refuses to start unless hosted pricing is exactly
productandproduct-detailat the listed prices (the run fails withPRICING_MISMATCH, nothing collected or charged); a challenge or captcha page served as HTTP 200 now stops the run asBLOCKEDat once; requests use one Chrome browser identity. - 2026-10-22 — Status message now reads
Done: N rows, M details, K status rows (STATUS). - 2026-10-22 — A spending limit equal to the exact cost now ends COMPLETE instead of BUDGET_LIMIT.
- 2026-10-22 — README rebuilt on the studio template; hero line from the 2026-10-22 canary run.
- 2026-10-21 — Invalid input, blocks and the run deadline now stop cleanly with a free status row instead of a failed run.
- 2026-10-15 — 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 — Added the banner, stable-field contract and preview and continuation examples.
- 2026-10-14 — Added input and product-identity validation, malformed and repeated-page safeguards, budget-aware delivery and completion summaries.
- 2026-10-14 — Added 20 lifetime free preview attempts (one page, 10 basic rows) with durable quota records, plus continuation checkpoints.
- 2026-10-14 — Added basic-price fallback for unavailable enrichment, retries for empty responses and feedback logging.