# Loblaws & No Frills Scraper: Prices, PC Optimum Deals (`vant47/loblaws-no-frills-scraper`) Actor

Get Loblaws, No Frills and Real Canadian Superstore products with the price at your store: was price, deals, multi-buys, PC Optimum points offers, unit price, stock, UPC, brand and size. Search, categories or product links by postal code. About $0.003 per product. No login.

- **URL**: https://apify.com/vant47/loblaws-no-frills-scraper.md
- **Developed by:** [fortbrand](https://apify.com/vant47) (community)
- **Categories:** E-commerce, Automation
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.40 / 1,000 products

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/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

## Loblaws & No Frills Scraper: Prices, PC Optimum Deals

For retail price analysts, grocery comparison teams and brands monitoring Canadian shelf prices and promotions at specific PC Express stores.

About **US$0.003 per product at one store**. A 20-product check costs about **US$0.06**, plus the small Actor start charge. Product prices in the output are **Canadian dollars**. Descriptions and available label details have the same product price.

Search Loblaws, No Frills, Real Canadian Superstore and the other supported PC Express banners.

### What you get

| Field | Meaning |
| --- | --- |
| `retailer`, `country`, `currency` | Grocery banner, CA and CAD |
| `storeId`, `storeName` | Verified online store used for this price |
| `location` | Requested postal code; null for explicit IDs or a default store |
| `productId`, `sku` | Full site product code, including its selling-unit suffix |
| `gtin` | Site barcode with a valid check digit, when available |
| `title`, `brand`, `size` | Product name, brand and original package-size text |
| `price` | Current single-purchase price supplied by the store |
| `regularPrice` | Site was-price when it is provided |
| `onSale` | Sale status from the price type, sale badge or was-price |
| `promoText`, `promoType` | Offer wording and source offer type |
| `promoStart`, `promoEnd` | Available primary-offer dates, preserved as source strings |
| `memberPrice`, `memberProgram` | Public member-only price and PC Optimum program |
| `unitPrice`, `unitOfMeasure` | Comparison price and its quantity/unit, such as 100 ml |
| `inStock`, `stockLevel` | Interpreted site stock status; quantity remains null when absent |
| `rating`, `reviewCount` | Available product rating and review count |
| `category` | Requested category ID for category results, where applicable |
| `url`, `imageUrl` | Storefront product link and product image |
| `scrapedAt` | UTC time the product response was read |

| Additional fields | What they add |
| --- | --- |
| `banner`, `articleNumber`, `upc`, `upcs` | Banner code, article number and original barcode strings |
| `gtinValid`, `dataNotes` | Barcode check-digit result and data-quality notes |
| `packageSize`, `aisle`, `sellingType`, `weighted` | Packaging and selling method |
| `priceType`, `priceQuantity`, `priceUnit`, `priceExpiry` | Price basis and original price validity |
| `comparisonPrices`, `memberUnitPrices` | All supplied unit-price comparisons |
| `multiBuyOffer`, `multiBuyPrice`, `multiBuyQuantity` | Source multi-buy value and available quantity condition |
| `multiBuyPriceBasis`, `multiBuyBundlePrice` | Whether the source wording states a per-item minimum or a bundle total |
| `pointsOffer`, `promotions`, `memberPromotions` | Public points rewards, conditions and individual offer dates |
| `memberPriceEnd`, `memberDeal`, `memberDealPrice`, `memberDealText` | Public member-price and conditional member-deal information |
| `purchaseLimit`, `maximumOrderQuantity` | Explicit purchase limits and the separate cart quantity cap |
| `stockStatus`, `isShoppable`, `rawRatings` | Original availability and ratings information |
| `deposit`, `environmentalHandlingFee`, `taxInfo`, `taxes`, `fees` | Supplied fee and tax information |
| `storeAddress`, `storeCity`, `storeProvince`, `storePostalCode` | Store address context |
| `storeCoordinates`, `locationResolution`, `requestedLocations` | Selection method, postcode match and available straight-line distance |
| `sourceType`, `sourceValue`, `sourceEndpoint` | Search, category or article lookup that produced the row |
| `description`, `ingredients`, `nutrition`, `images` | Available label details when requested; unavailable values remain null |

### Start in three steps

1. Choose a banner. The default is Loblaws.
2. Enter a search term and a Canadian postal code, or use an exact store ID.
3. Set your global product limit and start the run. Download JSON, CSV or Excel from the dataset.

The prefilled example searches for milk at postal code `K2C 0C6`. That code matches Loblaws Baseline Road, store `1050`, in the retailer directory. In a cloud validation run on 28 October 2026, the example delivered 20 unique products with 20 product charges in 17 seconds.

A larger cloud test delivered 400 unique products with 400 product charges in 178 seconds. Peak memory was about 73 MB at the 512 MB setting.

### Inputs

| Input | Default | Use |
| --- | --- | --- |
| `banner` | `loblaw` | Banner for search terms and product IDs |
| `searchTerms` | Milk in the prefill | Array of search words |
| `startUrls` | Empty | Optional category, deals, search or product URLs |
| `productIds` | Empty | Numeric article numbers or complete product codes |
| `locations` | One code in the prefill | Full Canadian postal codes |
| `storeIds` | Empty | Exact stores; these take priority over postal codes |
| `maxItemsPerSearch` | 20 | New unique products per source and store |
| `maxItems` | 20 | Global product limit across the run |
| `dealsOnly` | false | Keep products carrying a qualifying offer |
| `includeDetails` | false | Include available descriptions, ingredients and extra images |
| `includeSponsored` | false | Include and flag sponsored placements |
| `proxyConfiguration` | Automatic | Datacenter first, Canadian residential fallback after failures |

There is a limit of 200 source selectors, 20 postal codes or 50 store IDs, and 10,000 product rows per run. Use a larger `maxItems` for multi-store comparisons. Larger jobs may also need a longer Apify run timeout.

Supported banner codes are `loblaw`, `nofrills`, `superstore`, `zehrs`, `fortinos`, `valumart`, `independent`, `provigo`, `maxi`, `wholesaleclub`, `rass`, `independentcitymarket` and `dominion`, subject to the current public online-store directory. `rass` identifies Atlantic Superstore. Familiar aliases such as `loblaws`, `no frills`, `rcss` and `yig` are accepted.

#### A small basket at one store

```json
{
  "banner": "loblaw",
  "searchTerms": ["milk", "eggs", "bread"],
  "storeIds": ["1050"],
  "maxItemsPerSearch": 10,
  "maxItems": 30
}
```

#### Compare the same article at two stores

```json
{
  "banner": "loblaw",
  "productIds": ["20188873_EA"],
  "storeIds": ["1050", "1000"],
  "maxItemsPerSearch": 1,
  "maxItems": 2
}
```

#### Category promotions

```json
{
  "startUrls": ["https://www.loblaws.ca/en/food/dairy-and-eggs/milk-and-cream/c/28224"],
  "storeIds": ["1050"],
  "dealsOnly": true,
  "maxItemsPerSearch": 20,
  "maxItems": 20
}
```

Category IDs use `/c/` URLs. Product links use `/p/`. Article lookups retain exact matches.

### Read prices and offers correctly

The `price` field holds the store's current single-purchase price. A multi-buy offer has its own fields. For example, a product can cost C$2 individually and show C$1.50 with a minimum of two. The Actor preserves both numbers and the minimum-quantity wording.

Points offers retain their reward and conditions. A reward requiring a minimum spend is different from an unconditional price reduction. Member prices, multi-buy conditions and points promotions remain separately available for that reason.

Unit prices retain the source quantity. A comparison of C$0.28 per 100 ml keeps `unitPrice: 0.28` and `unitOfMeasure: "100 ml"`. Weighted products can have estimated display prices. Review `weighted`, `sellingType`, `priceUnit` and the original comparison prices when building a fixed basket. Final checkout weight, deposits and taxes can affect the amount paid.

Dates retain the strings returned by the site. Some dates have a timezone and some are local midnight values. The Actor adds no timezone to an unspecified value. Individual promotions retain their own expiry dates alongside the primary offer date.

### Postal codes and store selection

An exact match to a store's own postal code uses that verified directory entry. Other postal codes use a full-code match from Photon/OpenStreetMap, followed by the nearest listed online store in the same province. The returned selection metadata identifies the method and any available distance.

The public postcode data has coverage gaps. An unresolved full code produces a free note with no product charge. An exact `storeId` is useful for scheduled price monitoring and for postal codes absent from the public lookup. A banner with no verified online store in the requested province also produces a free note.

Public geocoding is cached for seven days. Product prices are read again on every new run. Geocoding data attribution: [OpenStreetMap contributors](https://www.openstreetmap.org/copyright), supplied through the [Photon public service](https://photon.komoot.io/). Use of the public service is limited to modest, paced postcode lookups.

### Output and exports

The default dataset has one row per distinct product and store. Discovering the same product through a search and a category adds no second row or product charge. Sponsored placements are excluded unless requested.

The **Store prices** view places price, was-price, promotions, member price, unit price, stock and store together. **Deals and member offers** shows the promotion columns. **Barcodes and stock** focuses on identifiers and availability.

Every run saves a `SUMMARY` JSON record with products, stores, deals, free failures, input progress and charge counts. `STORES` contains selected-store metadata. `DEALS` is a deals-only JSON export. Free `NOTE` rows explain invalid inputs, unresolved locations and unavailable source responses.

A real output excerpt from the prefill validation:

```json
{
  "retailer": "Loblaws",
  "currency": "CAD",
  "storeId": "1050",
  "storeName": "Loblaws Baseline Road",
  "location": "K2C 0C6",
  "productId": "20188873_EA",
  "title": "2% Milk",
  "brand": "Neilson",
  "size": "4 l",
  "price": 6.44,
  "regularPrice": null,
  "onSale": false,
  "promoText": null,
  "promoEnd": null,
  "memberPrice": null,
  "unitPrice": 0.16,
  "unitOfMeasure": "100 ml",
  "inStock": true,
  "upc": "06680000015",
  "gtin": null,
  "scrapedAt": "2026-10-28T20:42:44.540Z"
}
```

### Price and spending limits

One **Product** charge applies after a priced product row is saved. Duplicate product/store pairs, invalid inputs, empty results and failed source requests have no product charge. The platform start charge is retained.

| Products delivered | Base product fee |
| --- | --- |
| 20 | US$0.06 |
| 100 | US$0.30 |
| 1,000 | US$3.00 |

Bronze receives 5% off, Silver 10% off and Gold 20% off. The base product price includes available details. Use Apify's maximum charge setting to control the run. When the remaining allowance cannot cover a product, the Actor stops and records that in the summary.

### Monitor prices every day

Create a task containing your basket, banner and exact store IDs. In Apify Schedules, choose that task and set a daily or weekly time. Retain each run's dataset and compare `price`, `regularPrice`, `memberPrice`, `promoText` and `inStock` by `productId` and `storeId`.

For a 100-product basket checked once daily, the base product fee is about US$0.30 per check, before tier discounts and the small start charge. Five stores with the same 100 products can produce 500 rows, so set the global limit accordingly.

Keep `scrapedAt` with each observation. A resumed run retains already saved observations and their original times. A new scheduled run reads a new price snapshot.

### API use

Small runs can use Apify's synchronous dataset endpoint:

```bash
curl -X POST \
  'https://api.apify.com/v2/acts/vant47~loblaws-no-frills-scraper/run-sync-get-dataset-items' \
  -H 'Authorization: Bearer YOUR_APIFY_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{"searchTerms":["milk"],"storeIds":["1050"],"maxItems":20}'
```

For larger jobs, start an asynchronous run and fetch its default dataset after completion:

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('vant47/loblaws-no-frills-scraper').call({
  banner: 'loblaw',
  productIds: ['20188873_EA'],
  storeIds: ['1050', '1000'],
  maxItems: 2
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

### Use with AI agents (MCP)

Connect an Apify-compatible agent through [Apify MCP](https://mcp.apify.com). Ask it to use `vant47/loblaws-no-frills-scraper` and pass ordinary input fields. For example:

Get milk, egg and bread prices at Loblaws store 1050. Return up to 30 products with was-price, unit price, public member offers and stock.

### Limits and FAQ

**Does it require a grocery account?** It reads public signed-out store data. Personal PC Optimum offers and account-only information are unavailable.

**Are these national prices?** Every product identifies one store. Select exact store IDs for a stable comparison basket.

**Why is a barcode or label field null?** The source may omit it. Original UPC strings remain available even when they lack a complete GTIN check digit. Label fields depend on what the public response supplies.

**What happens after a restart?** Saved dataset rows form the recovery ledger. Stable product/store charge keys and acknowledged counts reconcile the saved rows before new products are delivered.

**What if the retailer refuses a request?** Requests use paced retries and fresh sessions, followed by Canadian residential fallback where needed. An exhausted source creates a free note. Read `SUMMARY.status` and `freeFailures` when validating a scheduled job.

**Does it keep price history?** Each new run returns current observations. Keep your datasets to build a history for your chosen products and stores.

# Actor input Schema

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

Banner used for searches and product IDs. Category and product URLs identify their own banner. Each row identifies its actual store.

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

Search words such as milk, eggs, bread or coffee. Products are priced at every selected store. Omit when supplying only category links or product IDs.

## `startUrls` (type: `array`):

Optional public PC Express URLs, for example https://www.loblaws.ca/en/food/dairy-and-eggs/milk-and-cream/c/28224 or a product /p/21425018_EA link. Search URLs with search-bar= are supported.

## `productIds` (type: `array`):

Optional article numbers or full product codes, for example 21425018 or 21425018_EA. Exact matches only; unrelated search suggestions are excluded.

## `locations` (type: `array`):

Full Canadian postal codes. The nearest verified online store of each requested banner is selected within the same province. Unresolved full codes produce a free note. Empty means the banner default store.

## `storeIds` (type: `array`):

Optional exact stores, such as 1050 for Loblaws Baseline Road. Leading zeros are preserved. Explicit IDs override postal codes and must belong to the selected banner.

## `maxItemsPerSearch` (type: `integer`):

Maximum new unique product rows per search, category or deals source at each store. Duplicates across sources are free.

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

Global maximum across every source and store. About $0.003 per product; 20 products cost about $0.06 plus the small start charge. Increase this for multi-store jobs.

## `dealsOnly` (type: `boolean`):

Keep sale prices, multi-buy offers, public member prices and points offers. Use with a search, category or deals URL. Promotional dates remain the source values.

## `includeDetails` (type: `boolean`):

Include descriptions, ingredients when supplied and additional images. Missing label fields remain null. There is no extra details charge.

## `includeSponsored` (type: `boolean`):

Sponsored placements are excluded by default. If included, isSponsored identifies them and duplicate products are still charged only once per store.

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

Empty or automatic Apify Proxy uses datacenter sessions first, with Canadian residential fallback after failures. Custom proxy settings override this. Public postal geocoding uses a cached, paced direct request.

## Actor input object example

```json
{
  "banner": "loblaw",
  "searchTerms": [
    "milk"
  ],
  "locations": [
    "K2C 0C6"
  ],
  "maxItemsPerSearch": 20,
  "maxItems": 20,
  "dealsOnly": false,
  "includeDetails": false,
  "includeSponsored": false
}
```

# Actor output Schema

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

No description

## `deals` (type: `string`):

No description

## `summary` (type: `string`):

No description

## `stores` (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 = {
    "banner": "loblaw",
    "searchTerms": [
        "milk"
    ],
    "locations": [
        "K2C 0C6"
    ],
    "maxItemsPerSearch": 20,
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("vant47/loblaws-no-frills-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 = {
    "banner": "loblaw",
    "searchTerms": ["milk"],
    "locations": ["K2C 0C6"],
    "maxItemsPerSearch": 20,
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("vant47/loblaws-no-frills-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 '{
  "banner": "loblaw",
  "searchTerms": [
    "milk"
  ],
  "locations": [
    "K2C 0C6"
  ],
  "maxItemsPerSearch": 20,
  "maxItems": 20
}' |
apify call vant47/loblaws-no-frills-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,vant47/loblaws-no-frills-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/KSWFDoWBdD6vOqmis/builds/PtQseemQkA1eqt0zG/openapi.json
