Morrisons publishes no barcode on its website. That is the main thing to know if you want to compare its products with other shops. Most price data is joined on the barcode (the EAN). Without one you need another way to say that two rows are the same product.
This post shows how to get Morrisons prices as rows with an Apify Actor I publish, and how the Actor's match fields work. It says how well the matching has been checked, and where it is weak.
Everything below is shelf prices as shown on the shops' websites when the run reads them. They are not a promise of the price in a store.
The Actor is UK Grocery Prices: Tesco, Sainsbury's, Asda, Aldi, Morrisons, published by Yappman Data: https://apify.com/yappman/uk-supermarket-price-scraper
Morrisons became one of its default shops on 9 November 2026, in version 0.2.32. A run with no shop list now reads all five shops.
An example Task that searches baked beans at all five, Morrisons included, is here: https://apify.com/yappman/uk-supermarket-price-scraper/examples/uk-baked-beans-five-shops
Get Morrisons rows
Start with a small run. This input searches one product at Morrisons and Tesco and saves at most 20 rows:
{ "mode": "search", "queries": ["baked beans"], "retailers": ["morrisons", "tesco"], "maxItems": 20 } You get one row per product. The fields that matter for Morrisons:
-
price: the current shelf price in pounds. -
loyaltyPrice: the More Card price, when there is one.pricestays the price any shopper pays. -
wasPriceandpromotionText: other reductions and offers, such asBuy 2 for £4. -
packSizeandunitPrice: the pack size and the price per litre, kilo or each. -
inStock: Morrisons' own availability flag. -
url: the product page. -
scrapedAt: when the row was read.
Items sold by weight, such as loose fruit and meat, are priced for their typical weight. packSize shows it, for example 0.25kg, and the per-kilo figure is in unitPrice.
What it costs
The Actor charges $0.005 for each run and $0.0025 for each product row saved. Those are Free plan prices. Higher Apify plans pay less per product.
The input above costs at most $0.055 ($0.005 plus 20 rows). A 20-row test run across all five shops on 9 November 2026 cost exactly that. A default run, which searches milk at all five shops and saves up to 100 rows, costs at most $0.255.
Set maxItems every time. The Actor also has a catalogue mode that reads the whole Morrisons category tree. A check run on 29 September 2026 returned about 26,000 rows, which would cost about $66 on the Free plan. That is why I keep maxItems small until the first rows look right.
What no barcode means
For Tesco, Sainsbury's and Asda, ean is filled on nearly every row. For Morrisons it is an empty array on every row. So you cannot join Morrisons to another shop on ean.
The Actor gives you three fields for this instead:
-
matchKey: a normalised version of brand, name and pack size. -
matchBasis: which parts went into the key. The values arebrand-name-size,name-size,brand-nameandname. -
matchConfidence: a number from 0 to 1.
Here is a row from the README, a Sainsbury's one, trimmed to the main match fields:
{ "retailer": "sainsburys", "name": "Sainsbury's British Semi Skimmed Milk 2.27L (4 pint)", "packSize": "2.27L", "matchKey": "sainsburys british semi skimmed milk 2.27l 4 pint 4pt", "matchBasis": "brand-name-size", "matchConfidence": 0.97 } matchConfidence is not a score for the row. It is a fixed value for each matchBasis. In the sample above, 0.97 is the value for brand-name-size. The name basis, at 0.50, is an assumed value, not a measured one.
How well it has been checked
I checked matchKey against barcodes on the 4 October 2026 weekly snapshot. Among the pairs of products at different shops that shared a matchKey and could be checked against a barcode, 97.6% were the same product (4,124 of 4,225).
Two limits on that figure:
- It was measured on Tesco, Sainsbury's and Asda rows, the shops that publish a barcode. Morrisons rows use the same per-basis values, but they are unmeasured, because there is no barcode to check against.
- It only counts pairs that the key found.
matchKeyjoins products that are named the same way, so it finds only some of the pairs a barcode would. Shops word the same product differently.
Two products that differ only in something the name leaves out, such as a scent or a flavour, can share a matchKey. matchConfidence does not account for that. Before you rely on a name-only join, check unitPrice and packSize.
Match in a few lines
This joins a run's rows on matchKey and keeps the pairs where one row is Morrisons. It reads a JSON file of rows downloaded from the run.
import json from collections import defaultdict with open("rows.json", encoding="utf-8") as f: rows = json.load(f) by_key = defaultdict(list) for r in rows: if r.get("matchKey"): by_key[r["matchKey"]].append(r) for key, group in by_key.items(): shops = {r["retailer"] for r in group} if "morrisons" in shops and len(shops) > 1: for r in group: unit = r.get("unitPrice") or {} print(r["retailer"], r.get("brand"), r["name"], r["packSize"], unit.get("value"), unit.get("measure"), r["price"]) print() Some shops leave the brand out of the product name, so the snippet prints brand too.
Read the printed pairs before you trust them. Look at packSize and unitPrice first. If two rows have the same key but different pack sizes, one of them is wrong for your purpose.
Things that catch people out
- Two ids for one item. Morrisons sometimes lists the same item twice, under two product ids with slightly different names, for example "British Semi Skimmed Milk 2 Pint" and "... 2 Pints". Both are returned and both are charged. Since 4 November 2026 the
matchKeytreats unit words as singular, so these two give the same key. Check for duplicates before you count products. - The key changed on 4 November 2026. If you join older rows to newer ones, join on
matchKeyV1, which carries the key as it was built before. It is on every row for one release and is then removed, with a notice. Older rows, including the earlier rows of my free daily dataset, have the old key inmatchKeyand nomatchKeyV1. - 50 products per page. A Morrisons listing page carries data for only its first 50 products. Search returns at most 50 products per query. In category mode the run summary has a
listingShortfallcount of the products that were not reached. - Aldi. Aldi also publishes no barcode and sells mostly its own labels. In the 4 October snapshot no Aldi
matchKeyequalled another shop's. For Aldi, compare byunitPrice.
Using the data
If you pay to run the Actor you may show the prices in your own app and keep a copy for up to about a week, as long as you show when the prices were read; you may not resell or pass the data on in bulk; the shops' own content is not ours, and their own terms apply to it. Product images, descriptions and ingredient text belong to the shops, and I do not license them.
Limits
- The Actor reads what Morrisons shows on its groceries website. It does not log in, and it does not read baskets, accounts or delivery slots.
- A run returns that day's prices. It does not compare them with earlier runs.
- The Actor's basket route on the Standby URL is separate, and it does not cover Morrisons.
This is an independent tool. It is not affiliated with or endorsed by Morrisons, Tesco, Sainsbury's, Asda or Aldi.