# Foodland, Thrifty Foods, IGA & FreshCo Scraper — Canada (`alduinxx/foodland-thrifty-foods-iga-freshco-scraper`) Actor

Prices, promotions and full nutrition panels for 150,000 products across four Canadian grocery banners. Compare the same SKU between stores.

- **URL**: https://apify.com/alduinxx/foodland-thrifty-foods-iga-freshco-scraper.md
- **Developed by:** [Michael Hill](https://apify.com/alduinxx) (community)
- **Categories:**
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.75 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

Most grocery scrapers give you a name and a price. This one gives you **the regular price, the sale price, and the date the promotion ends** — plus a **16-field nutrition panel** — across four Canadian grocery banners at once.

Foodland, Thrifty Foods, IGA and FreshCo, in one run. About **150,000 products** combined. Export to CSV, Excel, JSON, or pull it straight from the API.

### Why this one

| | Typical grocery scraper | This Actor |
|---|---|---|
| Current price | ✅ | ✅ |
| Regular price when an item is on sale | ❌ | ✅ `regularPrice` |
| When the promotion ends | ❌ | ✅ `priceValidUntil` |
| Full nutrition panel | ❌ | ✅ 16 fields |
| Same product across several stores | ❌ | ✅ shared `sku` |

If you are tracking promotions rather than just prices, that middle block is the difference between "this costs $4.99" and "this is $4.99 until Wednesday, normally $6.79."

### The four banners

| Store | Region | Products |
|---|---|---|
| **Foodland** | Ontario | 43,000 |
| **Thrifty Foods** | British Columbia | 38,000 |
| **IGA** | Quebec | 39,000 |
| **FreshCo** | Discount, national | 33,000 |

All four are Empire Company banners and share one product ID. **The same `sku` identifies the same item in every store**, so a single run gives you the same product priced in Ontario, BC and Quebec side by side — which is exactly the comparison a plain price scraper cannot do.

Sobeys and Safeway are covered by separate Actors and use the same `sku`, so all six line up together.

### What you get

| Field | Example |
|---|---|
| `banner` | Foodland — which store the row came from |
| `name` | Compliments Canola Oil 100% Pure 946 ml |
| `brand` | Compliments (empty for unbranded items) |
| `price` | 6.29 — the current price, promotional if one is running |
| `regularPrice` | 8.49 — the regular price; same as `price` when nothing is on offer |
| `onSale` | true / false |
| `priceValidUntil` | 2026-09-16 — when a promotion ends |
| `currency` | CAD |
| `availability` | InStoreOnly or OutOfStock |
| `category` | Oils, Vinegars & Salad Dressing |
| `categoryPath` | Pantry > Oils, Vinegars & Salad Dressing |
| `sku` | 135163 — the internal Empire ID, shared across banners; not a UPC |
| `imageUrl` | Link to the product image, where one is published |
| `url` | Link to the product page |
| `scrapedAt` | ISO timestamp |

With **Include nutrition panel** on (the default), each row also carries `servingSize`, `calories`, `fat`, `saturatedFat`, `transFat`, `omega3`, `omega6`, `cholesterol`, `sodium`, `carbohydrate`, `fibre`, `sugar`, `protein`, `calcium`, `iron` and `potassium`.

### What people use it for

- **Promotion tracking** — every discounted row carries the regular price and the end date, not just the sale price
- **Regional price comparison** — the same SKU in Ontario, BC and Quebec from one run
- **Discount-banner benchmarking** — FreshCo against the full-service banners, same product IDs
- **Nutrition and diet apps** — a large Canadian food database with real retail prices attached
- **Research** — food price inflation, assortment differences between regions

### How to use it

1. Pick your **Stores**. All four are selected by default.
2. Put one or more words in **Search terms** — `milk`, `bread`, `coffee`. Leave it empty to walk the whole catalogue.
3. Set **Maximum products**. This is the total across *all* selected stores and it is your spend ceiling — the budget is split evenly between them, so 100 products across four stores is about 25 each.
4. Run it. Results appear in the dataset tab and export in any format.

#### Tips

- Search terms are matched against the product name in the URL, so `milk` works and `2% milk 4L` will not.
- Matching is a plain substring test and any term can match, so short terms catch more than you expect — `com` also matches `compliments`.
- Leave the proxy on. These banners block unproxied requests outright.
- If you see failures, lower **Parallel requests**.

### Pricing

**$0.75 per 1,000 products**, plus a $0.00005 start fee that is small enough to ignore. You are charged per product returned — nothing for products that fail, and nothing for the sitemap pages the run reads to find them. A 100-product run costs about $0.08, and 1,000 products about $0.75.

### Notes

- Prices are the catalogue prices each banner publishes on its own product pages. They are not till prices at a particular branch — items are listed `InStoreOnly` or `OutOfStock`.
- **Every product carries a price.** Measured on a 1,000-product random sample across the four banners, 1,000 of 1,000 returned one.
- **About a third of the catalogue is `OutOfStock` at any time**, and it varies by banner — Foodland runs nearest a quarter, Thrifty Foods nearer two in five. Those rows still carry full price and nutrition data.
- **About one product in six is on offer at any time**, ranging from 14% at FreshCo to 22% at Thrifty Foods.
- **Roughly a quarter of products publish no image, and about one in ten no brand.** IGA has the best brand coverage of the four. Missing values come back **empty rather than missing**, so your CSV columns stay stable.
- **About four in five products publish usable nutrition data**, and a filled panel averages roughly half of its sixteen fields.
- Nutrition values come through exactly as published, which means some are numbers (`220`) and some are strings with units (`21 g`). A published `0` is a measured zero, not a gap.
- This reads the structured product data each banner publishes on its own pages for search engines, and only follows URLs listed in their public sitemaps. It does not touch account, cart, or checkout areas, which their robots.txt files disallow.

### Problems or requests

Open an issue on the Actor's Issues tab. Bug reports get fixed and reasonable field requests usually get added.

# Actor input Schema

## `banners` (type: `array`):

Which banners to scrape. Leave all four selected to compare the same product across stores — they share one product ID, so the rows line up.

## `keywords` (type: `array`):

Matched against the product name in the URL, e.g. milk, bread, coffee. Any term can match, and matching is a plain substring test, so short terms catch more than you expect ('com' matches 'compliments'). Leave empty to walk the whole catalogue.

## `maxProducts` (type: `integer`):

Total products across ALL selected stores — this is your spend ceiling, not a per-store figure. The budget is divided evenly between the stores you pick. The four catalogues hold about 150,000 products combined.

## `includeNutrition` (type: `boolean`):

Adds calories, fat, sodium, protein and the rest of the panel where the banner publishes it.

## `maxConcurrency` (type: `integer`):

Lower this if you see failures. Higher is faster but heavier on the site.

## `proxyConfiguration` (type: `object`):

Required. These banners block unproxied requests, so runs without a proxy fail immediately. Leave this on unless you are supplying your own proxy.

## Actor input object example

```json
{
  "banners": [
    "foodland",
    "thriftyfoods",
    "iga",
    "freshco"
  ],
  "keywords": [
    "milk"
  ],
  "maxProducts": 100,
  "includeNutrition": true,
  "maxConcurrency": 10,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `products` (type: `string`):

No description

## `productsCsv` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "banners": [
        "foodland",
        "thriftyfoods",
        "iga",
        "freshco"
    ],
    "keywords": [
        "milk"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("alduinxx/foodland-thrifty-foods-iga-freshco-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "banners": [
        "foodland",
        "thriftyfoods",
        "iga",
        "freshco",
    ],
    "keywords": ["milk"],
}

# Run the Actor and wait for it to finish
run = client.actor("alduinxx/foodland-thrifty-foods-iga-freshco-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "banners": [
    "foodland",
    "thriftyfoods",
    "iga",
    "freshco"
  ],
  "keywords": [
    "milk"
  ]
}' |
apify call alduinxx/foodland-thrifty-foods-iga-freshco-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,alduinxx/foodland-thrifty-foods-iga-freshco-scraper"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/RFyQ0AiyVwnVUei8p/builds/XlNGuISwTEI5BkGt4/openapi.json
