UK Supermarket Price Scraper: Tesco, Sainsbury's, Asda, Aldi avatar

UK Supermarket Price Scraper: Tesco, Sainsbury's, Asda, Aldi

Pricing

from $1.50 / 1,000 products

Go to Apify Store
UK Supermarket Price Scraper: Tesco, Sainsbury's, Asda, Aldi

UK Supermarket Price Scraper: Tesco, Sainsbury's, Asda, Aldi

Current shelf prices from Tesco, Aldi, Sainsbury's and Asda in one schema, with EAN barcodes, Clubcard and Nectar prices and promotions. Search, category or product URLs. Pay only per product returned.

Pricing

from $1.50 / 1,000 products

Rating

0.0

(0)

Developer

Russell Yapp

Russell Yapp

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

9 hours ago

Last modified

Categories

Share

UK Supermarket Price Scraper: Tesco, Sainsbury's, Asda and Aldi prices API

This is a UK grocery prices API returning current shelf prices from Tesco, Sainsbury's, Asda and Aldi in one schema, as structured data, searched by keyword, by retailer category, or by product page, and charged only for the products you get back. You get price, Clubcard and Nectar loyalty prices, an EAN barcode and stock per product, for price-comparison tools, competitor monitoring, inflation tracking and AI shopping agents.

Sample output row

{
"retailer": "sainsburys",
"retailerProductId": "3449370",
"ean": ["5000119000201"],
"matchKey": "sainsburys british semi skimmed milk 2.27l 4 pint 2270ml",
"name": "Sainsbury's British Semi Skimmed Milk 2.27L (4 pint)",
"brand": "Sainsbury's",
"packSize": "2.27L",
"price": 1.65,
"wasPrice": null,
"unitPrice": { "value": 0.73, "measure": "l" },
"loyaltyPrice": null,
"promotionText": null,
"promotionEndsAt": null,
"inStock": true,
"categoryPath": ["Dairy, eggs & chilled", "Milk & cream", "Fresh milk"],
"url": "https://www.sainsburys.co.uk/groceries/product/sainsburys-british-semi-skimmed-milk-2-27l-4-pint",
"imageUrl": "https://assets.sainsburys-groceries.co.uk/gol/3449370/image.jpg",
"rating": { "value": 4.7, "count": 1543 },
"scrapedAt": "2026-09-23T18:05:00Z"
}

The EAN, matchKey and ids in this sample are illustrative, not real.

What you get: the UK supermarket prices dataset

RetailerModesBarcode (EAN) coverageLoyalty / promotion fieldsStock
Tescosearch, category, productOn every row we have seen — Tesco's own product API returns a GTIN for every productloyaltyPrice carries single-item Clubcard prices, e.g. £1.60 Clubcard Price; multibuy Clubcard offers and meal deals go in promotionText only ( wasPrice is always null)Tesco's own online-availability flag (national, not per store)
Sainsbury'ssearch, category, productNearly every row — EANs come from Sainsbury's own product data (codes that fail the check digit are dropped)loyaltyPrice carries the Nectar price ( price stays the regular shelf price); promotionText for other offers, e.g. 2 for £3Sainsbury's own availability flag
Asdasearch, category, productAbout 97% of rows — recovered from Asda's product image id and validated as a GTIN; the rest have nonewasPrice carries Rollback was-prices; promotionText carries multibuys, e.g. Any 2 for £4Per store, set by the asdaStoreId input (default 4565); prices themselves are national
Aldisearch, category, productNone — Aldi publishes no barcode anywhere, so rows carry an empty ean and join on matchKeywasPrice on in-store markdowns; no loyalty priceNot published — inStock is always null

Every row also carries matchKey, a normalised brand + name + pack size, so you can join Aldi products to the others even without a barcode.

This Actor is not affiliated with, endorsed by, or partnered with Aldi, Sainsbury's, Asda or Tesco. Retailer names are used only to describe the public product data returned.

Free daily dataset: UK grocery price index

A fixed basket of 212 staple products, up to three products per retailer from Tesco, Sainsbury's, Asda and Aldi for each of 20 staples (milk, bread, eggs, butter, cheddar, chicken breast, bananas, apples, potatoes, onions, tomatoes, pasta, rice, baked beans, cereal, coffee, tea bags, sugar, orange juice, olive oil). This Actor fetches the basket every morning in product mode and appends the rows to one dataset, so the same products build a price history from 24 September 2026. Rows use the same schema as the Actor's own output, including ean, matchKey, loyaltyPrice and scrapedAt; filter or group on scrapedAt for a given day's snapshot.

  • JSON: https://api.apify.com/v2/datasets/ynAT9NPps2EdjMOJa/items?signature=MC4xNzkxNDcwNDMyMzg2LjEwV244Nll3OGZYYThIeTN6UkhENA
  • CSV: the same URL with &format=csv appended

No account or token needed. Apify's dataset items API limit, offset and format query parameters all apply, for example &format=csv&limit=1000.

Free to use and cite; attribution requested as "UK Supermarket Price Scraper (Apify), yappman/uk-supermarket-price-scraper". Prices are national shelf prices as published online by each retailer, collected once a day, provided as is; this Actor is not affiliated with the retailers.

Need other products, categories or intraday prices? Run the Actor yourself with your own queries, categoryUrls or productUrls.

Tesco, Sainsbury's, Asda and Aldi: what each retailer exposes

Tesco. There is no public Tesco API for prices, so this Tesco price scraper is the nearest thing: it reads Tesco's own product API and returns Tesco prices, the single-item Clubcard price and an EAN barcode on every row we have seen. Multibuy and meal-deal Clubcard offers appear only in promotionText, and wasPrice is always null for Tesco. Tesco prices and stock (inStock) are both national; there is no store selection for Tesco.

Sainsbury's. This Sainsbury's price scraper reads Sainsbury's API for product data and returns the regular shelf price alongside the Nectar price, with an EAN on nearly every row (codes that fail the check digit are dropped). loyaltyPrice carries that loyalty price while price stays the regular shelf price; other offers, such as 2 for £3, go in promotionText. Sainsbury's own availability flag sets inStock.

Asda. This Asda price scraper is also an Asda price tracker for Rollback deals: wasPrice carries the previous price whenever a Rollback reduction is shown, and promotionText carries multibuys such as Any 2 for £4. An EAN barcode is recovered from Asda's product image id on about 97% of rows; the rest carry none. Stock (inStock) is set per store by the asdaStoreId input (default 4565), though Asda's prices themselves are national.

Aldi. This Aldi price scraper returns Aldi's own in-store prices exactly as published; Aldi exposes no barcode anywhere in its listing API, product API or product pages, so rows carry an empty ean array. Instead, every row carries matchKey, a normalised brand, name and pack size, so Aldi products join to the other three retailers even without a barcode. Aldi does not publish stock online, so inStock is always null for Aldi rows.

Grocery price tracking and other use cases

  • Price-comparison apps and deal-tracking channels
  • Competitor and retail-analytics price monitoring
  • Inflation trackers and academic basket-of-goods research
  • AI agents doing UK grocery price or basket lookups

Input reference

FieldTypeDefaultNotes
retailersarray of tesco / sainsburys / asda / aldi["aldi","sainsburys"]Which retailers to search. Each runs as its own adapter. Ignored in category and product mode, where the retailer is inferred from each URL
modesearch / category / productsearchOne mode per run
queriesarray of strings["milk"]Search mode; each query runs against every selected retailer
categoryUrlsarray of URLs[]Category mode, up to 250 URLs per run; the retailer is inferred from each URL's hostname. Tesco: /shop/en-GB/browse/<department>/<aisle>/... as copied from the site, including the /all form (the older /groceries/en-GB/shop/... form works too). Aldi: /products/.../k/<id>. Sainsbury's: /groceries/browse/.../c:<id> (or the legacy /gol-ui/groceries/... form). Asda: /groceries/<category>[/<department>/<aisle>/<shelf>], including top-level pages like /groceries/food-cupboard
productUrlsarray of URLs[]Product mode; retailer inferred from each URL's hostname
maxItemsinteger100Hard cap on rows pushed across all retailers; 0 = no cap, bounded only by mode
asdaStoreIdstring4565Asda store used for stock (inStock). Leave blank for store 4565, the store asda.com uses before a postcode is entered. Asda prices are national, so this does not change prices
outputDatasetstring(empty)Advanced: also append every row to this named dataset in your account (created on first use), so repeated runs build history in one place. Rows are charged once, on the run's own dataset. Letters, digits and hyphens only
datasetNamestring(empty)Advanced: at the end of a successful run, name the run's own dataset with this name in your account, so it never expires and is easy to find. Placeholders: {isoWeek} (ISO week of the run start, UTC, e.g. 2026-W40), {date} (YYYY-MM-DD) and {retailer} (the retailer when exactly one ran, otherwise multi). Letters, digits and hyphens only after expansion, max 63 characters, starting with a letter or digit. Skipped with a warning if the name is already taken, nothing was pushed, the run stopped early (spend cap, a failed retailer) or the run is outside the platform, so a retry can take the name

Example inputs

Search three retailers for two terms, capped at 300 rows:

{
"retailers": ["aldi", "sainsburys", "asda"],
"mode": "search",
"queries": ["milk", "cheddar"],
"maxItems": 300
}

One Asda top-level category:

{
"mode": "category",
"categoryUrls": ["https://www.asda.com/groceries/food-cupboard"],
"maxItems": 500
}

Specific product pages:

{
"mode": "product",
"productUrls": [
"https://www.tesco.com/shop/en-GB/products/257265436",
"https://www.sainsburys.co.uk/groceries/product/cathedral-city-mature-cheddar-cheese-350g",
"https://www.aldi.co.uk/product/cowbelle-chocolate-milk-000000000000543221"
]
}

Weekly category snapshot of one retailer, named by ISO week:

{
"mode": "category",
"categoryUrls": ["https://www.tesco.com/shop/en-GB/browse/fresh-food/milk-butter-and-eggs/milk/all"],
"maxItems": 0,
"datasetName": "snapshot-{retailer}-{isoWeek}"
}

A run started on 4 October 2026 would name this run's dataset snapshot-tesco-2026-W40.

Output field reference

One row per product per retailer, same shape for all four.

FieldTypeNotes
retailerstringtesco / sainsburys / asda / aldi
retailerProductIdstringThe retailer's own id (Tesco tpnc, Sainsbury's product_uid, Asda item id, Aldi SKU)
eanarray of stringsCheck-digit-validated GTIN-13 strings; may hold several for multipacks; empty where the retailer exposes none (Aldi, and about 3% of Asda rows)
matchKeystringNormalised brand + name + pack size. The join key when ean is empty, and the recommended way to match the same product across retailers
namestringProduct name as shown on site
brandstring or nullOwn-label rows carry the retailer's name
packSizestring or nulle.g. 4x415g, 2.27L
pricenumberGBP, current shelf price
wasPricenumber or nullPrevious price when a reduction is shown
unitPriceobject or null{ "value": 0.99, "measure": "kg" }
loyaltyPricenumber or nullSingle-item Tesco loyalty price (Clubcard) or Sainsbury's loyalty price (Nectar); Asda and Aldi leave this null
promotionTextstring or nulle.g. 2 for £3, Any 2 for £4
promotionEndsAtdate or nullISO 8601 when the site exposes it
inStockboolean or nullnull where the retailer does not report stock online (always null for Aldi)
categoryPatharray of stringse.g. ["Dairy", "Milk", "Semi-skimmed"]
urlstringProduct page
imageUrlstring or nullLargest image available
ratingobject or null{ "value": 4.6, "count": 212 }
scrapedAtstringISO 8601, UTC

Pricing

Pay per event; platform usage is included in that price, so this is everything you pay:

  • $0.005 once per run (start fee)
  • $2.50 per 1,000 products returned ($0.0025 each) on the Free plan — discounts on higher Apify plans

Worked example. A run searching 5 terms across all four retailers that returns 300 products costs 300 × $0.0025 + $0.005 = $0.755.

Cap spend with Apify's maximum-charge setting on the run — the Actor stops pushing rows once the cap is reached — and/or with the maxItems input.

Limitations

  • Aldi prices are the in-store reference prices Aldi publishes online; Aldi reports no stock and no barcode for any product.
  • Asda stock (inStock) is for a single store, set by asdaStoreId; Asda prices themselves are national.
  • Sainsbury's rows the retailer itself lists at £0.00 are skipped rather than returned with a fake price.
  • Very large Asda categories are capped by Asda's own search at 1,000 products per department; the Actor splits these automatically, but extremely broad top-level categories may still not return every product.
  • Public, logged-out data only: no basket, account or delivery-slot data, and no Nectar or Asda Rewards account is used.
  • Prices can change during the day; treat scrapedAt as the price's timestamp.
  • Tesco search results include marketplace-seller and F&F clothing listings; the Actor skips both and returns only Tesco grocery products.
  • Tesco prices and stock (inStock) are both national; there is no store selection for Tesco.

FAQ

Is there a Tesco API for prices? No. Tesco does not publish one; this Tesco price scraper is the nearest thing, reading Tesco's own product API since v0.2, in search, category and product mode. It returns an EAN on every row we have seen, and single-item loyalty pricing in loyaltyPrice (multibuy and meal-deal offers appear in promotionText instead).

Does it return Clubcard and Nectar prices? Yes. loyaltyPrice carries the single-item Clubcard and Nectar prices for Tesco and Sainsbury's; Asda and Aldi leave the field null.

Can I track Asda Rollback prices? Yes. wasPrice carries the was-price whenever a reduction is shown, and promotionText carries multibuys such as Any 2 for £4.

Does it include Aldi prices? Yes, in search, category and product mode, exactly as Aldi publishes them online: no barcode, so join on matchKey instead of ean.

Why do Aldi rows have no EAN? Aldi does not publish a barcode anywhere in its listing API, product API or product pages. Aldi rows carry an empty ean array and a matchKey (normalised brand + name + pack size) instead.

How do I match the same product across retailers? Use ean where present (Sainsbury's on nearly every row, Asda on about 97%). Where it's missing, or to match Aldi products to the others, use matchKey.

Can I build a supermarket price comparison with it? Yes. Match rows on ean (or matchKey for Aldi) across Tesco, Sainsbury's, Asda and Aldi to build supermarket price comparison data in one schema.

Which Asda store's stock does this use? Whichever store id you set in asdaStoreId (default 4565, the store asda.com uses before a postcode is entered). Asda's prices are national and don't change with the store.

How do I limit what I spend? Set Apify's maximum-charge cap on the run, and/or set maxItems to cap the number of rows returned.

How fresh is the data? Every row is scraped live during the run; scrapedAt records when. Prices can move during the day, so treat each row as a snapshot.

Is there a free sample or historical data? Yes. See Free daily dataset: UK grocery price index above: a fixed 212-product basket across all four retailers, fetched daily since 24 September 2026, no account or token needed.

Does this use my Sainsbury's or Asda account? No. The Actor only reads public, logged-out product data — no login, basket or account information.

After a successful run, please leave a review on this Actor's Store page.

Changelog

See ./CHANGELOG.md for version history.

For developers

python -m venv .venv
.venv/Scripts/python -m pip install -r requirements-dev.txt
.venv/Scripts/python -m pytest
apify run