# Grocery Price Tracker: Build a Basket Index in Python

> Build a grocery price tracker: a fixed basket of Waitrose, Aldi or Walmart products, scraped on a schedule, with unit prices per litre and a basket index.

- URL: https://datatooly.xyz/guides/grocery-price-tracker-scraper/
- Author: Omar Eldeeb (https://datatooly.xyz/about/)
- Published: 2026-10-20
- Updated: 2026-10-20
- Free tool: [E-commerce price tracker — scrape product prices, stock and variants](https://datatooly.xyz/ecommerce-price-tracker/)
- Tags: python, webscraping, datascience, ecommerce

A grocery price tracker is a fixed basket of product pages, scraped on a schedule, with each product's price converted to a unit price (per litre or kg) and the basket's total compared with a base date as an index (base = 100). Keep the basket fixed, compare only products priced on both dates, and record when a product disappears.

This guide builds one in Python on Waitrose and Aldi UK product pages, through an Apify actor, and shows its real output. It also lists which grocers the actor can reach, where unit prices come from, and how a home-made basket relates to official food inflation figures.

## Which grocers you can scrape

From the [E-commerce Product Scraper Pro](https://apify.com/constructive_calm/ecommerce-product-scraper-pro?fpr=v77kxu) supported-sites table:

| Grocer | Status | How pages are reached | Listings and search |
|---|---|---|---|
| Aldi UK (`aldi.co.uk`) | supported | Datacenter | yes |
| Waitrose (`waitrose.com`) | supported | Datacenter | yes, including its "load more" pages |
| Walmart (`walmart.com`) | supported | Datacenter | yes |
| Target (`target.com`) | supported | Datacenter (crawler user agent) | yes |
| Sainsbury's (`sainsburys.co.uk`) | best-effort | Datacenter | first page only |
| Lidl (`lidl.co.uk`) | best-effort | Datacenter | first page only |
| Tesco (`tesco.com`) | best-effort | Residential | best-effort |
| Ocado (`ocado.com`) | best-effort | Residential | first page only |
| Morrisons (`groceries.morrisons.com`) | best-effort | Residential (listings datacenter) | first page only |
| Iceland (`iceland.co.uk`) | best-effort | Browser | best-effort |
| Coles (`coles.com.au`) | best-effort | Residential | best-effort |
| Meijer (`meijer.com`) | best-effort | Datacenter | product URLs only |

Asda, Co-op, Albertsons, Costco and BJ's are listed as **unsupported**: they block automated visitors on every route the actor tested, so their inputs get a status row and aren't fetched. Instacart is covered in the actor's food-delivery section, through category and product URLs.

For a tracker, product URLs beat searches. A search's results change from day to day, while a fixed list of product pages gives you the same basket every time.

## Python: a basket, a history file and an index

The script scrapes a five-item basket, appends the prices to `basket_prices.csv`, and prints the basket cost and an index for every date in the file:

```python
import csv
import os
import re
from datetime import date
from apify_client import ApifyClient

BASKET = [  # url, how many you buy per basket
    ("https://www.waitrose.com/ecom/products/essential-british-free-range-semi-skimmed-milk-4-pints/053457-26759-26760", 1),
    ("https://www.waitrose.com/ecom/products/essential-british-free-range-whole-milk-2-pints/053403-26709-26710", 1),
    ("https://www.waitrose.com/ecom/products/duchy-organic-semi-skimmed-milk-4-pints/053924-27034-27035", 1),
    ("https://www.waitrose.com/ecom/products/essential-british-free-range-semi-skimmed-milk-1-pint/053447-26747-26748", 2),
    ("https://www.aldi.co.uk/product/cowbelle-chocolate-fudge-milk-1-litre-000000000000543237", 1),
]
TO_BASE = {"pint": 0.568261, "pints": 0.568261, "l": 1, "litre": 1, "litres": 1, "ml": 0.001,
           "kg": 1, "g": 0.001}   # imperial pint -> litres; grams -> kg

def pack_size(name):
    """Litres or kg from the product name ("4 Pints", "1 Litre", "500g"); None if absent."""
    m = re.search(r"(\d+(?:\.\d+)?)\s*(pints?|litres?|l|ml|kg|g)\b", name, re.I)
    return round(float(m.group(1)) * TO_BASE[m.group(2).lower()], 3) if m else None

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("constructive_calm/ecommerce-product-scraper-pro").call(run_input={
    "detailsUrls": [{"url": u} for u, _ in BASKET],
    "maxProductResults": len(BASKET),
})
today = date.today().isoformat()
new_rows = []
for p in client.dataset(run["defaultDatasetId"]).iterate_items():
    size = pack_size(p["name"])
    new_rows.append({"date": today, "url": p["inputUrl"], "name": p["name"], "price": p["price"],
                     "currency": p["currency"], "in_stock": p["inStock"], "size": size,
                     "unit_price": round(p["price"] / size, 2) if size and p["price"] else None})

history = "basket_prices.csv"
exists = os.path.exists(history)
with open(history, "a", newline="", encoding="utf-8") as f:
    w = csv.DictWriter(f, fieldnames=list(new_rows[0]))
    if not exists:
        w.writeheader()
    w.writerows(new_rows)

# Basket cost per date, over the items priced on every date, and an index (first date = 100)
rows = list(csv.DictReader(open(history, encoding="utf-8")))
qty = dict(BASKET)
dates = sorted({r["date"] for r in rows})
common = set.intersection(*({r["url"] for r in rows if r["date"] == d and r["price"]} for d in dates))
base = None
for d in dates:
    total = sum(float(r["price"]) * qty[r["url"]] for r in rows if r["date"] == d and r["url"] in common)
    base = base or total
    print(f"{d}  basket £{total:.2f}  index {100 * total / base:.1f}  ({len(common)} items)")
for r in new_rows:
    print(f"  £{r['price']:.2f}  {r['size'] or '?'} l  £{r['unit_price'] or 0:.2f}/l  {r['name']}")
```

`basket_prices.csv` already held the same five products from an actor run on 2026-10-17. Output on 2026-10-20 (`pip install apify-client`, `APIFY_TOKEN` set):

```text
2026-10-17  basket £8.40  index 100.0  (5 items)
2026-10-20  basket £8.40  index 100.0  (5 items)
  £0.95  1.0 l  £0.95/l  Chocolate Fudge Milk 1 Litre
  £1.75  2.273 l  £0.77/l  Essential British Free Range Semi-Skimmed Milk 4 Pints
  £2.65  2.273 l  £1.17/l  Duchy Organic Semi-Skimmed Milk 4 pints
  £1.25  1.137 l  £1.10/l  Essential British Free Range Whole Milk 2 Pints
  £0.90  0.568 l  £1.58/l  Essential British Free Range Semi-Skimmed Milk 1 Pint
```

Nothing moved in three days, which is normal for milk. The useful line is the unit price. The 1-pint bottle costs about twice as much per litre (£1.58) as the 4-pint one (£0.77). That's the comparison a per-item price hides.

This basket is five milks to keep the demo small. A real tracker uses 20 to 50 staples across categories (milk, bread, eggs, pasta, rice, fruit, vegetables, meat, tea, washing-up liquid) with quantities that match what you actually buy.

## Where unit prices come from

Each product row has `unitPrice` and `unitPriceUnit` fields, but they're filled only when the page publishes a unit price in a form the actor reads:

- **Walmart:** yes. A search for "great value whole milk" on 2026-10-20 returned `"price": 3.48, "unitPrice": 0.027, "unitPriceUnit": "fl oz"` for a gallon of Great Value whole milk.
- **Waitrose and Aldi UK:** no, in all our runs (60 Waitrose rows, and 71 Aldi UK rows covering 20 products). That's why the script parses the size from the product name.

Name parsing has limits. Aldi names often omit the size ("Whole Milk 3.6% Fat"), and multipacks ("4 x 500g") need their own rule. When the name has no size, add it to your basket file by hand. A wrong pack size makes a wrong unit price, so check every product once. UK milk is sold in imperial pints, 568 ml each.

## Keep the index honest

1. **Fixed basket, fixed quantities.** If you swap a product, start a new base date or chain the index, as statistics offices do. Don't let the basket change silently.
2. **Compare like with like.** The script only sums products priced on every date. A product that went out of stock or disappeared would otherwise drop out and make the basket look cheaper.
3. **Watch for removed products.** With `monitorMode: true`, a product page that now returns 404 is reported as `removed` in the status dataset. Replace it with the nearest equivalent and note the date.
4. **Promotions vs regular prices.** `originalPrice` is filled when the page shows a was-price. Decide whether your index uses the shelf price (what you'd pay) or the regular price (the underlying level), and keep to it.
5. **Member prices.** Some UK supermarkets show separate prices for loyalty-card members (for example Tesco Clubcard Prices and Sainsbury's Nectar Prices). Check which price your rows hold before mixing stores.
6. **Delivery area.** Online grocers can vary range and availability by delivery area. The actor uses each site's own country for residential exits (`proxyCountry` on Auto) but has no postcode input.

## How your basket relates to official food inflation

Official figures measure a much broader basket with fixed weights. In the UK, the ONS [Consumer price inflation bulletin for August 2026](https://www.ons.gov.uk/economy/inflationandpriceindices/bulletins/consumerpriceinflation/august2026) (released 16 September 2026) says: "The 12-month inflation rate for food and non-alcoholic beverages was 1.3% in August 2026, unchanged from July." The headline CPI rate was 3.1%. The September 2026 bulletin is due on 21 October 2026.

Your basket won't match that number, and it shouldn't. It covers fewer products, one or two stores, and your quantities. Its value is that it's yours: it shows what your shop costs, which store is cheaper per litre or kilo, and when a price moved, days before any monthly statistic.

## Holiday season: start the Christmas basket in November

Christmas food shopping concentrates spending into a few weeks. Seasonal products (turkeys, puddings, party food) appear only for the season, so they can't have a long history. Add them to a separate basket as soon as they're listed and scrape it daily, so you have a few weeks of prices before the last shopping week. Keep the everyday basket running alongside it to see whether the season moves staples too. The [Christmas price tracker](https://datatooly.xyz/guides/christmas-price-tracker/) covers gift prices, and the [holiday sales calendar](https://datatooly.xyz/guides/holiday-sales-calendar-price-tracking/) has the 2026–2027 dates, including Boxing Day.

## What it costs

Pay per event, from the actor's pricing table. Waitrose, Aldi UK, Sainsbury's, Lidl and Walmart are reached over datacenter connections, with no traffic fee:

- **A 50-item basket, daily, FREE tier:** 50 × $0.0048 + $0.005 = $0.245 per run, about $7.35 for 30 days.
- **The same on GOLD:** 50 × $0.0008 + $0.005 = $0.045 per run, $1.35 for 30 days.
- **With `onlyChanges: true`:** unchanged products are billed as "product checked" ($0.0012 FREE, $0.0002 GOLD) and aren't written, so your script must carry the last known price forward. That's 50 × $0.0012 + $0.005 = $0.065 per run on FREE when nothing changed.

Tesco, Ocado and Morrisons product pages go through residential proxies, which add $0.018 (FREE) to $0.014 (GOLD) per MB for items that delivered. The actor hasn't published a measured MB-per-product figure for those three; its README says residential product pages are usually in the 0.1–0.5 MB range. Apify's free plan includes $5 of usage every month, with no credit card required. There's no free trial inside the actor.

## Related guides

- [Competitor price monitoring](https://datatooly.xyz/guides/competitor-price-monitoring-scraper/): the same scheduling pattern for any store.
- [Price drop alerts to email or Slack](https://datatooly.xyz/guides/price-drop-alerts-email-slack/).
- [Walmart and Target price scraper](https://datatooly.xyz/guides/walmart-target-price-scraper/), for US grocery staples.

## Do it without code

The free [e-commerce price tracker builder](https://datatooly.xyz/ecommerce-price-tracker/) turns a list of product URLs into a ready-to-run monitor-mode input for the actor, with a fixed example of the output. It doesn't fetch grocer pages from your browser.

*Disclosure: I built the actor and this site.*

Sources: [ONS, Consumer price inflation, UK: August 2026](https://www.ons.gov.uk/economy/inflationandpriceindices/bulletins/consumerpriceinflation/august2026) (checked 2026-10-20), actor runs on 2026-10-17 and 2026-10-20, and the actor's README (supported-sites table and pricing).

## Quick answers

### How do I track grocery prices over time?

Pick a fixed list of products (the basket) with a quantity for each, scrape their prices on a schedule, append each run to a file, and compare the basket's total with the first run. Only compare products priced on both dates, and change the basket deliberately, not by accident.

### Which UK supermarkets can be scraped?

With the E-commerce Product Scraper Pro actor, Aldi UK and Waitrose are listed as supported, and Sainsbury's, Tesco, Ocado, Morrisons, Lidl UK and Iceland as best-effort (some first page only, some via residential proxies or a browser). Asda and Co-op are listed as unsupported.

### How do I compare grocery prices per litre or per kilo?

Use the unit price the store prints when the scraper captures it (Walmart pages gave a price per fl oz in our runs). Otherwise parse the pack size from the product name, such as '4 Pints' or '500g', convert it to litres or kilograms, and divide the price by it.

### What is UK food inflation right now?

The ONS reported that food and non-alcoholic beverages inflation was 1.3% in the 12 months to August 2026, unchanged from July (bulletin released 16 September 2026). The September 2026 figures are due on 21 October 2026, so check the ONS bulletin for the latest month.

## Related guides

- [Retail Sales Calendar 2026-2027: When to Track Prices](https://datatooly.xyz/guides/holiday-sales-calendar-price-tracking/)
- [Amazon Price History Without Keepa: Build Your Own](https://datatooly.xyz/guides/amazon-price-history-without-keepa/)
- [Amazon Price Scraper: Prices by ASIN on Any Marketplace](https://datatooly.xyz/guides/amazon-price-scraper-asin/)
- [Back in Stock Alert Scraper: Products, Sizes, Colours](https://datatooly.xyz/guides/back-in-stock-alert-scraper/)
- [Back to School Price Tracker: Tax Holidays & Laptops](https://datatooly.xyz/guides/back-to-school-price-tracker/)
- [Compare Black Friday Prices Across Stores (Google Shopping)](https://datatooly.xyz/guides/black-friday-google-shopping-price-comparison/)
- [Black Friday Price Tracker: Watch 2026 Prices Daily](https://datatooly.xyz/guides/black-friday-price-tracker/)
- [Boxing Day Sales Tracker UK 2026: Prices in GBP](https://datatooly.xyz/guides/boxing-day-sales-price-tracker-uk/)
- [Christmas Price Tracker: When to Buy Gifts Cheapest](https://datatooly.xyz/guides/christmas-price-tracker/)
- [Competitor Price Monitoring: Scraper Setup and Cost](https://datatooly.xyz/guides/competitor-price-monitoring-scraper/)
- [Cyber Monday Price Tracker: Hourly Checks for Nov 30](https://datatooly.xyz/guides/cyber-monday-deal-tracker/)
- [MAP Monitoring: Find Minimum Advertised Price Violations](https://datatooly.xyz/guides/map-price-monitoring/)
- [Price Drop Alerts to Slack or Email with a Scraper](https://datatooly.xyz/guides/price-drop-alerts-email-slack/)
- [Prime Day Price Tracker: ASINs, Big Deal Days, 2027](https://datatooly.xyz/guides/prime-day-price-tracker/)
- [Ramadan Sale Price Tracker 2027: Noon & Amazon UAE](https://datatooly.xyz/guides/ramadan-eid-sales-price-tracking/)
- [Scrape Product Prices with Python from JSON-LD](https://datatooly.xyz/guides/scrape-product-prices-json-ld-python/)
- [Scrape Product Reviews from Walmart, Target and Amazon](https://datatooly.xyz/guides/scrape-product-reviews-walmart-target-amazon/)
- [Scrape Product Variants: Sizes, Colors and Price per SKU](https://datatooly.xyz/guides/scrape-product-variants-sizes-colors/)
- [Fake Black Friday Deals: Check the Real Discount](https://datatooly.xyz/guides/spot-fake-black-friday-discounts/)
- [Apify E-commerce Scraping Tool Alternative: Same Input](https://datatooly.xyz/guides/switch-from-apify-ecommerce-scraping-tool/)
- [When Do Black Friday Prices Drop? The 2026 Timeline](https://datatooly.xyz/guides/thanksgiving-black-friday-price-drop-timeline/)
- [Walmart and Target Price Scraper: Search and Product Pages](https://datatooly.xyz/guides/walmart-target-price-scraper/)
- [White Friday & 11.11 Tracker: Noon and Amazon UAE Prices](https://datatooly.xyz/guides/white-friday-11-11-noon-amazon-uae-prices/)
- [Shopify products.json Pagination: 250 Limit and the 25,000 Cap](https://datatooly.xyz/guides/shopify-products-json-pagination/)
- [AI Share of Voice: Measure It in ChatGPT and Perplexity](https://datatooly.xyz/guides/ai-share-of-voice-chatgpt-perplexity/)
