# Canadian Grocery Prices Scraper (`automation-lab/superstore-no-frills-grocery-prices`) Actor

Search current Real Canadian Superstore and No Frills prices by product term, category query, and Canadian store. Export package, sale, unit price, promotion, availability, and source records.

- **URL**: https://apify.com/automation-lab/superstore-no-frills-grocery-prices.md
- **Developed by:** [Stas Persiianenko](https://apify.com/automation-lab) (community)
- **Categories:** E-commerce
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#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

## Canadian Grocery Prices Scraper

Monitor current **Canadian grocery prices** at Real Canadian Superstore and No Frills. Search the public PC Express catalog by product term or category phrase, select a Canadian pickup store, and export typed product, package, price, promotion, and availability records.

Use recurring runs to build store-level basket histories or compare like-for-like products across the two supported banners. Each row includes its query, store, product identity, current and regular price, unit-price text, promotion details, source URL, and collection time.

### What does Canadian Grocery Prices Scraper do?

The Actor queries the structured catalog used by the Real Canadian Superstore and No Frills websites.

It can:

- search one banner for multiple product terms;
- submit category-oriented phrases such as `dairy and eggs`;
- scope results to a retailer store ID or exact Canadian postal code;
- return only products carrying a sale, deal, or loyalty promotion;
- deduplicate products found by more than one query;
- stop at a user-selected maximum result count;
- export results as JSON, CSV, Excel, XML, or through the Apify API.

This is a catalog-data Actor. It does not place orders, access accounts, reserve stock, or create historical price data by itself.

### Who is it for?

- Retail analysts comparing recurring grocery baskets.
- Consumer packaged goods teams checking store-level shelf prices and promotions.
- Procurement teams collecting a repeatable local price snapshot.
- Deal and savings applications looking for current promotion records.
- Data teams feeding Canadian grocery product records into warehouses or spreadsheets.
- Developers building scheduled price-monitoring workflows with Apify.

### Why use this Actor?

#### Store-scoped results

Prices and offers can vary by location. Every run resolves one visible pickup store and includes its ID, name, and address in every product row.

#### Structured price fields

The output separates current, regular, and sale price values while preserving the retailer's unit-price and promotion text.

#### Two Canadian banners

Choose either Real Canadian Superstore or No Frills with the same input and output contract. Run each banner separately when you want a cross-banner comparison.

#### Lightweight API extraction

The Actor uses the retailer's structured catalog service rather than loading product images and browser pages. This keeps the workflow bounded and reproducible.

### What Canadian grocery price data can I extract?

| Field | Description |
| --- | --- |
| `banner` | `superstore` or `nofrills` |
| `storeId` | Retailer pickup-store identifier |
| `storeName` | Display name of the resolved store |
| `storeAddress` | Public address of the resolved store |
| `query` | Search term or category phrase that found the product |
| `queryType` | `search` or `category` |
| `productId` | Stable retailer product identifier |
| `articleNumber` | Retail article number when supplied |
| `brand` | Product brand when supplied |
| `productName` | Product display name |
| `packageSize` | Package quantity or size text |
| `currentPrice` | Current listed price in Canadian dollars |
| `regularPrice` | Previous/regular price when supplied; otherwise current price |
| `salePrice` | Current price when a deal or promotion is detected; otherwise `null` |
| `unitPrice` | Retailer unit-price text, such as `$0.35/100ml` |
| `currency` | `CAD` |
| `isOnSale` | Whether a previous price, deal, loyalty offer, or promotion is present |
| `availability` | Public inventory indicator text |
| `promotion` | Combined public deal and promotion text |
| `promotionExpiry` | Promotion expiry timestamp when supplied |
| `imageUrl` | Public product-image URL when supplied |
| `productUrl` | Product page URL |
| `sourceUrl` | Banner search URL for the originating query |
| `scrapedAt` | UTC collection timestamp |

Nullable fields remain `null` when the source does not provide them. Prices are numeric CAD values; `unitPrice` remains text because the retailer publishes different units and formats.

### How much does it cost to monitor Canadian grocery prices?

This Actor uses pay-per-event pricing:

- **Start:** $0.005 once per run.
- **Product item:** tiered by your Apify plan; the BRONZE tier is $0.005632 per saved product.

Only valid, unique rows saved to the default dataset receive an item charge. Duplicate, filtered, malformed, and failed products are not charged as items.

Example BRONZE prices:

| Saved products | Estimated Actor charge |
| ---: | ---: |
| 5 | $0.0332 |
| 25 | $0.1458 |
| 100 | $0.5682 |
| 500 | $2.8210 |

These examples cover Actor events only. Your Apify plan can also account for platform compute and proxy usage. Actual item pricing depends on your Apify tier; review the live pricing panel before a large run.

### How to scrape Real Canadian Superstore prices

1. Open the Actor input page.
2. Select **Real Canadian Superstore**.
3. Keep store ID `1517` for the provided Vancouver example, or enter another public Superstore pickup-store ID.
4. Add product terms such as `milk`, `eggs`, and `bread`.
5. Set `maxItems` to the number of unique products you need.
6. Click **Start**.
7. Open the default dataset and export the `overview` view.

Example:

```json
{
  "banner": "superstore",
  "storeId": "1517",
  "searchTerms": ["milk", "eggs"],
  "maxItems": 20
}
```

### How to scrape No Frills prices

Select `nofrills` and use a No Frills store ID. Store `3663` is the real store used by the source-specific example.

```json
{
  "banner": "nofrills",
  "storeId": "3663",
  "searchTerms": [],
  "categoryQueries": ["dairy and eggs"],
  "maxItems": 20
}
```

A store ID belongs to one banner. If the ID cannot be resolved for the selected banner, the run fails with a clear validation error rather than returning misleading prices.

### Input parameters

#### `banner`

Choose `superstore` or `nofrills`. The default is `superstore`.

#### `searchTerms`

A list of product, brand, or basket terms. Examples:

- `milk`
- `eggs`
- `chicken breast`
- `President's Choice coffee`

#### `categoryQueries`

A list of category-oriented phrases submitted to catalog search. Examples:

- `dairy and eggs`
- `fresh vegetables`
- `bakery bread`

This mode is useful for category-like discovery, but it is not an exhaustive crawl of a retailer department taxonomy.

#### `storeId`

An optional 3–6 digit public retailer store ID. It takes priority over `postalCode`.

Without a store ID or postal code, the Actor uses a known example store for the selected banner.

#### `postalCode`

An optional Canadian postal code, such as `M6M 0A1`. The Actor selects a visible pickup location whose address has that exact postal code. It does not perform nearest-store distance calculations.

#### `onSaleOnly`

When enabled, save only products with a previous price, deal, loyalty offer, or other promotion signal.

#### `maxItems`

Maximum unique products saved across all queries. The supported range is 1–5,000 and the default is 100.

At least one non-empty `searchTerms` or `categoryQueries` value is required.

### Example output

The following is a shortened record from a real Superstore run:

```json
{
  "banner": "superstore",
  "storeId": "1517",
  "storeName": "Real Canadian Superstore Marine Drive",
  "storeAddress": "350 SE Marine Dr, Vancouver, BC, V5X 2S5",
  "query": "milk",
  "queryType": "search",
  "productId": "20264273_EA",
  "articleNumber": "20264273",
  "brand": "Blue Diamond",
  "productName": "Almond Breeze, Unsweetened Vanilla",
  "packageSize": "946 ml",
  "currentPrice": 2.99,
  "regularPrice": 2.99,
  "salePrice": 2.99,
  "unitPrice": "$0.35/100ml",
  "currency": "CAD",
  "isOnSale": true,
  "availability": "Available",
  "promotion": "Limit 4, after limit $3.29; PC Optimum Points",
  "promotionExpiry": "2026-09-09T00:00:00Z",
  "productUrl": "https://www.realcanadiansuperstore.ca/en/en/almond-breeze-unsweetened-vanilla/p/20264273_EA",
  "sourceUrl": "https://www.realcanadiansuperstore.ca/en/search?search-bar=milk",
  "scrapedAt": "2026-08-17T15:15:54.050Z"
}
```

The source can change product titles, prices, promotion wording, and availability at any time.

### Build a recurring basket-price monitor

A single run is a current snapshot. To build history:

1. Keep banner, store, and search terms stable.
2. Create an Apify Schedule for daily or weekly runs.
3. Save each run's dataset ID and collection timestamp.
4. Join snapshots by `banner`, `storeId`, and `productId`.
5. Compare `currentPrice`, `regularPrice`, `availability`, and `promotion` over time.

A useful sale-basket input is:

```json
{
  "banner": "superstore",
  "storeId": "1517",
  "searchTerms": ["milk", "eggs", "bread", "chicken"],
  "onSaleOnly": true,
  "maxItems": 40
}
```

The Actor does not label a row as “new” or “changed”; your workflow determines changes by comparing snapshots.

### Compare Superstore and No Frills

Run the same terms once for each banner and store. Then join products using identifiers where they match, or use normalized brand, product name, and package size where the banners expose different identifiers.

For defensible comparisons:

- compare similar package sizes;
- retain `storeId` and `scrapedAt`;
- distinguish loyalty promotions from unconditional prices;
- treat textual unit prices carefully when units differ;
- avoid assuming one location represents an entire province or chain.

### Data exports and integrations

The default Apify dataset works with:

- Google Sheets and Microsoft Excel;
- CSV or JSON downloads;
- Make and Zapier automations;
- webhooks after a successful run;
- Snowflake, BigQuery, or another data warehouse;
- Python, JavaScript, and REST API clients.

Common workflows include:

- alerting when a watched product's current price drops;
- tracking weekly basket totals by store;
- checking promoted products for a CPG brand;
- feeding local grocery offers into a deal application;
- comparing current and prior datasets in a notebook.

### Use the Actor through the Apify API

Set `APIFY_TOKEN` as an environment variable rather than committing it to source code.

#### cURL

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/automation-lab~superstore-no-frills-grocery-prices/runs?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"banner":"superstore","storeId":"1517","searchTerms":["milk"],"maxItems":10}'
```

#### JavaScript

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

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor(
  'automation-lab/superstore-no-frills-grocery-prices'
).call({
  banner: 'superstore',
  storeId: '1517',
  searchTerms: ['milk'],
  maxItems: 10,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

#### Python

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor(
    'automation-lab/superstore-no-frills-grocery-prices'
).call(run_input={
    'banner': 'nofrills',
    'storeId': '3663',
    'searchTerms': ['eggs'],
    'maxItems': 10,
})
items = client.dataset(run['defaultDatasetId']).list_items().items
print(items)
```

### Use Canadian Grocery Prices Scraper with MCP

Add the Actor to Claude Code through Apify MCP:

```bash
claude mcp add --transport http apify \
  "https://mcp.apify.com?tools=automation-lab/superstore-no-frills-grocery-prices"
```

#### Claude Desktop setup

Add this server entry to Claude Desktop's MCP configuration.

#### Cursor setup

Add the same server entry in Cursor under **Settings → MCP**.

#### VS Code setup

Add the same server URL through your VS Code MCP extension or workspace MCP configuration.

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=automation-lab/superstore-no-frills-grocery-prices"
    }
  }
}
```

Example prompts:

- “Search the Marine Drive Superstore for milk and eggs and return 20 products.”
- “Collect current No Frills dairy and eggs prices from store 3663.”
- “Find promoted bread and chicken products at Superstore and summarize unit prices.”

### Tips for reliable grocery price datasets

- Use stable store IDs for recurring comparisons.
- Keep the same query list and maximum when comparing snapshots.
- Search specific product families instead of ambiguous one-word terms when precision matters.
- Use `onSaleOnly` for promotion monitoring, not as proof that every row has a lower regular price.
- Store the raw `promotion` text because loyalty conditions and quantity limits matter.
- Keep `productId`, `packageSize`, and `unitPrice` when matching products over time.
- Start with 20–100 items before scheduling a 5,000-item run.

### Limitations

- Only Real Canadian Superstore and No Frills are supported.
- One run is scoped to one banner and one store.
- Category queries use catalog search and are not guaranteed exhaustive department crawls.
- Postal-code selection requires an exact visible pickup-location match.
- The output reflects public catalog data, not checkout totals or guaranteed in-store shelf stock.
- Taxes, deposits, delivery fees, personalized account offers, and basket-level discounts are not calculated.
- Promotion fields follow source wording and may include loyalty or quantity conditions.
- Historical changes require scheduled snapshots and downstream comparison.
- Retailer API or anti-bot changes can temporarily affect runs.

### Failure behavior and troubleshooting

#### “Provide at least one searchTerms or categoryQueries value”

Add at least one non-empty term. Empty arrays do not define a catalog job.

#### “No visible pickup location matched”

Check that the store ID belongs to the selected banner, or use an exact postal code from the banner's public store locator.

#### The dataset has fewer products than `maxItems`

`maxItems` is a ceiling, not a promised count. The query may have fewer unique products, products can overlap across queries, and `onSaleOnly` can filter many rows.

#### The same product appeared under several queries but only once

This is intentional. Rows are deduplicated by banner, store, and source product ID. The first query that accepts the product is retained.

#### Prices differ from a later website visit

Catalog values can change, and stores can differ. Confirm the row's `storeId`, `scrapedAt`, package size, and promotion conditions.

#### A run fails on an upstream response

Retry later before changing your workflow. The Actor retries bounded transient network and server errors, but it fails closed when the response is blocked or no longer has the expected structured product collection.

### Responsible use and legality

This Actor extracts public product-catalog information. Users are responsible for complying with applicable law, Apify's terms, the retailers' terms, and reasonable request volumes.

Do not use the Actor to evade access controls, collect private account information, misrepresent prices, or make automated purchasing decisions without independent verification. Product names, images, and trademarks remain the property of their respective owners.

### Related Automation Lab Actors

- [Approved Food UK Scraper](https://apify.com/automation-lab/approved-food-uk-scraper) for discount grocery products in the United Kingdom.
- [Open Food Facts Scraper](https://apify.com/automation-lab/openfoodfacts-scraper) for open product and nutrition records rather than retailer-specific live prices.
- [USDA FoodData Scraper](https://apify.com/automation-lab/usda-fooddata-scraper) for United States nutrition reference data.

These Actors cover different sources and jobs; they do not substitute for Superstore or No Frills store-level catalog prices.

### FAQ

#### Does this Actor cover all Canadian grocery chains?

No. It supports Real Canadian Superstore and No Frills only.

#### Can it search Canadian grocery prices by province?

You can select stores in different provinces using their public store IDs and compare separate runs. The Actor does not automatically enumerate provinces or claim one store represents province-wide prices.

#### Does it return both regular and sale prices?

It returns the source's current price and previous/regular price when supplied. `salePrice` is populated when a promotion signal is detected. Read `promotion` for loyalty or quantity conditions.

#### Can I use a postal code instead of a store ID?

Yes, when it exactly matches a visible pickup store address. Store ID is more reliable for recurring monitoring.

#### Can I search both banners in one run?

No. Run once per banner so every row has a clear store and banner context.

#### Does it create a historical price database?

No. Schedule recurring runs and retain datasets to build history.

#### Is availability guaranteed at checkout?

No. The field is the public catalog inventory indicator captured at run time.

#### Are duplicate products charged more than once?

No. Accepted products are deduplicated within the run before charging and saving.

#### What happens when the retailer changes its catalog API?

The Actor fails rather than returning an empty success from an unrecognized response. Check run logs and retry later; a persistent source change may require an Actor update.

# Actor input Schema

## `banner` (type: `string`):

Choose Real Canadian Superstore or No Frills.

## `searchTerms` (type: `array`):

Product names or basket terms to search, such as milk, eggs, or chicken breast.

## `categoryQueries` (type: `array`):

Category phrases to submit to the retailer catalog, such as dairy and eggs or fresh vegetables. These are search-based category queries, not exhaustive department crawls.

## `storeId` (type: `string`):

Optional numeric retailer store ID. It takes priority over postal code. Defaults to a known store for the selected banner.

## `postalCode` (type: `string`):

Optional Canadian postal code used to select a pickup store with the exact same postal code, for example M6M 0A1.

## `onSaleOnly` (type: `boolean`):

Return only products with a previous price, deal, loyalty offer, or promotion.

## `maxItems` (type: `integer`):

Stop after this many unique products across all supplied queries.

## Actor input object example

```json
{
  "banner": "superstore",
  "searchTerms": [
    "milk",
    "eggs"
  ],
  "categoryQueries": [],
  "storeId": "1517",
  "onSaleOnly": false,
  "maxItems": 20
}
```

# Actor output Schema

## `overview` (type: `string`):

Open the default dataset overview containing the unique product records saved by this run.

# 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 = {
    "banner": "superstore",
    "searchTerms": [
        "milk",
        "eggs"
    ],
    "storeId": "1517",
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/superstore-no-frills-grocery-prices").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 = {
    "banner": "superstore",
    "searchTerms": [
        "milk",
        "eggs",
    ],
    "storeId": "1517",
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/superstore-no-frills-grocery-prices").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 '{
  "banner": "superstore",
  "searchTerms": [
    "milk",
    "eggs"
  ],
  "storeId": "1517",
  "maxItems": 20
}' |
apify call automation-lab/superstore-no-frills-grocery-prices --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation-lab/superstore-no-frills-grocery-prices"
        }
    }
}

```

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/b4e7PJd8cQ0udI25e/builds/8TJ9ulM1xrJsdODT4/openapi.json
