# SAQ Scraper — Quebec Wine & Spirits Prices, Sales & Store Stock (`yugenox/saq-scraper`) Actor

Scrape the SAQ (saq.com) catalogue: wine, spirits, beer and cider with prices, sales, bonus points, UPC, producer, import agent, grapes, tasting notes, ratings and store-level stock with quantities. Search, categories, SAQ codes or nearest stores. English or French, no login.

- **URL**: https://apify.com/yugenox/saq-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 $2.00 / 1,000 result (product or store)s

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

## SAQ Scraper — Quebec Wine & Spirits Prices, Sales & Store Stock

Scrape the full **SAQ** catalogue (Société des alcools du Québec, [saq.com](https://www.saq.com)): wine, spirits, beer, cider, sake and more — with **current and regular prices, rebates, bonus points, UPC codes, producers, import agents, grapes, vintages, tasting profiles, ratings** and **store-level stock with exact quantities**.

Search like the saq.com search box, list whole categories, track a watch-list of SAQ codes, or pull the store directory. English or French. No SAQ account needed.

### What you can do with it

- **Price & sale tracking** — run it daily on a list of SAQ codes or a category and see price drops, rebates and bonus-point offers as they appear.
- **"What's in stock near me?"** — products available at the stores nearest a postal code, or at specific stores, with the quantity on each shelf.
- **Trade & import-agency intelligence** — every listing with its producer, import agent, listing type (continuous supply, speciality lot, private import), status and UPC. Watch competitors' listings and delistings.
- **Restaurants & retailers** — find where a wine is still available and how many bottles each store holds.
- **Cross-province price comparison** — match on UPC against other Canadian liquor boards.
- **Wine apps & datasets** — grapes, appellations, alcohol and sugar content, SAQ taste tags, aromas, body, acidity, serving temperature and ageing potential.

### Input

| Field | What it does |
|---|---|
| **Search terms** | Keywords, e.g. `merlot`, `gin`, `Tanqueray`, `Rioja`. Each term runs its own search. |
| **Categories** | Names or paths in English or French: `Red wine`, `vin blanc`, `Spirit`, `products/spirit/gin`. |
| **saq.com URLs** | Category, search or product pages copied from saq.com. |
| **SAQ codes** | Specific products by SAQ code (e.g. `11184968`). |
| **Language** | `en` or `fr` — names, types, countries and tasting notes in that language. |
| **Availability** | Any, in stock (online or in store), online only, in stores only. |
| **On sale only** | Only products with a current rebate. |
| **Min / max price** | Price band in CAD. |
| **Countries of origin** | e.g. `Italy`, `france`, `USA`, `Italie` — matched to SAQ's spelling in either language, any case or accents. |
| **Location** | Postal code (`H2X 1Y4`), a city (`Sherbrooke, QC`) or `latitude,longitude`. |
| **Nearest stores** | Only products in stock at the N stores nearest the location. |
| **Store IDs** | Only products in stock at these stores. |
| **Include store quantities** | Adds each product's stores (nearest first) with the exact quantity on hand. |
| **Mode: Stores** | Lists SAQ stores instead of products (address, phone, GPS, hours, services), optionally the N nearest. |
| **Max results / per query** | Caps for the run and for each search, category or URL. |

Filters used on their own (for example just *On sale only*) apply to the whole catalogue.

#### Example inputs

Everything on sale at the three stores nearest a postal code:

```json
{ "onSaleOnly": true, "location": "H2X 1Y4", "nearestStores": 3 }
```

French red wines between $15 and $20, in French:

```json
{ "categories": ["vin rouge"], "countries": ["France"], "minPrice": 15, "maxPrice": 20, "language": "fr" }
```

Daily watch-list with shelf quantities near Quebec City:

```json
{ "skus": ["11184968", "10699644", "12592980"], "includeStoreInventory": true, "maxStoresPerProduct": 10, "location": "G1R 4P5" }
```

### Output

One row per product (store rows in Stores mode). Example (trimmed):

```json
{
  "sku": "11184968",
  "name": "Rocca di Montemassi Le Focaie Maremma Toscana",
  "url": "https://www.saq.com/en/11184968",
  "imageUrl": "https://www.saq.com/media/catalog/product/1/1/11184968-1_1691165583.png",
  "productType": "Red wine",
  "price": 16.5,
  "regularPrice": 17.5,
  "onSale": true,
  "discountPercent": 5.71,
  "latestOffers": ["Bonus point", "Rebates"],
  "bonusPoints": 500,
  "inStock": true,
  "availableOnline": true,
  "availableInStores": true,
  "storeCountInStock": 127,
  "storeIdsInStock": ["23002", "23008", "…"],
  "country": "Italy",
  "region": "Tuscany",
  "appellation": "Maremma Toscana",
  "sizeMl": 750,
  "alcoholPercent": 13,
  "sugarGramsPerLitre": 2.2,
  "tasteTag": "Aromatic and supple",
  "aromas": ["pastry", "wild fruit", "sweet spices", "vanilla"],
  "tasting": { "body": "medium-bodied", "acidity": "present", "sweetness": "dry", "oak": "dominant", "servingTempMinC": 15, "servingTempMaxC": 17, "agingPotential": "Drink now or keep 6 years following the vintage" },
  "producer": "C.D.A. S.A.R.L.",
  "importAgent": "Mosaiq Vins et Spiritueux Inc.",
  "upc": "08002235023313",
  "listingType": "Current continuous supply",
  "specialFeatures": ["Organic product"],
  "ratingPercent": 88,
  "reviewCount": 90,
  "storeInventory": [
    { "storeId": "23028", "name": "Faubourg Sainte-Catherine", "city": "Montréal", "phone": "514 935-5127", "distanceKm": 1.81, "quantity": 6, "openingHours": [{ "day": "Monday", "open": "13:00", "close": "20:00" }] }
  ]
}
```

Also included: `color`, `subRegion`, `designation`, `classification`, `grapes`, `grapeShares`, `vintage`, `unitsPerPack`, `spiritType`, `closure`, `container`, `productStatus`, `quebecProduct`, `newArrival`, `depositFee`, `onlineQuantityLimit`, `description`, `availableSince`, `language`, `source`, `scrapedAt`.

Download as JSON, CSV, Excel or via API. The dataset has three views: **Products**, **Trade** (producer, agent, UPC, listing) and **Stores**.

### Speed

About 250 products per request — a 1,000-product category takes seconds, and the whole catalogue (~26,700 products) runs in a few minutes. Store quantities add one request per product.

### FAQ

**Do I need an SAQ account?** No.

**How fresh is the data?** Live — prices, sales and stock are read at run time.

**Why is `grapes` or `tasteTag` empty for some products?** SAQ only publishes them for some products (e.g. wines have grapes, beers don't).

**What do `inStock`, `availableOnline` and `availableInStores` mean?** `availableOnline`: can be ordered on saq.com. `availableInStores`: on the shelf in at least one store (`storeCountInStock` stores). `inStock`: either.

**How do I get store IDs?** Run the Stores mode (optionally with a location and *Nearest stores*) — each row has a `storeId`.

**Can I get French data?** Yes — set *Language* to `fr`. Category names and paths are translated automatically.

**Is there a limit?** Only the ones you set. Very large queries are split automatically so nothing is cut off.

**Is it legal to scrape SAQ?** This Actor only collects publicly available data: product listings, prices, sales, stock levels and store locations that anyone can see on saq.com 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 the SAQ'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 SAQ 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`):

Products: wine, spirits, beer, cider… with prices and stock. Stores: the SAQ store directory (address, phone, hours, services, GPS), optionally only the stores nearest to a location.

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

Keywords, exactly like the saq.com search box: a grape (merlot), a product (gin, rosé), a producer or brand (Tanqueray), a region (Rioja)… Each term runs its own search.

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

Category names or paths, in English or French: "Red wine", "vin blanc", "Spirit", products/spirit/gin, produits/biere… Every product in the category is listed (combine with the filters below).

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

Paste saq.com pages: a category (https://www.saq.com/en/products/wine/red-wine), a search (https://www.saq.com/en/catalogsearch/result/?q=merlot) or a product (https://www.saq.com/en/11184968).

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

Look up specific products by their SAQ code (the number in the product URL, e.g. 11184968). Ideal for daily price and stock tracking of a watch-list.

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

Language of names, product types, countries and tasting notes. Category paths and names are translated automatically.

## `availability` (type: `string`):

Only products that can be bought online, in stores, or either.

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

Only products with a current SAQ rebate (price below the regular price).

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

Lowest price, in Canadian dollars.

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

Highest price, in Canadian dollars (inclusive).

## `countries` (type: `array`):

Only products from these countries, spelled as on saq.com in the chosen language (English: Italy, France, United States… French: Italie, France, États-Unis…).

## `location` (type: `string`):

A Canadian postal code (H2X 1Y4), a city (Sherbrooke, QC) or "latitude,longitude". Used to find the nearest stores, and to sort each product's store stock by distance.

## `nearestStores` (type: `integer`):

Products mode: only products in stock at the N stores nearest to the location. Stores mode: list only the N nearest stores. 0 = off.

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

Only products in stock at any of these stores (5-digit SAQ store IDs, e.g. 23134 — run the Stores mode to get them).

## `includeStoreInventory` (type: `boolean`):

Add the stores holding each product with the exact quantity on hand, address, phone and distance (nearest first). One extra request per product — billed per store row.

## `maxStoresPerProduct` (type: `integer`):

How many stores (nearest first) to list per product when store quantities are included.

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

Maximum number of products (or stores) for the whole run. Leave empty for no limit — the full SAQ catalogue is about 26,700 products.

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

Cap for each individual search term, category or URL. Leave empty for no per-query limit.

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

How many requests run at the same time.

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

Apify Proxy is recommended (automatic selection works well).

## Actor input object example

```json
{
  "mode": "products",
  "searchTerms": [
    "merlot"
  ],
  "language": "en",
  "availability": "any",
  "onSaleOnly": false,
  "nearestStores": 0,
  "includeStoreInventory": false,
  "maxStoresPerProduct": 10,
  "maxItems": 50,
  "maxConcurrency": 5,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

All scraped products or 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 = {
    "searchTerms": [
        "merlot"
    ],
    "maxItems": 50,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

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

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

```

## MCP server setup

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