# Caskers Scraper (`crawlerbros/caskers-scraper`) Actor

Scrape Caskers (caskers.com) - US craft wine & spirits online retailer. Search by keyword, browse spirit categories, fetch products by ID or URL, and collect product reviews. Includes prices, bottle size, ABV/proof, tasting notes, ratings, images, and availability.

- **URL**: https://apify.com/crawlerbros/caskers-scraper.md
- **Developed by:** [Crawler Bros](https://apify.com/crawlerbros) (community)
- **Categories:** E-commerce, Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Caskers Scraper

Scrape **Caskers (caskers.com)** — one of the largest US online retailers of craft wine & spirits — and get product-level data for whiskey, bourbon, scotch, tequila, rum, vodka, gin, cognac, mezcal, champagne, non-alcoholic drinks and more. Search by keyword, browse spirit categories, fetch products by ID or URL, and pull per-product customer reviews. Includes prices, bottle size, proof/ABV, tasting notes, ratings, review counts, images and stock status. HTTP-only via the public website — no login, no API key, no proxy required.

### Data Source

> **Replacement actor:** this actor replaces the former **Total Wine & More (totalwine.com)** slot. Total Wine was hard-blocked from Apify cloud egress (403 on every path — product API, search, homepage, direct and US-proxy requests). The replacement platform is **Caskers (caskers.com)**, a US craft wine & spirits online retailer in the same category (wine/liquor retail). All pages are served as server-side-rendered Magento 2 HTML over plain HTTPS and are reachable from Apify cloud datacenter egress without a proxy.

Data comes from three public HTML surfaces:

- **Category pages** — `https://www.caskers.com/<category>/` (e.g. `/spirits/`, `/spirits/whiskey/bourbon/`, `/non-alcoholic-drinks/`). Each page renders 20–80 product cards plus the total result count. Pagination via `?p=`, sort via `?product_list_order=&product_list_dir=`, price bands via `?price=`, brand via `?brand_dropdown=`.
- **Search results** — `https://www.caskers.com/catalogsearch/result/?q=<keyword>` — the same product-card grid, with the same pagination and sort controls.
- **Product detail pages** — `https://www.caskers.com/<product-slug>/` — rich SSR markup (bottle size, proof/ABV, description, tasting notes, rating summary) plus a `schema.org/Product` JSON-LD block (brand, SKU, GTIN, aggregate rating, offer price/availability, US states the item cannot ship to, and sample reviews).
- **Reviews endpoint** — `https://www.caskers.com/review/product/listAjax/id/<productId>/` — per-product customer reviews (title, body, Quality/Value/Price star ratings, author, date).

Caskers has no wine/beer categories (those paths return 404); the catalog is spirits-focused. The REST API (`/rest/`) is Cloudflare-protected and **not** used.

### What this actor does

- **Five modes:** `search`, `byCategory`, `byProductIds`, `byUrls`, `productReviews`
- **Complete catalog coverage:** ~11,000 product URLs are exposed via the public sitemap; category totals run to 10,000+ spirits
- **Rich product records:** price, bottle size, proof/ABV, tasting notes, description, ratings, review counts, gallery images, stock status
- **Filters:** sort order, price band, brand
- **Typed error records:** failed lookups surface as `recordType: "error"` entries with the reason
- **Empty fields are omitted**

### Output per product

- `productId`, `sku` — Caskers entity ID (use for `byProductIds` / `productReviews`) + SKU
- `name`, `brand`, `category`
- `price`, `currency` — final USD price
- `size` — bottle size (e.g. `750mL`)
- `proof`, `abv` — e.g. `80`, `40.0`
- `availability` — `In stock` / `Out of stock`
- `rating`, `reviewCount` — customer rating (0–5) and count
- `description`, `tastingNote`
- `gtin`
- `restrictedStates[]` — US states where this item cannot ship
- `imageUrl`, `imageUrls[]` — CDN product images (cdn.caskers.com)
- `badges[]` — e.g. `Limited-Edition`, `PREORDER-NOW`
- `sourceUrl`, `recordType: "product"`, `scrapedAt`

### Output per review

- `productId`, `productUrl`
- `reviewTitle`, `reviewContent`, `reviewAuthor`, `reviewDate`
- `ratings` — per-dimension star scores (`Quality`, `Value`, `Price`)
- `overallRating` — average of the dimensions
- `sourceUrl`, `recordType: "review"`, `scrapedAt`

### Output per error

- `errorType` — `invalidInput` / `notFound` / `blocked` / `rateLimited` / `httpError` / `parseError`
- `subject` — the input that failed
- `message`, `sourceUrl`
- `recordType: "error"`, `scrapedAt`

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `mode` | string | `search` | `search` / `byCategory` / `byProductIds` / `byUrls` / `productReviews` |
| `searchQuery` | string | `whiskey` | Free-text keyword (mode=search) |
| `category` | string | `spirits` | Caskers category path (mode=byCategory) |
| `sortBy` | string | `relevance` | `relevance` / `nameAsc` / `nameDesc` / `priceAsc` / `priceDesc` |
| `priceRange` | string | `any` | `any` / `0-50` / `50-100` / `100-200` / `200-500` / `500-plus` (mode=byCategory) |
| `brandId` | int | – | Caskers brand\_dropdown ID, e.g. `3369` = Hennessy (mode=byCategory) |
| `productIds` | array | – | Numeric product IDs (mode=byProductIds / productReviews) |
| `productUrls` | array | – | Full product URLs (mode=byUrls) |
| `maxReviewsPerProduct` | int | `10` | Cap on review records per product (mode=productReviews) |
| `maxItems` | int | `50` | Hard cap on emitted records (1–1000) |
| `limit` | int | `20` | Products per page (`20` / `40` / `80`) |
| `proxyConfiguration` | object | off | Optional Apify AUTO proxy; not needed for normal use |

#### Example: search whiskey, cheapest first

```json
{
  "mode": "search",
  "searchQuery": "whiskey",
  "sortBy": "priceAsc",
  "maxItems": 20
}
```

#### Example: browse tequila in a price band

```json
{
  "mode": "byCategory",
  "category": "spirits/tequila",
  "priceRange": "50-100",
  "sortBy": "nameAsc",
  "maxItems": 40
}
```

#### Example: fetch specific products by ID

```json
{
  "mode": "byProductIds",
  "productIds": ["7582", "1023"]
}
```

> Product IDs are the numeric `productId` values that appear on scraped product records (or `data-product-id` on category cards) — not SKUs, which live in a different namespace.

#### Example: collect reviews for a product

```json
{
  "mode": "productReviews",
  "productIds": ["1023"],
  "maxReviewsPerProduct": 10
}
```

### Use cases

- **Price & product intelligence** — track whiskey, bourbon and tequila prices across Caskers' 10,000+ item catalog
- **Category benchmarking** — compare product counts, price bands and ratings per spirit type
- **Market research** — analyze tasting notes, ABV distribution and brand presence in the US craft spirits market
- **Retail dropship / aggregation** — enrich a product feed with prices, sizes, images and availability
- **Review mining** — pull Quality/Value/Price customer ratings per product for sentiment analysis

### FAQ

**Is this affiliated with Caskers?**
No. This is an independent third-party actor that reads publicly served HTML pages from caskers.com.

**Do I need a proxy or account?**
No. All surfaces are public server-rendered HTML. The optional Apify AUTO proxy exists as a fallback only if Caskers starts rate-limiting datacenter IPs.

**Why is Total Wine & More mentioned in the README?**
This actor replaced the Total Wine & More scraper slot: totalwine.com hard-blocks all Apify cloud egress (403 on every endpoint), so the slot was repurposed to Caskers, a US wine & spirits retailer in the same category.

**How fresh is the data?**
Every run fetches pages live — prices, availability and ratings are as of the request moment. Caskers updates its catalog continuously.

**Why are some products missing fields?**
Fields are only emitted when the page actually contains them (e.g. a product with zero reviews has no `rating`/`reviewCount`). Empty fields are always omitted.

**What does `restrictedStates` mean?**
Caskers publishes the US states to which an item cannot ship (from the product's schema.org offer data). Products that ship everywhere simply omit the field.

**Are there rate limits?**
No documented API limits — the actor keeps polite delays between requests and retries with backoff on 429/5xx, auto-escalating on 403.

**Which categories are available?**
Whiskey (bourbon, scotch, Irish, rye, Japanese, moonshine, American single malts), vodka, tequila, rum, gin, brandy, cognac, mezcal, absinthe, liqueur, vermouth, non-alcoholic drinks, champagne & sparkling, plus curated lists (best sellers, limited editions, new arrivals, rare finds, gift sets, price-sliced "under $50/100/200" pages).

**How reliable is `sortBy: priceAsc` / `priceDesc`?**
Caskers sometimes prepends a small "featured products" widget to the first category page, which arrives unsorted regardless of the sort parameter. To guarantee the documented sort contract, the actor applies a client-side final sort by price on every listing run — `priceAsc`/`priceDesc` output is always price-ordered (records without a visible price sort last).

# Actor input Schema

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

What to fetch.

## `searchQuery` (type: `string`):

Free-text keyword, e.g. `whiskey`, `single malt`, `mezcal`, `bourbon`.

## `category` (type: `string`):

Caskers category path to browse.

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

Result ordering (used by mode=search and mode=byCategory).

## `priceRange` (type: `string`):

Restrict category results to a price band.

## `brandId` (type: `integer`):

Restrict category results to one brand (Caskers brand\_dropdown ID, e.g. 3369 = Hennessy). The actor resolves the ID to the brand name from the site's own brand page and filters records client-side, so it is honored on every category. Optional; leave empty for all brands.

## `productIds` (type: `array`):

Caskers numeric product IDs (e.g. `7582`, `1023`). Use the `productId` field from any scraped product record, or the data-product-id of a category card.

## `productUrls` (type: `array`):

Full Caskers product URLs (e.g. `https://www.caskers.com/bushmills-12-year-old-single-malt-irish-whiskey-750/`).

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

Cap on review records per product. Pass the numeric product ID from any scraped `productId` field (e.g. `1023`). Caskers shows up to 9 reviews per product.

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

Hard cap on emitted records (products or reviews).

## `limit` (type: `integer`):

How many products Caskers renders per category/search page.

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

Optional. Caskers serves pages to datacenter IPs without a proxy; enable Apify AUTO proxy only if you hit blocks.

## Actor input object example

```json
{
  "mode": "search",
  "searchQuery": "whiskey",
  "category": "spirits",
  "sortBy": "relevance",
  "priceRange": "any",
  "productIds": [],
  "productUrls": [],
  "maxReviewsPerProduct": 10,
  "maxItems": 50,
  "limit": 20,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

Dataset containing all scraped Caskers products and reviews.

# 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 = {
    "mode": "search",
    "searchQuery": "whiskey",
    "category": "spirits",
    "sortBy": "relevance",
    "priceRange": "any",
    "productIds": [],
    "productUrls": [],
    "maxReviewsPerProduct": 10,
    "maxItems": 50,
    "limit": 20,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("crawlerbros/caskers-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 = {
    "mode": "search",
    "searchQuery": "whiskey",
    "category": "spirits",
    "sortBy": "relevance",
    "priceRange": "any",
    "productIds": [],
    "productUrls": [],
    "maxReviewsPerProduct": 10,
    "maxItems": 50,
    "limit": 20,
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("crawlerbros/caskers-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "mode": "search",
  "searchQuery": "whiskey",
  "category": "spirits",
  "sortBy": "relevance",
  "priceRange": "any",
  "productIds": [],
  "productUrls": [],
  "maxReviewsPerProduct": 10,
  "maxItems": 50,
  "limit": 20,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call crawlerbros/caskers-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=crawlerbros/caskers-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/8dXlx88FtjtQgQ9vn/builds/1D9yBFpa1uQqjmFud/openapi.json
