# Shoppers Drug Mart & Pharmaprix Scraper — Prices & PC Optimum (`yugenox/shoppers-drug-mart-scraper`) Actor

Shoppers Drug Mart & Pharmaprix products by keyword, category, brand, UPC or the whole ~23,500-product catalogue: price, sale price and end date, every deal, PC Optimum points and offers, online stock count, UPC, ratings, category and images. Store locator included. No login.

- **URL**: https://apify.com/yugenox/shoppers-drug-mart-scraper.md
- **Developed by:** [Yugenox Corp](https://apify.com/yugenox) (community)
- **Categories:** E-commerce, Automation, Integrations
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 result rows

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

## Shoppers Drug Mart & Pharmaprix Scraper — Prices, Deals & PC Optimum

Scrape **Shoppers Drug Mart** and **Pharmaprix** products at scale: prices, sale prices and when they end, PC Optimum points and bonus offers, online stock counts, UPC barcodes, ratings, full category paths, images and ingredients. Search by keyword, browse categories, look up UPCs, pull a brand's whole range, or download the **entire online catalogue (~23,500 products)** in one run.

- 🏷️ **Every deal, with dates.** Percent off, sale price, multi-buy ("2 for $24.00"), gift with purchase — each with its start and end date.
- ⭐ **PC Optimum built in.** Base points per product plus bonus offers ("5,000 bonus points when you buy 2") and points multipliers.
- 📦 **Real online stock counts** (e.g. `onlineStock: 104`), not just in/out of stock.
- 🔢 **UPC barcodes on every row**, so you can match products against Amazon, Walmart or your own catalogue.
- 🗂️ **Whole catalogue or just what you need**: keywords, categories, brands, UPC lists, or everything.
- 🇫🇷 **Pharmaprix (Quebec)** with French names and deal labels.
- 📍 **Store locator**: the 50 stores nearest any Canadian city, with address, phone and opening hours.
- 🔑 No account, no login, no cookies. Paste and run.

### What you can scrape

| Input | Example | What you get |
|---|---|---|
| 🔎 Search terms | `advil`, `vitamin d`, `cerave` | The products the website search returns (up to ~500 per term) |
| 🗂️ Categories | `Skin Care`, `57127`, `https://www.shoppersdrugmart.ca/…/c/57127` | Every product in the category |
| 🔗 Website URLs | product, category or search page links | That product / category / search |
| 🏷️ UPCs | `062107004107` | That exact product — ideal for monitoring a fixed list |
| Brands | `Life Brand`, `CeraVe` | The brand's whole range, or narrows your searches/categories to it |
| Entire catalogue | `fullCatalog: true` | All ~23,500 products (Pharmaprix: ~22,500) |
| 📍 Store locator | `Toronto`, `Montreal`, `43.65,-79.38` | The 50 nearest stores with hours and phone |

#### Filters and sorting

- **On sale only** — only products with a sale price or deal right now. With *Entire catalogue* this gives you **every current Shoppers deal** (~4,000 products) in about two minutes.
- **PC Optimum bonus offers only** — only products with a bonus-points offer.
- **Sort** — relevance, price low→high, price high→low, newest, top rated. With a per-search limit this picks e.g. the 50 cheapest.

### Use cases

- **Price and promotion monitoring** for brands and distributors (MAP compliance, competitor pricing, promo calendars).
- **Deal and points sites** — the full weekly list of Shoppers sales and PC Optimum bonus offers with exact end dates.
- **Retail arbitrage** — UPCs, prices and live online stock to match against other marketplaces.
- **Assortment and market research** — what Canada's largest pharmacy chain carries, by category and brand, with ratings.
- **Beauty analytics** — shades, finishes, skin types and luxury flags from the Beauty Boutique range.

### Input

| Field | Type | Description |
|---|---|---|
| `searchTerms` | array | Keywords, as typed on the website |
| `categories` | array | Category names, numbers or URLs |
| `startUrls` | array | shoppersdrugmart.ca / pharmaprix.ca product, category or search URLs |
| `upcs` | array | UPC barcodes / product codes |
| `brands` | array | Brand names |
| `fullCatalog` | boolean | The whole online catalogue |
| `onlyOnSale` | boolean | Only products on sale |
| `onlyPcOptimumOffers` | boolean | Only products with a PC Optimum bonus offer |
| `sort` | string | `relevance`, `price-asc`, `price-desc`, `newest`, `top-rated` |
| `maxItemsPerQuery` | integer | Limit per search term / category (default 500, 0 = none) |
| `maxItems` | integer | Limit for the whole run (0 = none) |
| `banner` | string | `shoppers` (default) or `pharmaprix` |
| `language` | string | `auto`, `en` or `fr` |
| `includeDescription` | boolean | Description, ingredients, how to use (default on, no extra requests) |
| `fetchMissingImages` | boolean | Fetch the image for the ~1 in 3 products the listing shows without one (default on) |
| `includeDetails` | boolean | Product-page details: every shade/size with its own price and stock, canonical URL, extra ratings |
| `storeLocations` | array | Cities or `lat,lng` for the store locator |

Example — every vitamin, supplement and Advil product on sale right now, cheapest first:

```json
{
  "categories": ["Vitamins & Supplements"],
  "onlyOnSale": true,
  "searchTerms": ["advil"],
  "sort": "price-asc",
  "maxItemsPerQuery": 0
}
```

### Output

One row per product. A real example (long text fields shortened):

```json
{
  "productId": "SDM_062107004343",
  "upc": "062107004343",
  "name": "Children's Fever and Pain Relief Ibuprofen Oral Suspension, Dye Free, Grape, 100 mL",
  "brand": "Advil",
  "packageSize": "100 ml",
  "price": 12.99,
  "regularPrice": 14.49,
  "wasPrice": 14.49,
  "onSale": true,
  "discountPercent": 10,
  "salePriceEndsAt": "2026-09-25T03:59:59.000Z",
  "dealLabel": "Now $12.99",
  "pcOptimumPoints": 180,
  "pcOptimumOffer": "5,000 bonus points when you buy 2",
  "pcOptimumBonusPoints": 5000,
  "pcOptimumOfferEndsAt": "2026-10-10T03:59:59.000Z",
  "promotions": [
    { "type": "LOYALTY", "kind": "bonus-points", "label": "5,000 bonus points when you buy 2", "value": 5000, "minQuantity": 2, "validFrom": "2026-09-12T04:00:00.000Z", "validTo": "2026-10-10T03:59:59.000Z" },
    { "type": "DEAL", "kind": "sale-price", "label": "Now $12.99", "value": 12.99, "validFrom": "2026-09-19T04:00:00.000Z", "validTo": "2026-09-25T03:59:59.000Z" }
  ],
  "inStock": true,
  "onlineStock": 13,
  "maxOrderQuantity": 6,
  "category": "Children's Pain & Fever Relief",
  "categoryPath": "Health > Medicine & Treatments > Pain Relief > Children's Pain & Fever Relief",
  "categoryIds": ["57127", "57139", "57203", "57490"],
  "badges": ["Prepared in Canada", "PC Optimum Offer", "Sale"],
  "isScheduleThree": false,
  "imageUrl": "https://assets.beauty.shoppersdrugmart.ca/bb-prod-product-image/062107004343/…/white.jpg",
  "url": "https://www.shoppersdrugmart.ca/…/p/SDM_062107004343",
  "description": "…",
  "ingredients": "…",
  "rank": 36,
  "source": "search:advil",
  "banner": "Shoppers Drug Mart",
  "scrapedAt": "2026-09-24T04:31:10.000Z"
}
```

Other fields include `rating`, `ratingCount`, `reviewCount`, `shade` / `shadeHex` for makeup, `attributes` (finish, skin type, concern, hair type…), `isPrivateLabel`, `isLuxury`, `isOnlineExclusive`, `images`, `howToUse` and `featuresBenefits`. Prices are in CAD. With **Product page details** on, `variants` lists every shade or size with its own UPC, price and stock.

Store locator rows (`dataType: "store"`) carry `storeNumber`, `name`, `address`, `city`, `province`, `postalCode`, `phone`, `latitude`, `longitude`, `distanceKm`, `openStatus`, `hours` and links to the store page, flyer and pharmacy appointment booking.

The dataset has three views: **Products**, **Deals & PC Optimum** and **Stores**. Export as JSON, CSV, Excel or via API.

### FAQ

**Are prices the same in every store?**
The prices, deals and stock here are the **online** (shoppersdrugmart.ca / pharmaprix.ca) ones. In-store prices can differ by location.

**Why does a search stop at about 500 products?**
That is the website's own limit for a keyword search. For more, use categories (no limit) or *Entire catalogue*.

**Why do the last results of a search look loosely related?**
The website's search is AI-based: it ranks by similarity, so the top results match best and the tail drifts (a typo still returns products). Keep the per-search limit modest for keyword searches, or add *Brands* to keep only exact brand matches.

**I searched for stores in Montreal with Shoppers Drug Mart selected.**
Quebec stores are listed under Pharmaprix. When a location has no stores under the selected banner, the other banner's store list is used automatically.

**How long does the entire catalogue take?**
About 7–8 minutes for all ~23,500 products. *On sale only* narrows it to about 4,000 products and under two minutes. Switch off *Fill in missing images* for the fastest run.

**What is `onlineStock`?**
The number of units available for online orders at the time of the run. `inStock` is `true` when it is above zero.

**Pharmaprix or Shoppers Drug Mart?**
Pharmaprix is the Quebec banner with its own catalogue and prices (French by default). Its online catalogue carries far fewer over-the-counter medicines, so some Shoppers products and UPCs are not found there. Pick it under *Store*; French search terms give the best results. Pharmaprix URLs pasted into *Website URLs* are recognised automatically.

**Does it return reviews?**
It returns the average rating and the number of ratings and reviews, not the review texts.

**Can I track prices over time?**
Yes. Schedule the actor (daily or weekly) with the same input and compare runs by `productId` or `upc`.

**A product I know exists was not found by UPC.**
Only products in the online catalogue can be found. Some in-store-only items are not sold online.

**Is it legal to scrape Shoppers Drug Mart?**
This Actor only collects publicly available data: product listings, prices, deals, PC Optimum offers, stock and store locations that anyone can see on the Shoppers Drug Mart and Pharmaprix websites without an account. Collecting publicly available data is generally legal, but you're responsible for how you use it. You must follow privacy laws such as GDPR, PIPEDA and CCPA, as well as Shoppers Drug Mart's and Pharmaprix's terms. If you're unsure, check with a lawyer. More on this: [Is web scraping legal?](https://blog.apify.com/is-web-scraping-legal/)

**Does it access any private data?**
No. Everything comes from pages Shoppers Drug Mart and Pharmaprix show to any visitor without logging in. It never uses a login, never touches private or restricted accounts, and never reaches password-protected areas.

# Actor input Schema

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

Keywords to search, exactly as you would type them on the website, e.g. "advil", "vitamin d", "cerave moisturizer". Each search returns up to ~500 products (the website's own limit) — for more, use categories or the entire catalogue.

## `categories` (type: `array`):

Category page URLs (…/c/57127), category numbers, or names: Beauty, Makeup, Skin Care, Hair Care, Fragrance, Personal Care, Health, Vitamins & Supplements, Medicine & Treatments, Baby & Child, Home & Electronics, Electronics, Grocery & Household, Sun Care, Men's. Every product in the category is returned (subject to the per-query limit).

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

Paste shoppersdrugmart.ca or pharmaprix.ca links: product pages (…/p/BB\_062107004107), category pages (…/c/57127) or search pages (…/search?text=sunscreen).

## `upcs` (type: `array`):

Look up specific products by UPC barcode (e.g. 062107004107) — one row per product found. Handy for price and stock monitoring of a fixed list.

## `brands` (type: `array`):

Brand names as shown on the website (e.g. "Life Brand", "CeraVe", "L'Oréal Paris"). On their own they return the brand's whole range; combined with search terms or categories they narrow those results to the brand.

## `fullCatalog` (type: `boolean`):

Scrape every product in the online catalogue (~23,500 at Shoppers Drug Mart, ~22,500 at Pharmaprix). Combine with "On sale only" to get every current deal (~4,000 products). Takes about 7–8 minutes.

## `onlyOnSale` (type: `boolean`):

Keep only products with a sale price or deal right now (percent off, sale price, multi-buy).

## `onlyPcOptimumOffers` (type: `boolean`):

Keep only products with a PC Optimum bonus-points offer right now. With "On sale only" also on, a product must match both.

## `sort` (type: `string`):

Order in which search and category results are ranked (the "rank" field). With a per-query limit, this decides which products you get — e.g. the 50 cheapest.

## `maxItemsPerQuery` (type: `integer`):

Upper limit for each search term and each category. 0 = no limit.

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

Stop the whole run after this many results. 0 = no limit.

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

Shoppers Drug Mart (rest of Canada) or Pharmaprix (Quebec). They have separate catalogues and prices.

## `language` (type: `string`):

Language of product names, descriptions and deal labels. Auto = English for Shoppers Drug Mart, French for Pharmaprix. Search terms work best in the same language.

## `includeDescription` (type: `boolean`):

Include the long text fields. No extra requests; switch off for smaller files.

## `fetchMissingImages` (type: `boolean`):

About a third of products come without an image in the listing; this fetches their product page to get one. Switch off for the fastest entire-catalogue runs.

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

Fetch every product's page too: all shades/sizes with their own price and stock, canonical URL, ratings for products the listing has none for, bonus-points text. One extra request per product — slower.

## `storeLocations` (type: `array`):

Also list the 50 stores nearest each location: a Canadian city ("Toronto", "Montreal", "Calgary"…) or "latitude,longitude". Returns address, phone, opening hours and open/closed status. Use the Pharmaprix store for Quebec locations.

## `concurrency` (type: `integer`):

How many requests run at once.

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

Apify datacenter proxy works well and is the default. If requests start getting blocked the run switches to residential proxy automatically.

## Actor input object example

```json
{
  "searchTerms": [
    "advil",
    "vitamin d"
  ],
  "fullCatalog": false,
  "onlyOnSale": false,
  "onlyPcOptimumOffers": false,
  "sort": "relevance",
  "maxItemsPerQuery": 50,
  "maxItems": 0,
  "banner": "shoppers",
  "language": "auto",
  "includeDescription": true,
  "fetchMissingImages": true,
  "includeDetails": false,
  "concurrency": 12,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

Every row as JSON: price, sale price, promotions, PC Optimum points and offers, online stock, UPC, ratings, category and images.

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

The same rows as CSV, for a spreadsheet or a price tracker.

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

The rows narrowed to the key columns: product, brand, price, was-price, deal, PC Optimum, stock, UPC and link.

# 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 = {
    "searchTerms": [
        "advil",
        "vitamin d"
    ],
    "maxItemsPerQuery": 50,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("yugenox/shoppers-drug-mart-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 = {
    "searchTerms": [
        "advil",
        "vitamin d",
    ],
    "maxItemsPerQuery": 50,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("yugenox/shoppers-drug-mart-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 '{
  "searchTerms": [
    "advil",
    "vitamin d"
  ],
  "maxItemsPerQuery": 50,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call yugenox/shoppers-drug-mart-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,yugenox/shoppers-drug-mart-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/Dz0nXBC0c1E9HCGdr/builds/CLgPgnEJKQEgKSEhp/openapi.json
