If you want to watch the same shopping basket week after week, you need three things: a fixed list of products, something that reads their prices on a timer, and somewhere to look at the result. This post shows one way to do all three with an Apify Actor I publish, and a Google Sheet. It also says what each part costs and where it stops.
The prices below are 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
An example of a saved Task for it, searching baked beans at all five shops, is here: https://apify.com/yappman/uk-supermarket-price-scraper/examples/uk-baked-beans-five-shops
Part 1: the list, as a Task
The Actor has a product mode. You give it product page links and it returns one row per product. The shop is worked out from each link, so you do not set the shop list. Here is the shape, using three product pages from the Actor's README:
{
"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"
],
"maxItems": 3,
"outputDataset": "my-weekly-basket"
}
Use real links, copied from the shop's site. For your own basket, replace the three links with your list and set maxItems to the number of links. If you leave maxItems out and call the Actor through the API, a run stops at 100 rows. The Console form starts at 10.
outputDataset is an optional field. It adds every row to a dataset with that name in your account, so each run builds on the last one. Use letters, digits and hyphens only, and start with a letter or digit.
In the Console, save this input as a Task.
Part 2: the schedule
- Open the Task, then Schedules, and add a schedule.
- Pick a time. For Monday at 6am, the cron expression is
0 6 * * 1, with the time zoneEurope/London. - Enable it. A new schedule starts disabled.
- In the Task's run options, set a maximum cost per run a little above what the run should cost.
What a run 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.
For the three-link test above, that is at most $0.0125. For a 20-product basket it is at most $0.055 a run. A product that cannot be read saves no row and is not charged. Rows added to your own named dataset are not charged a second time.
maxItems is a hard cap on rows. Together with the maximum cost per run setting, it means a scheduled job cannot run away on you.
Part 3: the Sheet
Reading your own dataset into a Sheet needs your Apify token, and I have not tested that route, so I will not show it here. To show the Sheet pattern without a token, I use a public dataset I also publish: a fixed basket of 212 staple products, up to three per shop for each of 20 staples, at Tesco, Sainsbury's, Asda and Aldi. It is intended to be added to daily since 24 September 2026, and it needs no account or token.
I tested these steps on 9 November 2026.
- Make a new Google Sheet. Rename the first tab
Raw. The tab name matters, because the formulas below refer to it. - In
Raw!A1, paste this on one line:
=IMPORTDATA("https://api.apify.com/v2/datasets/ynAT9NPps2EdjMOJa/items?format=csv&clean=true&desc=true&limit=1000&fields=retailer,retailerProductId,name,packSize,price,loyaltyPrice,inStock,scrapedAt")
- The cell shows
#REF!with a yellow banner about allowing access to external data. The owner of the Sheet clicks Allow access once. Then the rows load, newest first. - Add a second tab called
Summary. InSummary!A1, paste:
=LEFT(INDEX(Raw!H2:H,1),10)
That gives the date of the newest row.
- In
Summary!A3, paste:
=QUERY({Raw!A2:A, Raw!E2:E, ARRAYFORMULA(LEFT(Raw!H2:H,10))}, "select Col1, count(Col2), avg(Col2) where Col3 = '"&A1&"' and Col2 is not null group by Col1 label count(Col2) 'Items', avg(Col2) 'Average price'", 0)
In my test the date cell showed 2026-11-09. The table had one row for each of the four shops, with item counts of 46 (Aldi), 48 (Asda), 59 (Sainsbury's) and 58 (Tesco), 211 items in all. The list has 212 products, so the count can be one lower when a product has no price that day.
Keeping a history
The Sheet only shows what is in the public dataset's latest rows. To keep your own weekly record, add a History tab. Once a week, copy the Summary rows and paste them as values below the last row. A chart on History then shows the trend.
If you use your own Task from Parts 1 and 2 as the data source, the named dataset already holds every run, so the history lives in the dataset instead of in the Sheet. You read it from https://api.apify.com/v2/datasets/<your-apify-username>~my-weekly-basket/items with your Apify token. Keep the token out of anything you share.
Reading the numbers
The averages in the Summary tab are averages over a fixed list. They are not an inflation measure. Each shop has a different number of items in the list, as the counts show, so do not compare shops on the averages alone. To compare, look at the same product at each shop, joined on the barcode (ean) where the shop publishes one, or on matchKey where it does not.
Some fields are empty by design. Aldi publishes no barcode and no stock level, so inStock is always null for Aldi rows.
Using the data
The public basket dataset is free to use and cite under CC BY 4.0. Credit it as: Source: Yappman Data, UK Supermarket Prices: Daily 212-Item Basket, CC BY 4.0.
For rows from your own runs, a different rule applies. 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
- A run returns the prices a shop shows on its website when the run reads them. It does not log in, and it does not read baskets or accounts.
- Tesco and Sainsbury's are read with no store chosen. Asda and Aldi use one fixed store or service point, so prices in other stores may differ.
- A run does not compare itself with earlier runs. The history comes from the dataset you keep.
- The Sheet recipe depends on the public dataset staying readable without a token and on the
Rawtab name. If the formula shows an error, open the dataset address in a browser to see what it returns.
This is an independent tool. It is not affiliated with or endorsed by Tesco, Sainsbury's, Asda, Aldi or Morrisons.
Top comments (0)