UK Grocery Price Matrix — Compare Supermarket Prices by EAN avatar

UK Grocery Price Matrix — Compare Supermarket Prices by EAN

Pricing

from $3.00 / 1,000 matrix rows

Go to Apify Store
UK Grocery Price Matrix — Compare Supermarket Prices by EAN

UK Grocery Price Matrix — Compare Supermarket Prices by EAN

Compare grocery prices across Tesco, Sainsbury's, Asda, Morrisons, Waitrose and Aldi UK in one run. Matches products by EAN barcode with a fuzzy-name fallback and flags the cheapest retailer per product.

Pricing

from $3.00 / 1,000 matrix rows

Rating

0.0

(0)

Developer

Studio Amba

Studio Amba

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

3 days ago

Last modified

Categories

Share

UK Grocery Price Matrix

Compare grocery prices across Tesco, Sainsbury's, Asda, Morrisons, Waitrose and Aldi UK in a single run. Give it a product name or an EAN barcode, and it returns one row per product per retailer: the price, any promotion, and whether that retailer carries the product at all.

This actor does not scrape a website itself. It runs our six UK supermarket scrapers in parallel, matches what comes back by EAN barcode (with a fuzzy product-name fallback when a barcode is missing), and builds the comparison matrix from the results.

Why use this actor?

CPG pricing teams, category managers and price-comparison sites need to know one thing constantly: what does this SKU cost at every major UK grocer, right now. Pulling that by hand from six retailer sites is slow and the barcode matching is the annoying part. This actor does the fan-out and the matching in one call and returns a flat table you can pivot in a spreadsheet or load into a pricing dashboard.

What it returns

Each row in the output is one product at one retailer. If a retailer doesn't stock the product, you still get a row for it, marked available: false, so you can tell "we checked and it's not there" apart from "we didn't check."

[
{
"query": "heinz baked beans",
"productName": "Heinz Baked Beans 415g",
"brand": "Heinz",
"ean": "5000157024671",
"retailer": "Tesco",
"price": 1.35,
"promoPrice": null,
"pricePerUnit": "£0.33/100g",
"currency": "GBP",
"available": true,
"url": "https://www.tesco.com/groceries/en-GB/products/...",
"imageUrl": "https://digitalcontent.api.tesco.com/...",
"matchType": "ean",
"cheapestRetailer": "Aldi",
"priceSpreadPct": 22.7,
"absenceReason": null
},
{
"query": "heinz baked beans",
"productName": "Heinz Baked Beans 415g",
"brand": "Heinz",
"ean": "5000157024671",
"retailer": "Aldi",
"price": 1.10,
"promoPrice": null,
"pricePerUnit": "£0.27/100g",
"currency": "GBP",
"available": true,
"url": "https://www.aldi.co.uk/heinz-baked-beans/p/...",
"imageUrl": "https://...",
"matchType": "ean",
"cheapestRetailer": "Aldi",
"priceSpreadPct": 22.7,
"absenceReason": null
},
{
"query": "heinz baked beans",
"productName": "Heinz Baked Beans 415g",
"brand": null,
"ean": "5000157024671",
"retailer": "Waitrose",
"price": null,
"promoPrice": null,
"pricePerUnit": null,
"currency": "GBP",
"available": false,
"url": null,
"imageUrl": null,
"matchType": "ean",
"cheapestRetailer": "Aldi",
"priceSpreadPct": 22.7,
"absenceReason": "not_found"
}
]

Use cases

  • CPG pricing managers tracking how their products are priced across the big six UK grocers, spotting where a retailer is undercutting the recommended shelf price.
  • Price-comparison sites that need a live, EAN-matched feed instead of manually reconciling six product catalogues.
  • Category managers building a competitor price index for a shopping basket of SKUs, refreshed on a schedule.
  • EAN/GTIN matching for any team that has a barcode list and wants to know which UK grocers stock each code and at what price.

How to scrape UK supermarket price data

  1. Enter one or more products in Search Queries, one per line. A product name (heinz baked beans) or an EAN barcode (5000157024671) both work.
  2. Choose which Retailers to include. Leave all six selected for the full matrix, or narrow it down if you only care about a subset.
  3. Set Max Items Per Source to control how many candidate products each retailer contributes per query.
  4. Run the actor. All selected retailers are queried in parallel per query, so adding retailers doesn't multiply the run time.
  5. Open the dataset. Each matched product has one row per retailer: a price row where it's carried, an absence row where it isn't.
  6. Use cheapestRetailer and priceSpreadPct to sort straight to the products with the widest price gaps.

Input

FieldTypeDescription
searchQueriesarrayProduct names or EAN barcodes to price-check. Defaults to ["heinz baked beans"].
retailersarrayWhich of the six retailers to include (tesco, sainsburys, asda, morrisons, waitrose, aldi). Empty means all.
maxItemsPerSourceintegerMax candidate products considered per retailer per query (1-50, default 15).
timeoutPerSourceSecsintegerMax seconds to wait for each retailer scraper before treating it as failed (30-600, default 150).
proxyConfigurationobjectProxy settings passed to the underlying retailer scrapers. UK residential is recommended.

Example input

{
"searchQueries": ["heinz baked beans", "cadbury dairy milk"],
"retailers": ["tesco", "sainsburys", "asda", "morrisons", "waitrose", "aldi"],
"maxItemsPerSource": 15,
"proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"], "apifyProxyCountry": "GB" }
}

Output

FieldTypeDescription
querystringThe search query this row was matched against
productNamestringCanonical product name
brandstringProduct brand, when exposed
eanstringNormalized EAN barcode used for matching, or null
retailerstringTesco, Sainsbury's, Asda, Morrisons, Waitrose or Aldi
pricenumberRegular price in GBP, null on an absence row
promoPricenumberCurrent promotional price, when on offer
pricePerUnitstringRetailer-formatted unit price
currencystringAlways GBP
availablebooleanFalse on an absence row
urlstringProduct page URL at that retailer
imageUrlstringProduct image URL
matchTypestringean (barcode match) or name (fuzzy match)
cheapestRetailerstringRetailer with the lowest price for this product
priceSpreadPctnumberPercentage gap between the highest and lowest price found
absenceReasonstringnot_found (retailer checked, doesn't carry it) or source_failed (that retailer's scraper timed out or errored)

Matching logic

Products are matched across retailers primarily by EAN/GTIN barcode, with leading zeros stripped. When one side has no barcode, the actor falls back to fuzzy name matching: product names are normalized (lowercased, punctuation stripped) and compared by token overlap, with a match requiring at least 60% overlap. matchType on every row tells you which method produced that group.

Search coverage

All six retailers are queried through keyword search (as of the July 2026 update; Asda via its Algolia index, Morrisons via its platform search endpoint, Waitrose via its public product sitemap with full detail-page fetches). absenceReason: "not_in_sample" can still appear in rare fallback situations and deliberately does NOT claim the retailer lacks the product; a true not_found means the retailer's search returned no match.

FAQ

Does this actor scrape the retailer websites itself? No. It calls our own Tesco, Sainsbury's, Asda, Morrisons, Waitrose and Aldi UK scrapers and combines their output. Each of those actors is also available separately on the Store.

Why does Aldi show a price but I can't add it to a basket? Aldi UK doesn't run an online supermarket. Its catalogue is browse-only, so Aldi's price field reflects the current in-store price shown on aldi.co.uk, not something purchasable online. It's still a valid comparison price.

Why does one retailer show absenceReason: "source_failed" on every row? That retailer's scraper timed out or errored for that query. A single failing retailer never blocks the run; it just shows up as failed instead of not_found so you know the difference.

Can I search by EAN barcode instead of product name? Yes. Put the barcode in searchQueries instead of a name. It works reliably on Tesco, Sainsbury's and Aldi (which pass it straight to their own search). On Asda it also works because Asda's output includes EAN data even without a search field. Morrisons and Waitrose don't expose EAN on their catalogue listings, so barcode queries against those two fall back to name matching, which usually won't find anything for a bare number.

How is priceSpreadPct calculated? (highest price - lowest price) / lowest price * 100, using the promo price where one is active, across whichever retailers actually carry the matched product. It's null when only one retailer has the product.

How many products can I check in one run? Add as many lines to searchQueries as you need. Each query launches its own round of retailer calls, so run time scales roughly linearly with the number of queries.

Do I need my own proxy or Bright Data key? No. Proxy and anti-bot handling is managed inside each underlying retailer scraper. Just run this actor with the default settings.

Limitations

  • Matching quality depends on EAN coverage. Retailers that don't expose a barcode (Morrisons, Aldi, and Waitrose unless barcode fetching is enabled on that actor) rely on fuzzy name matching, which can occasionally group similar-but-different pack sizes together.
  • Prices reflect each retailer's standard online delivery pricing at the time of the run and can vary by postcode or in-store.
  • Data is scraped from public retailer websites and may change without notice.

This matrix is built on our standalone UK grocery scrapers, each of which you can also run on its own for a full-catalogue pull:

We run this comparison as a managed service too: scheduled runs, a fixed SKU list, and delivery to your inbox, Google Sheets, or API, maintenance included. See studioamba.dev/services or email hello@studioamba.dev for a free data sample.