# Best Buy Canada Scraper — Prices, UPC, Store Stock & Sellers (`yugenox/bestbuy-canada-scraper`) Actor

Scrape bestbuy.ca products by keyword, category, marketplace seller, SKU or URL. Prices by province, sale end dates, UPC + model number, specs, exact per-store stock near any postal code, all marketplace offers with the buy-box winner, reviews and store locations. No login.

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

## Pricing

$1.50 / 1,000 product or store 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

## Best Buy Canada Scraper

Scrape **bestbuy.ca**: products, prices, sale end dates, **UPC and model numbers**, specs, **exact stock at each store near a postal code**, **every marketplace seller's offer** with the buy-box winner, customer reviews and store locations. Search by keyword, category, marketplace seller, SKU list or any bestbuy.ca URL. English or French, prices for any province. No account or login needed.

### What you can do with it

- **Price monitoring**: track a list of SKUs or a whole category every day. Regular price, sale price, discount, sale start and end dates, clearance and open-box flags.
- **Retail arbitrage**: UPC and model number on every product, so you can match Best Buy against Amazon.ca, Walmart.ca or your own catalogue.
- **Local stock checks**: units on hand at each of the nearest stores (for example 17 at Union Station and 13 at Eaton Centre Dundas), plus online stock.
- **Marketplace intelligence**: every seller's offer on a product, who wins the buy box, and full storefront crawls of any marketplace seller.
- **Assortment and catalogue data**: full specs, descriptions, images and what's in the box, across whole departments.
- **Review analysis**: ratings, review text, verified-purchase flags, helpful votes and photos.
- **Store locator data**: address, phone, hours and coordinates of every Best Buy store near a postal code.

### Why this scraper

- **Gets past the 2,000-result limit.** Best Buy shows at most 2,000 products per search or category. This scraper splits big listings automatically (by category, price band, brand and seller), so it can collect whole departments. In tests it got 99.99% of a 14,110-product category and 98% of a 33,267-product department.
- **Store-level stock counts.** You get the number of units at each store, not just "in stock / out of stock".
- **Prices for your province.** Set the province for the right environmental handling fee (EHF). With seller offers turned on, you get the EHF for every province.
- **Fast.** 100 products per request. A whole 14,000-product category comes back in about 30 seconds with details off. With full details, 1,000 products take about 30 seconds. With details, store stock, seller offers and reviews all on, 1,000 rows take about 45 seconds.
- **French and English.** Names, descriptions, specs and URLs come in either language.

### Input

Use any mix of the following:

| Field | Example | What it does |
|---|---|---|
| Search terms | `airpods`, `oled tv` | Keyword search, same as the site's search box |
| Best Buy URLs | `https://www.bestbuy.ca/en-ca/category/laptops-macbooks/20352` | Product, category or search pages. Any filters and sort order in the URL are kept |
| Category IDs | `20352`, `20001` | The number at the end of a category URL. Whole departments work too. An ID that does not exist gives a free error row (it is never crawled as the whole catalogue) |
| Marketplace sellers | `PC Heaven` | A seller's whole storefront (not case-sensitive) |
| SKUs | `19779374` | A fixed product list. Up to 100 SKUs are checked per request, which makes daily price tracking cheap |
| List stores near postal codes | `M5V 3L9` | One row per nearby store |

Options:

- **Max results** (total) and **Max results per search / category / seller**
- **Province** and **Language**
- **Sort**: best match, price low to high, price high to low, highest rated
- **Full product details** (on by default): brand, model number, UPC, specs, description, images and online stock count
- **Store stock near a postal code**, with the number of nearest stores to check (up to 50)
- **All seller offers**: every seller's price, the buy-box winner and EHF by province
- **Reviews per product**, with the review order
- **Filters**: min and max price, on sale only, clearance only, sold by Best Buy only, and condition (new, open box or refurbished)

Example input:

```json
{
  "searchQueries": ["airpods"],
  "categoryIds": ["20352"],
  "maxItems": 500,
  "region": "ON",
  "includeStoreStock": true,
  "postalCode": "M5V 3L9",
  "includeOffers": true,
  "onSaleOnly": true
}
```

### Output

The output has one row per product. Store lookups add one row per store. A SKU that doesn't exist on bestbuy.ca gets a short `error` row, so you can see what went wrong. Here is a trimmed sample:

```json
{
  "dataType": "product",
  "sku": "18470962",
  "name": "Apple AirPods 4 In-Ear True Wireless Earbuds with USB-C Charging Case",
  "url": "https://www.bestbuy.ca/en-ca/product/apple-airpods-4-in-ear-true-wireless-earbuds-with-usb-c-charging-case/18470962",
  "brand": "APPLE",
  "modelNumber": "MXP63AM/A",
  "upc": "195949688454",
  "category": "Wireless Earbuds & Earphones",
  "price": 149.98,
  "regularPrice": 169.98,
  "isOnSale": true,
  "discountPercent": 11.8,
  "saleStartDate": "2026-09-21T17:32:00.300Z",
  "saleEndDate": "2026-09-25T07:00:00.000Z",
  "ehf": 0,
  "region": "ON",
  "condition": "Brand New",
  "isMarketplace": false,
  "sellerName": "Best Buy Canada",
  "rating": 4.07,
  "ratingCount": 137,
  "onlineAvailability": "InStock",
  "onlineStock": 3753,
  "image": "https://multimedia.bbycastatic.ca/multimedia/products/1500x1500/184/18470/18470962.jpg",
  "specs": { "Product Condition": "Brand New", "Product Line": "AirPods 4", "Truly Wireless": "Yes" },
  "offers": [
    { "sellerName": "Best Buy Canada", "isBuyBoxWinner": true, "price": 149.98, "ehfByProvince": { "BC": 0.55, "QC": 0.55 } }
  ],
  "buyBoxSeller": "Best Buy Canada",
  "storeStock": [
    { "storeId": "259", "storeName": "Best Buy Express Union Station", "city": "Toronto", "distanceKm": 0.56, "quantity": 17 },
    { "storeId": "198", "storeName": "Best Buy Express Eaton Centre Dundas", "city": "Toronto", "distanceKm": 1.39, "quantity": 13 }
  ],
  "storesWithStock": 10,
  "totalStoreQuantity": 282,
  "reviews": [
    { "rating": 5, "title": "Great product", "text": "These AirPods work much better than I expected…", "submittedAt": "2026-02-25T13:39:17.000Z", "isVerifiedPurchaser": false }
  ],
  "source": { "type": "search", "input": "airpods" },
  "scrapedAt": "2026-09-24T03:01:25.451Z"
}
```

Every product row has the same fields in the same order, so CSV and Excel exports line up. A field that doesn't apply is `null`. For example, `storeStock` is `null` when store stock is off.

The dataset has three views: **Products**, **Stock** and **Stores**.

### FAQ

**How many products can I get?**
As many as the listing has. Listings with more than 2,000 products are split automatically. If a listing can't be split cleanly, for example one marketplace seller with thousands of near-identical items, the run collects everything it can reach and says so in the run's status message.

**Which provinces and languages are supported?**
All 13 provinces and territories, in English or French.

**Can I track the same products every day?**
Yes. Put the SKUs in **SKUs** and schedule the actor. Up to 100 SKUs are checked per request, so large lists are cheap to track.

**How does store stock work?**
Turn on **Store stock near a postal code** and enter a postal code. Each product row then gets the units on hand at each of the nearest stores (10 by default, up to 50), plus online shipping and pickup status. Stores are checked in batches, so this adds very few requests.

**Do I need a Best Buy account or cookies?**
No.

**Is it fast?**
With details off, a 14,000-product category takes about 30 seconds. With full details, 1,000 products take about 30 seconds.

**What if some add-on data fails to load?**
The product row is still saved, and only the missing add-on fields are empty. The run's status message says how many lookups failed.

**Is it legal to scrape Best Buy Canada?**
This Actor only collects publicly available data: product listings, prices, stock levels, seller offers, customer reviews and store locations that bestbuy.ca shows to any visitor without an account. Collecting publicly available data is generally legal, but you're responsible for how you use it. When you turn on reviews, results can include reviewers' display names and locations, and that counts as personal data. You must follow privacy laws such as GDPR, PIPEDA and CCPA, as well as Best Buy Canada'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 data bestbuy.ca serves 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

## `searchQueries` (type: `array`):

Keywords to search on bestbuy.ca, e.g. "airpods", "oled tv", "gaming laptop". Each term is scraped separately.

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

bestbuy.ca product, category or search pages (English or French). Filters and sort order in the URL are kept, so you can set up a page in your browser and paste it here.

## `categoryIds` (type: `array`):

Category IDs from the end of a category URL, e.g. 20352 for Laptops & MacBooks (bestbuy.ca/en-ca/category/laptops-macbooks/20352). Whole departments work too, e.g. 20001 for Computers & Tablets.

## `sellerNames` (type: `array`):

Scrape a marketplace seller's whole storefront. Use the seller name exactly as shown on bestbuy.ca, e.g. "PC Heaven".

## `skus` (type: `array`):

Best Buy SKUs (the number at the end of a product URL, e.g. 19779374) or product URLs. Great for tracking prices and stock on a fixed list of products.

## `storePostalCodes` (type: `array`):

Adds one row per Best Buy store near each postal code: address, phone, hours, coordinates, distance and store ID.

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

Hard cap on rows for the whole run. 0 = no cap (a whole category can be tens of thousands of products).

## `maxItemsPerSource` (type: `integer`):

Cap for each search term, category, seller or URL. 0 = no cap.

## `region` (type: `string`):

Prices and environmental handling fees (EHF) are shown for this province.

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

Language of product names, descriptions, specs and URLs.

## `sortBy` (type: `string`):

Order of search and category results. For very large listings (over 2,000 products) the sort applies within each slice.

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

Adds brand, model number, UPC, full specs, description, what's in the box, protection plans, all images and the online stock count. One extra request per product. Turn off for the fastest price-only runs.

## `includeStoreStock` (type: `boolean`):

Adds the exact units on hand at each Best Buy store near the postal code below, plus online shipping status.

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

Canadian postal code, e.g. M5V 3L9. Used when "Store stock" is on.

## `maxStores` (type: `integer`):

How many of the nearest stores to check for stock.

## `includeOffers` (type: `boolean`):

Adds every seller's offer for each product (price, sale dates, warranty, clearance, EHF for every province) and who wins the buy box.

## `maxReviewsPerProduct` (type: `integer`):

Customer reviews to include with each product (0 = none). Reviews come 100 per request.

## `reviewsSort` (type: `string`):

Which reviews come first.

## `minPrice` (type: `integer`):

Only products at or above this current price.

## `maxPrice` (type: `integer`):

Only products at or below this current price.

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

Only products whose current price is below the regular price.

## `clearanceOnly` (type: `boolean`):

Only clearance products.

## `excludeMarketplace` (type: `boolean`):

Skip products sold by marketplace sellers.

## `condition` (type: `string`):

Filter by product condition.

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

How many requests run in parallel. Higher is faster.

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

Apify Proxy (automatic) works well and is the default. Blocked IPs are rotated automatically, with a residential fallback.

## Actor input object example

```json
{
  "searchQueries": [
    "airpods"
  ],
  "maxItems": 50,
  "maxItemsPerSource": 0,
  "region": "ON",
  "language": "en-CA",
  "sortBy": "relevance",
  "includeDetails": true,
  "includeStoreStock": false,
  "maxStores": 10,
  "includeOffers": false,
  "maxReviewsPerProduct": 0,
  "reviewsSort": "relevancy",
  "onSaleOnly": false,
  "clearanceOnly": false,
  "excludeMarketplace": false,
  "condition": "any",
  "maxConcurrency": 20,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

All scraped products and stores.

## `run` (type: `string`):

Status and statistics for 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 = {
    "searchQueries": [
        "airpods"
    ],
    "maxItems": 50,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("yugenox/bestbuy-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 = {
    "searchQueries": ["airpods"],
    "maxItems": 50,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("yugenox/bestbuy-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 '{
  "searchQueries": [
    "airpods"
  ],
  "maxItems": 50,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call yugenox/bestbuy-canada-scraper --silent --output-dataset

```

## MCP server setup

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