- Costco
- Web scraping
- Price data
- Apify
How to Scrape Costco Prices from costco.ca and costco.com
Costco doesn’t publish a product API, so prices have to come out of the catalogue behind the storefront. That catalogue is open to anyone. Skip the location, though, and it hands back an empty list with a green checkmark.
Savvi4 min read
Start here
This input works. Paste it into the Input tab of the Costco Scraper API on Apify, switch to JSON view, and hit Start. Rows land in Output as they arrive.
{
"site": "CABC",
"categories": ["grocery-household"],
"postal_code": "V6B 1V4",
"max_items": 500
}Same run over HTTP. Grab a token from your Apify integrations page:
curl -X POST \
"https://api.apify.com/v2/acts/sunny_eternity~costco-scraper/run-sync-get-dataset-items?token=YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"site": "CABC", "categories": ["grocery-household"], "postal_code": "V6B 1V4", "max_items": 100}'run-sync-get-dataset-items blocks until the run finishes and hands you the rows. Fine for a few hundred products. It times out on the full catalogue, so for anything big, POST to /runs and read defaultDatasetId off the response.
What comes back
One flat record per product. The highlighted fields are the ones I check first:
{ "store": "Costco", "currency": "CAD", // CAD on .ca, USD on .com "name": "Ruffles Potato Chips Variety Pack, 36 × 28 g", "price": 17.49, "was_price": 20.99, "is_on_sale": true, "promotion_validity": "Valid for orders placed 08/03/26 to 08/30/26.", // the dates the offer runs "unit_price": "1008g, $1.74/100g", // derived from the pack size in the title "item_number": "4160094", // shelf-tag number - Costco’s own SKU … 15 more fields}
From Python
pip install apify-client, then:
from apify_client import ApifyClient
client = ApifyClient("YOUR_API_TOKEN")
run = client.actor("sunny_eternity/costco-scraper").call(run_input={
"site": "CABC",
"categories": ["grocery-household"],
"postal_code": "V6B 1V4",
"max_items": 500,
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
print(item["item_number"], item["name"], item["price"])From JavaScript
npm i apify-client, then:
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_API_TOKEN' });
const run = await client.actor('sunny_eternity/costco-scraper').call({
site: 'CABC',
categories: ['grocery-household'],
postal_code: 'V6B 1V4',
max_items: 500,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.length, 'products');.call() waits for the run to finish before it returns, so both snippets block for as long as the scrape takes.
Three things that cost me time
- Empty runs report success. Costco’s catalogue answers with zero products when no location is set, and the status code is still 200. The Actor checks for a location up front and fails loudly instead. If you’re calling Costco directly, assert on row count, because a nightly job that quietly returns nothing is a miserable thing to debug three weeks later.
- Assortment is regional. Price and selection track the province or state rather than the individual warehouse. Two Vancouver warehouses return the same catalogue; Vancouver and Toronto don’t. Match on
item_numberbefore you compare regions, or you’re comparing two different product lists. - Currency follows the storefront. costco.ca returns CAD, costco.com returns USD, and the
currencyfield tells you which. Nothing converts between them for you.
What the fields mean
| Field | What it holds |
|---|---|
price, was_price, is_on_sale | Current price, the pre-sale price, and the discount flag. was_price is null when nothing is struck through. |
promotion_text, promotion_validity | Costco’s own wording ("$3.50 OFF") and the dates it runs. The validity string is how you tell whether a deal you captured last week is still live. |
item_number | The number printed on the shelf tag. This is the key you want for a price history. product_id is a separate catalogue ID used in the URL. |
unit_price, comparable_unit_price | Per-100 g, per-100 ml or per-unit pricing. comparable_unit_price is the numeric one, so sort and compare on that. |
location, location_name | The warehouse the prices were resolved for. Store it alongside the prices, because the region is what makes the number meaningful. |
Costco publishes no unit price and no package-size field, which matters the moment you compare it against another retailer. Both get derived from the product title, which Costco reliably suffixes with the size (, 1 kg, 36 × 28 g, 21-pack). Multipacks collapse to total contents, so that 36 × 28 g variety pack becomes 1008 g priced per 100 g, so a bulk pack lines up against a supermarket one. Titles with no parseable size (gift cards, furniture, apparel) come back null. Treat those as missing rather than zero.
Picking what to scrape
| Input | What it does |
|---|---|
categories | Departments, subcategories, or Costco’s curated lists like New Items and Treasure Hunt. 43 entries in total. Picking a department pulls everything nested under it. |
startUrls | Raw costco.ca or costco.com links. Product pages become single lookups, category pages get scraped in full, search pages run as searches. |
search_terms | Free-text queries like "olive oil", each run separately. Combine them with categories and they switch to filtering those results by keyword instead. |
max_items caps the run and defaults to 1000. Set it to 0 for everything, which is roughly 11,000 products on Costco Canada. Start with one department. And categories: ["all"] pulls appliances, electronics and furniture too, so name the grocery departments if you want a grocery basket.
{ "site": "CABC", "categories": ["grocery-household"], "postal_code": "M5H 2N2", "on_sale_only": true }{ "site": "USBC", "latitude": "47.6062", "longitude": "-122.3321", "search_terms": ["olive oil"] }{ "site": "CABC", "categories": ["all"], "postal_code": "V6B 1V4", "max_items": 0 }If you want Costco priced against the regular supermarkets rather than on its own, the grocery data API covers 16 Canadian chains and returns the same field names.
Frequently asked questions
Do I need a Costco membership to scrape prices?
No. The catalogue the Actor reads is the public one, so it works without an account.
Is there an official Costco API?
No, and that’s why this is a scraping job. The prices live in the catalogue behind costco.ca and costco.com, and that’s where they have to come from.
Can I scrape Costco US as well as Costco Canada?
Yes. Set site to USBC and pass latitude and longitude, which is how you pick a US warehouse. Prices come back in USD.
Where does the unit price come from if Costco does not publish one?
It’s derived from the product title, which Costco reliably suffixes with the pack size. Multipacks collapse to total contents, so a title reading 36 × 28 g becomes 1008 g priced per 100 g.
Why is comparable_unit_price null on some rows?
The title had no size in it. Gift cards, furniture and apparel all land here. You get null instead of a guessed number, so treat those rows as missing rather than zero.
How many products are in the full Costco catalogue?
Around 11,000 for Costco Canada if you scrape every category with max_items set to 0. A single department is much smaller and much faster.
How do I track Costco prices over time?
Schedule the Actor and diff price by item_number between runs. The shelf-tag number is the stable key; the catalogue ID in the URL is not. Keep location in the key if you scrape more than one region, since assortment differs by province.
Is scraping Costco legal?
Public product and price information is generally legal to scrape. Costco’s terms of service and the law where you operate are still your responsibility, and since the catalogue API is the same one the storefront calls, keeping request volume modest is both polite and practical.
Try it on your own data
The Costco Scraper API runs on Apify. Paste an input, hit Start, and see what comes back before you write any code against it.
Get the Costco Scraper APIOther retailers
- Canadian Grocery Price Comparison API16 chains matched, scored and ranked in a single call.
- Loblaws, No Frills & Superstore API12 Loblaw banners with PC Optimum offers and multi-buy deals.
- Save-On-Foods & PriceSmart APIFour Pattison Food Group chains across BC and the prairies.
- T&T Supermarket APICanada’s largest Asian grocery chain, in BC, AB, ON and QC.
They all share the same field names. See them side by side on the grocery data API page, with a sample record for each.
Keep reading
- How to Compare Grocery Prices Across 16 Canadian Chains in One CallThis one isn’t a scraper, it’s a matcher. How the confidence scoring works, how to filter on it, and how to get basket totals per store.
- How to Scrape Loblaws, Superstore and No Frills PricesTwelve banners share one input and one output shape. Which banner covers your region, the promo fields only this chain exposes, and how to join across banners.