# Metro Grocery Scraper API - Quebec & Ontario Prices (`alduinxx/metro-grocery-scraper`) Actor

Prices, sale end dates, multi-buy offers, unit prices and barcodes for Metro (metro.ca) groceries in Quebec and Ontario. Search by keyword or barcode.

- **URL**: https://apify.com/alduinxx/metro-grocery-scraper.md
- **Developed by:** [Michael Hill](https://apify.com/alduinxx) (community)
- **Stats:** 2 total users, 1 monthly users, 95.7% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $6.00 / 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?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# 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 sale ends**, **multi-buy offers** ("2 / $8.00"), the **unit price** and the **barcode**, for **Metro**, one of Quebec's and Ontario's biggest grocery chains.

It reads metro.ca's own published catalogue for Quebec and Ontario. Search by keyword, give it a list of barcodes to monitor a fixed basket, or walk the whole catalogue. Export to CSV, Excel or 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 sale ends | ❌ | ✅ `priceValidUntil` |
| Multi-buy offers ("2 / $8.00") | ❌ | ✅ `multiBuyQuantity`, `multiBuyPrice`, `multiBuyUnitPrice` |
| Unit price ("$1.75 /100g") | rarely | ✅ `unitPrice`, `unitPriceUnit` |
| Barcode you can join on | rarely | ✅ `gtin` (UPC/EAN) |
| Taxable flag | ❌ | ✅ `taxable` |
| Ingredients and dietary labels | ❌ | ✅ `ingredients`, `dietaryLabels` |

If you are tracking promotions rather than just prices, the difference is "this costs $3.49" versus "this is $3.49 until 4 November, normally $3.99."

**The barcode is the join key.** For packaged goods, Metro's product number is the product's UPC or EAN, so rows line up with any other source that carries barcodes.

### What you get

| Field | Example |
|---|---|
| `sku` | 014100170952: Metro's product number |
| `gtin` | 014100170952: the barcode (empty for produce and counter items, which use short internal codes) |
| `name` | Baked Cheddar Crackers |
| `brand` | Goldfish |
| `size` | 200 g |
| `price` | 3.49: the price of **one** item now, the sale price if a sale is on |
| `regularPrice` | 3.99: the regular price; same as `price` when nothing is on sale |
| `onSale` | true / false |
| `priceValidUntil` | 2026-11-04: when the sale ends |
| `multiBuyQuantity` | 2: how many you must buy for the multi-buy price; empty when there is none |
| `multiBuyPrice` | 8.00: what that many cost together |
| `multiBuyUnitPrice` | 4.00: the price per item when you buy that many |
| `multiBuyValidUntil` | 2026-10-07: when the multi-buy ends |
| `unitPrice`, `unitPriceUnit` | 1.75, 100g: the unit price Metro shows next to its headline price |
| `taxable` | true when Metro marks the price "+tx" |
| `currency` | CAD |
| `availableOnline` | false when Metro lists the product but does not sell it online |
| `soldByWeight`, `alcohol` | true / false |
| `department` | Snacks |
| `category`, `categoryPath` | crackers · snacks > salty snacks > crackers |
| `dietaryLabels` | \["Organic", "Non GMO", "Fairtrade"]: Metro's "My Health My Choices" labels |
| `ingredients` | Enriched wheat flour (niacin, iron, …) |
| `description` | The product description |
| `imageUrl` | Link to the product image |
| `storeId`, `storeName`, `storeCity`, `storeProvince` | 389 · Metro Niagara Falls · Niagara Falls · ON: the store this price is from (see Notes) |
| `url` | Link to the product page |
| `scrapedAt` | ISO timestamp |

Every row has every key; values Metro does not publish come back empty rather than missing.

### What people use it for

- **Price and promotion monitoring**: regular price, sale price and sale end dates, week to week
- **Price comparison apps**: Quebec and Ontario coverage, with barcodes to match products across stores
- **CPG and brand teams**: published shelf price, promotions and online availability for your own products, store by store
- **Research**: food prices over time, promotion depth and frequency, multi-buy use

### How to use it

1. Put one or more words in **Search terms**, such as `milk`, `coffee` or `chips`. Or put barcodes or product URLs in **Product codes** to fetch exactly those products.
2. Choose **Provinces**: Quebec, Ontario, or both. A product sold in both comes back once.
3. Set **Maximum products**.
4. Run it, then download the dataset.

#### Input example

```json
{
    "keywords": ["coffee", "milk"],
    "provinces": ["QC"],
    "maxProducts": 200
}
```

#### Monitoring a fixed basket

```json
{
    "productCodes": ["014100170952", "066721026590", "060410054406"]
}
```

### Notes

- **Which products it can find.** Search terms and bare codes are looked up in metro.ca's published catalogue (its sitemaps). Some of metro.ca's servers hand out copies of that catalogue that are almost two years old, so the Actor checks each copy's date and asks again until it gets the current one. If it cannot, it uses the newest copy it got and says so in the log. **Product URLs** in **Product codes** are read directly, without the catalogue.
- **Search terms match the product name only.** They are matched against the product name in the URL, not the aisle, so `milk` does not return every product in the dairy aisle. Matching is a plain substring test: `tea` also matches `steak`.
- **Multi-buys.** When an item is on a multi-buy such as "2 / $8.00", `price` is still the price of **one** item (the regular price), and the offer is in the `multiBuy*` fields. Metro's own structured data lists the group total ($8.00) as the promotional price; this Actor does not report it as the price of one item.
- **Unit price** is the one Metro displays next to its headline price. On a multi-buy, that is the unit price at the multi-buy price.
- **Whose price is this? Every row says.** metro.ca prices each product page at one store, the one nearest the visitor's location, and names it on the page. This Actor reads metro.ca through proxies in many locations, so **rows in one run can come from different stores**, and `storeId`, `storeName`, `storeCity` and `storeProvince` tell you which. Prices can differ between stores: a product can be cheaper in Ontario than in Quebec, or sold online in one province only. **You cannot choose the store.** Every run saves Metro's full store list (312 stores) as **STORES** under the Storage tab.
- **Products without a price.** When a product is not sold online at the store a page was priced at, Metro usually shows no price. Such pages are skipped, and no result fee is charged for them. Turn on **Include products without a price** to get them with an empty price and `availableOnline: false`.
- **Discontinued products.** metro.ca's catalogue still lists some products it has removed. Their pages redirect to the aisle or show "Page Not Found"; they are skipped, with no result fee.
- **Nutrition panels** are not published on metro.ca product pages as text, so this Actor has no nutrition fields.
- **English catalogue.** Product names and descriptions are in English.
- **Speed.** metro.ca is behind bot protection, so this Actor reads product pages through Apify's Unblocker proxy. Runs are slower than a plain scraper; failed attempts are retried automatically.

### Pricing

**$6.00 per 1,000 products returned**, and nothing else to work out: platform usage, including the Unblocker proxy that metro.ca requires, is included. Each run also has a start fee of $0.00005.

Skipped products (no price, discontinued, or blocked after retries) are free. **Maximum products** caps what a run costs: 100 products cost $0.60. If you set a maximum cost per run, the Actor stops when it is reached and keeps the products found so far.

### Related Actors

The same structured approach for Canada's other big grocer, Sobeys (Empire Company), and its banners:

- [Sobeys Grocery Scraper](https://apify.com/alduinxx/sobeys-grocery-scraper)
- [Safeway Canada Grocery Scraper](https://apify.com/alduinxx/safeway-canada-grocery-scraper)
- [Foodland, Thrifty Foods, IGA & FreshCo Scraper](https://apify.com/alduinxx/foodland-thrifty-foods-iga-freshco-scraper): IGA is Metro's main rival in Quebec

### Feedback

Found a product that looks wrong, or need a field that is missing? Open an issue on the **Issues** tab with the product URL, and it will be looked at.

# Actor input Schema

## `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 ('tea' matches 'steak'). Leave empty to walk the whole catalogue.

## `productCodes` (type: `array`):

Instead of search terms, the exact products to fetch: the number at the end of a metro.ca product URL (for packaged goods it is the barcode, e.g. 014100170952), or the product URL itself. A URL is read directly; a bare code is looked up in Metro's catalogue. Useful for monitoring a fixed basket.

## `provinces` (type: `array`):

Which of Metro's online catalogues to read. A product sold in both is returned once.

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

Stop after this many products. This also caps what the run costs.

## `includeUnpriced` (type: `boolean`):

Metro lists some products it does not sell online and shows no price for them. They are skipped (and not charged) unless you turn this on; they then come back with an empty price and availableOnline set to false.

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

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

## Actor input object example

```json
{
  "keywords": [
    "coffee"
  ],
  "productCodes": [],
  "provinces": [
    "QC",
    "ON"
  ],
  "maxProducts": 10,
  "includeUnpriced": false,
  "maxConcurrency": 5
}
```

# 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 = {
    "keywords": [
        "coffee"
    ],
    "maxProducts": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("alduinxx/metro-grocery-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 = {
    "keywords": ["coffee"],
    "maxProducts": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("alduinxx/metro-grocery-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 '{
  "keywords": [
    "coffee"
  ],
  "maxProducts": 10
}' |
apify call alduinxx/metro-grocery-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,alduinxx/metro-grocery-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/0aKYAUzWoQBtxjmIp/builds/rjvic6Srq1nqAvvZo/openapi.json
