# Home Hardware Canada Scraper — Store Prices, Sales & Stock (`yugenox/home-hardware-canada-scraper`) Actor

Home Hardware Canada products with store-level prices, sale prices and end dates, in-store and online stock, ratings, model numbers and Made-in-Canada flags. Search, categories, product watchlists and the ~990-store directory, by postal code or store. English and French.

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

## Pricing

from $1.00 / 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

## Home Hardware Canada Scraper: Store Prices, Sales & Stock

Scrape **Home Hardware Canada** ([homehardware.ca](https://www.homehardware.ca)) product data **for each store**: the price at that store, sale price and sale end date, in-store stock, ship-to-home stock, availability, ratings, model numbers and Made-in-Canada flags. You can search by keyword, crawl whole categories, track a watchlist of product codes across many stores, or export the store directory of about 990 stores.

Home Hardware is a dealer-owned co-op, so **each store sets its own prices**. The same drill kit can cost $59.99 in Toronto and $74.99 in Vancouver on the same day. This actor gives you the price and stock at the store you choose, not a national average.

- 🏪 **Store-level data.** Enter postal codes or cities (the nearest store is used, or the nearest N) or store numbers, or run it at every store, optionally filtered by province.
- 🏷️ **Prices and deals.** Current price, regular price, sale price, % off, and when the sale ends.
- 📦 **Stock.** Units on hand at stores that report live inventory, plus ship-to-home and warehouse stock and pickup/delivery availability.
- ⭐ **Ratings.** Average rating, review count and the 1-5 star breakdown.
- 🇨🇦 **English or French.**
- ⚡ **Fast and inexpensive.** About 120 products per request. A 4,000-product category crawl takes about a minute.

### What you can scrape

| Mode | You give | You get |
|---|---|---|
| **Search** | search terms, e.g. `cordless drill` | matching products at each store |
| **Categories** | category URLs or names, e.g. `Paint`, `Hand Tools` | every product in the category, sub-categories included |
| **Product codes** | a watchlist of product codes or URLs | price, sale and stock of each product at each store |
| **Store directory** | nothing, or postal codes / provinces | every store: address, phone, hours, services, store type |

### Input examples

**Search near a postal code** (this is the pre-filled example):

```json
{
  "queries": ["cordless drill"],
  "locations": ["M5V 2T6"],
  "maxItems": 50
}
```

**Compare prices across the 5 nearest stores in two cities:**

```json
{
  "queries": ["snow shovel", "furnace filter"],
  "locations": ["Halifax, NS", "Calgary, AB"],
  "storesPerLocation": 5,
  "maxItemsPerQuery": 100
}
```

**Crawl a whole category, on-sale items only, in French:**

```json
{
  "mode": "category",
  "categories": ["https://www.homehardware.ca/en/paint/c/1525535408186"],
  "storeCodes": ["13817"],
  "onSaleOnly": true,
  "language": "fr",
  "maxItemsPerQuery": 0
}
```

**Price and stock watchlist at every store in Ontario:**

```json
{
  "mode": "products",
  "productCodes": ["1239142", "1239221", "https://www.homehardware.ca/en/x/p/1030728"],
  "allStores": true,
  "provinces": ["ON"],
  "maxItems": 5000
}
```

**Store directory for BC and Alberta:**

```json
{ "mode": "stores", "provinces": ["BC", "AB"] }
```

#### Main input fields

| Field | What it does |
|---|---|
| `mode` | `auto` (default), `search`, `category`, `products` or `stores`. `auto` uses product codes if you gave any, then categories, then search terms. |
| `queries` | Search terms, one per line. |
| `categories` | Category page URLs, or names such as `Paint`. |
| `productCodes` | 7-digit codes (`1239142` or `1239-142`) or product URLs. |
| `locations` | Postal codes, cities ("Kelowna, BC") or coordinates ("43.64,-79.39"). Each one resolves to its nearest store. |
| `storesPerLocation` | Use the N nearest stores for each location (1-50). |
| `storeCodes` | Store numbers, such as `13817`. |
| `allStores` + `provinces` | Every store, or only the stores in the provinces you list. |
| `language` | `en` or `fr`. |
| `sort` | `relevance`, `price_asc`, `price_desc`, `rating_asc` or `rating_desc`. |
| `inStockOnly` / `onSaleOnly` | Only items available at the store / only items on sale. |
| `maxItemsPerQuery` | Limit per search term or category per store (`0` means no limit). A product-code watchlist is never cut by this; only `maxItems` applies. |
| `maxItems` | Limit for the whole run (`0` means no limit). |
| `includeDescription` / `includeRaw` | Add the marketing description / the complete original record. |

If you give no location and no store code, the actor uses Home Hardware's default online store.

### Output

One row per **product × store**:

```json
{
  "productCode": "1239025",
  "displayItem": "1239-025",
  "name": "20V Max 1/2\" Cordless Hammer Drill Kit with Battery & Charger",
  "brand": "DEWALT",
  "model": "DCD798D1",
  "url": "https://www.homehardware.ca/en/20v-max-12-cordless-hammer-drill-kit-with-battery-charger/p/1239025",
  "imageUrl": "https://homehardware.sirv.com/products/1239/1239025.view?thumb=image&w=500&q=75",
  "categoryPath": "Tools & Accessories > Power Tools > Drills > Cordless Drills",
  "department": "TOOLS, POWER",
  "price": 148,
  "regularPrice": 239.99,
  "salePrice": 148,
  "onSale": true,
  "discountPercent": 38,
  "saleEndsAt": "2026-10-01T03:59:00.000Z",
  "priceAvailable": true,
  "currency": "CAD",
  "sellingUnit": "KT",
  "storeCode": "13817",
  "storeName": "College Home Hardware",
  "storeCity": "Toronto",
  "storeProvince": "ON",
  "storeType": "Home Hardware",
  "storeStock": null,
  "storeStockTracked": false,
  "warehouseStock": 4,
  "shipToHomeStock": 4,
  "availabilityCodes": ["RETAIL_ITEM", "IN_STOCK_ONLINE"],
  "inStockOnline": true,
  "shipToHomeOnly": false,
  "pickupOnly": false,
  "discontinued": false,
  "rating": 4.8,
  "reviewCount": 24,
  "ratingHistogram": { "1": 0, "2": 0, "3": 1, "4": 4, "5": 19 },
  "madeInCanada": false,
  "productOfCanada": false,
  "onlineOnly": false,
  "packageDimensions": { "length": 3.5, "width": 12, "height": 9, "unit": "in", "weight": 5.035, "weightUnit": "lbs" },
  "createdAt": "2023-02-17T03:20:47.385Z",
  "updatedAt": "2026-09-19T03:59:39.067Z",
  "language": "en",
  "source": "search",
  "searchQuery": "cordless drill",
  "position": 1,
  "scrapedAt": "2026-09-24T03:32:44.907Z"
}
```

Other fields:

- `distributionCentreStock`: stock at Home Hardware's regional distribution centres.
- `maxQtyPickup` and `maxQtyShipToHome`: the most units you can order for each.
- `buyable`.
- `className` and `fineLine`: Home Hardware's merchandise classification.
- `category`: the category you crawled, in category mode.

**Store directory rows** contain:

- `storeCode`, `name` and `storeType` (Home Hardware, Home Hardware Building Centre or Home Building Centre).
- `address`, `city`, `province`, `postalCode`, `latitude`, `longitude` and `phone`.
- `hours` for each day and `services` (key cutting, propane, rentals, and so on).
- `realTimeInventory`, which says whether the store reports live stock.
- `shipToHome` and `temporarilyClosed`.

Each run also saves a `RUN_SUMMARY` record in its key-value store. For each search or category it shows how many products the listing reported and how many were collected, plus any notes.

### Good to know

- **`storeStock` is `null` with `storeStockTracked: false`** for stores that don't report live stock (about 1 in 4). `warehouseStock` and `shipToHomeStock` are still filled in.
- **`priceAvailable: false`** is set on special-order and supplier-direct items that have no shelf price at that store.
- About 20 small stores don't sell online, so Home Hardware publishes no prices for them. Product searches skip those stores and say so in the status message. They still appear in the store directory, with `ecommerce: false`.
- **Coverage.** A listing's product count includes a few items the website doesn't actually show, so a complete crawl usually returns about 94-99% of the reported number. The status message and `RUN_SUMMARY` give the exact figure for each listing.
- **Sale end dates** are in UTC. `2026-10-01T03:59:00Z` means the sale ends at 11:59 pm Eastern on Sep 30.
- Rows are **one per product per store**. If a product matches two of your search terms, it's saved once (turn off `dedupeAcrossQueries` to keep both).

### Use cases

- **Price monitoring and MAP compliance.** Brands and distributors can track their products' shelf and sale prices at every dealer-owned store.
- **Competitive pricing.** Compare Home Hardware with RONA, Canadian Tire, Home Depot or independent stores, region by region.
- **Deal and flyer apps.** Collect this week's sales, discounts and sale end dates by postal code.
- **Stock checks.** See which nearby stores have an item on hand before you drive over.
- **Market research.** Look at assortment, brands, Made-in-Canada share and ratings by category and province.
- **Store location data.** Get every store with its coordinates, hours and services.

### FAQ

**Do I need a Home Hardware account?** No. The actor collects publicly available catalogue data. No login or cookies are needed.

**How fast is it?** About 120 products per request, with several requests running in parallel. In our tests a 4,100-product crawl of two categories finished in about a minute, and the ~990-store directory in about 30 seconds.

**Why do prices differ between stores?** Home Hardware stores are independently owned, and each dealer sets its own prices. That is why this actor works per store.

**How do I find a store code?** Run the Store directory mode once, or enter a postal code, since every row includes `storeCode`.

**Can I schedule it?** Yes. Use Apify Schedules (for example, daily at 6 am) to build a price history. The combination of `productCode` + `storeCode` + `scrapedAt` makes a natural key.

**Which proxy should I use?** Keep the default Apify Proxy. Requests use datacenter IPs first and automatically switch to Canadian residential IPs when needed.

**Is it legal to scrape Home Hardware?** This Actor only collects publicly available data: product listings, store-level prices, sales and stock, and store locations that anyone can see on Home Hardware's website 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 Home Hardware'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 Home Hardware shows 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

## `mode` (type: `string`):

Search: products matching your search terms. Category: every product in the categories you list. Product codes: a watchlist of specific products (price + stock at each store). Stores: the store directory (address, hours, services) for all ~990 stores, or only the stores nearest your postal codes / cities / store codes. Auto picks from what you fill in: product codes, then categories, then search terms.

## `queries` (type: `array`):

Keywords, one per line, exactly as you would type them in the homehardware.ca search box (e.g. "cordless drill", "snow shovel", "furnace filter 16x25").

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

Category page URLs from homehardware.ca (e.g. https://www.homehardware.ca/en/paint/c/1525535408186), or category names like "Paint", "Hand Tools", "Snow Removal". Includes all sub-categories.

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

Home Hardware product codes (7 digits, e.g. 1239142 or 1239-142) or product page URLs (…/p/1239142). Returns the price, sale and stock of each product at every selected store.

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

Canadian postal codes (M5V 2T6), cities (Kelowna, BC) or coordinates (43.64,-79.39). Each one is matched to its nearest Home Hardware store — prices and stock are store-specific. Leave empty (with no store codes) to use Home Hardware's default online store.

## `storesPerLocation` (type: `integer`):

How many of the nearest stores to use for each postal code or city (1-50). Use this to compare prices across the stores in an area.

## `storeCodes` (type: `array`):

Home Hardware store numbers (e.g. 13817). Run the Store directory mode once to get every store's code.

## `allStores` (type: `boolean`):

Run your search / categories / product codes at every Home Hardware, Home Hardware Building Centre and Home Building Centre store in Canada (~990 stores). Combine with Provinces to narrow it down. Rows = products x stores, so set a sensible maximum.

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

Only used with "All stores" and the Store directory: keep stores in these provinces / territories.

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

Product names, categories and URLs in English or French.

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

Order of search and category results.

## `inStockOnly` (type: `boolean`):

Only products that can be bought at / from the selected store (the site's "Available in store" filter).

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

Only products with a sale price at the selected store.

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

Limit for each search term or category at each store. 0 = no limit (walk the whole listing).

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

Stop the run after this many rows. 0 = no limit.

## `dedupeAcrossQueries` (type: `boolean`):

If a product matches several of your search terms or categories, keep only its first row at each store. Turn off to get every search term's full result list (e.g. for search-rank tracking).

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

Add the marketing description of each product.

## `includeRaw` (type: `boolean`):

Add the complete unprocessed product record under "raw" (large; for advanced use).

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

Parallel requests (1-10). The default is fast and stays under the site's bot protection.

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

Apify Proxy is required. Datacenter proxies are used by default; blocked requests automatically retry on Canadian residential proxies.

## Actor input object example

```json
{
  "mode": "auto",
  "queries": [
    "cordless drill"
  ],
  "locations": [
    "M5V 2T6"
  ],
  "storesPerLocation": 1,
  "allStores": false,
  "language": "en",
  "sort": "relevance",
  "inStockOnly": false,
  "onSaleOnly": false,
  "maxItemsPerQuery": 120,
  "maxItems": 50,
  "dedupeAcrossQueries": true,
  "includeDescription": false,
  "includeRaw": false,
  "maxConcurrency": 6,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `results` (type: `string`):

All scraped rows.

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

Coverage per listing (products reported vs served), notes and request statistics.

# 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 = {
    "queries": [
        "cordless drill"
    ],
    "locations": [
        "M5V 2T6"
    ],
    "maxItems": 50,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("yugenox/home-hardware-canada-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 = {
    "queries": ["cordless drill"],
    "locations": ["M5V 2T6"],
    "maxItems": 50,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("yugenox/home-hardware-canada-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 '{
  "queries": [
    "cordless drill"
  ],
  "locations": [
    "M5V 2T6"
  ],
  "maxItems": 50,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call yugenox/home-hardware-canada-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,yugenox/home-hardware-canada-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/gvYpIqcC4Ru9xApxE/builds/0gZI78QJ5I9tPwTYY/openapi.json
