UK Grocery Price Matrix — Compare Supermarket Prices by EAN
Pricing
from $3.00 / 1,000 matrix rows
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
Maintained by CommunityActor 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
- Enter one or more products in Search Queries, one per line. A product name (
heinz baked beans) or an EAN barcode (5000157024671) both work. - Choose which Retailers to include. Leave all six selected for the full matrix, or narrow it down if you only care about a subset.
- Set Max Items Per Source to control how many candidate products each retailer contributes per query.
- Run the actor. All selected retailers are queried in parallel per query, so adding retailers doesn't multiply the run time.
- 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.
- Use
cheapestRetailerandpriceSpreadPctto sort straight to the products with the widest price gaps.
Input
| Field | Type | Description |
|---|---|---|
searchQueries | array | Product names or EAN barcodes to price-check. Defaults to ["heinz baked beans"]. |
retailers | array | Which of the six retailers to include (tesco, sainsburys, asda, morrisons, waitrose, aldi). Empty means all. |
maxItemsPerSource | integer | Max candidate products considered per retailer per query (1-50, default 15). |
timeoutPerSourceSecs | integer | Max seconds to wait for each retailer scraper before treating it as failed (30-600, default 150). |
proxyConfiguration | object | Proxy 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
| Field | Type | Description |
|---|---|---|
query | string | The search query this row was matched against |
productName | string | Canonical product name |
brand | string | Product brand, when exposed |
ean | string | Normalized EAN barcode used for matching, or null |
retailer | string | Tesco, Sainsbury's, Asda, Morrisons, Waitrose or Aldi |
price | number | Regular price in GBP, null on an absence row |
promoPrice | number | Current promotional price, when on offer |
pricePerUnit | string | Retailer-formatted unit price |
currency | string | Always GBP |
available | boolean | False on an absence row |
url | string | Product page URL at that retailer |
imageUrl | string | Product image URL |
matchType | string | ean (barcode match) or name (fuzzy match) |
cheapestRetailer | string | Retailer with the lowest price for this product |
priceSpreadPct | number | Percentage gap between the highest and lowest price found |
absenceReason | string | not_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.
Related actors
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.