# Sam's Club Scraper - Product & Price Tracker (USA) (`superslowsloth/samsclub-products`) Actor

Sam's Club scraper: search terms or category pages to one row per product with USD price, was-price and discount percent, unit price, price range of options, rating and reviews, stock, pickup/shipping flags, member purchase limit and image. For warehouse-club price watching.

- **URL**: https://apify.com/superslowsloth/samsclub-products.md
- **Developed by:** [Superslow Sloth](https://apify.com/superslowsloth) (community)
- **Categories:** E-commerce, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.62 / 1,000 product scrapeds

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

## Sam's Club scraper: product and price tracker (USA)

This Sam's Club scraper searches [Sam's Club](https://www.samsclub.com) (the US
warehouse club) by keyword, or reads a category page, and returns one flat row
per product: the current price in **USD**, the was-price, the discount in
dollars and percent, the per-unit price, the price range of a product's
options, rating and review count, stock, whether the club ships, offers pickup
or delivers it, the member order limit, the promotion texts, the product link
and the photo. Run it on a schedule and diff the dataset to get a **warehouse
club price and markdown watch**: which TVs, grocery lines or tires were
reduced since last week, and by how much.

No account, no API key, no login, no browser. One request returns about 45
products.

### Input

| Field | Notes |
|---|---|
| `searchTerms` | One or more search terms, one per line (`paper towels`, `tv`, `coffee`). A term Sam's Club recognises is sent to its category list and that whole list is read (`shoes` goes to `/browse/shoes/1090111`); any other term is read as search results. A term it sends to a page with no product list (`tires`, `grill`, `toys`, `membership`) fails at no charge. A product found by two terms is returned once, under the first. |
| `startUrls` | Sam's Club pages to read in full: a category (`https://www.samsclub.com/browse/televisions/1087`) or a search URL (`/search?q=tv`, read as that search term). A filter copied from the site (`?facet=Brand%3ASamsung`, or `cat_id=1087` on a search) is kept; every other parameter is dropped. `/b/<name>/<id>` links work too. Product pages (`/ip/...`) and `/cp/...` landing pages are not accepted. Give terms, URLs or both. |
| `maxItems` | Products to read per term or page (default 100, up to 10,000). About 45 per page. |
| `onlyOnSale` | Return only products that are reduced (`on_sale`). Products that are not reduced are read, not returned and not charged, and do not count toward `maxItems`. |
| `sort` | `relevance` (default), `price-asc`, `price-desc`, `top-selling`: the choices of the site's own sort menu. |
| `proxyConfiguration` | Apify datacenter proxy by default. |

Example input:

```json
{
  "searchTerms": ["paper towels", "tv"],
  "maxItems": 200,
  "onlyOnSale": true,
  "sort": "price-asc"
}
```

### Output

Prices are numbers in US dollars; `currency` is `USD` (every page measured).
The club is the default one the site assigns to a visitor with no account, so
prices, stock and the pickup/delivery flags are that club's, not the club
nearest you (a `stores` parameter was tried and ignored by the server).

| Field | Notes |
|---|---|
| `query`, `position` | The search term or page URL that first found the product, and its place in Sam's Club's list for that source and sort (1 is first). Positions count every card of the walk, sponsored ones included, so they are not exactly organic ranks. |
| `sponsored` | True when the site marked the card as a sponsored placement. Sponsored cards repeat on every page of a search; a product is returned once, under its first sighting, so a product first seen in a sponsored slot says true even if it also sits in the organic list. A sorted search (any sort but relevance) has no sponsored cards. |
| `product_id`, `url` | Sam's Club's item id (`15505523006`, always a string) and the product link. |
| `name`, `product_type` | As the page shows them. `product_type` is the site's own type (`Televisions`) and is null if the page sends none. |
| `category_ids` | The department path, outermost first (`["1086", "1087", "1088"]`). Any id works as `/browse/<name>/<id>`. |
| `price` | The current price of the option the card shows. **Null when the card says "See price in cart"** (`price_visibility` is `in_cart`). |
| `price_range_min`, `price_range_max` | For a product with options priced differently ("Options from $499.00 - $899.00"): the lowest and the highest option price. `price` is the default option, not necessarily the lowest. An open-ended "From $7.41" gives a floor and no top. Both null for a single-price product. |
| `original_price` | The was-price. **Null when the product is not reduced**: the page sends none. |
| `on_sale` | True when `original_price` is above `price`. |
| `discount_amount`, `discount_percent` | Dollars off as the page sends them, and `(1 - price / original_price) * 100` to one decimal, worked out from the prices. **Null when not on sale**, never 0. |
| `unit_price` | The per-unit price text as shown (`$0.31/oz`). Null when the card shows none (many non-grocery lines). |
| `price_visibility` | `sign_in` when the card says to sign in to see the price (the list still carries a figure, in `price`), `in_cart` when it says "See price in cart" (then `price` is null). Null for an ordinary price. |
| `promotions` | The promotion texts on the card (`$40 off`, `Earn $15 Sam's Cash`). Empty list when there are none. |
| `has_options` | True when the product has options (sizes or colours) that are separate sellable variants. |
| `order_limit` | The most a member may buy per order. Null when the page sends none. |
| `rating`, `review_count` | The average star rating (two decimals) and how many reviews it is from. **Null for an unrated product**; `review_count` is 0 when the page says 0 reviews and `rating` is then null too. |
| `availability`, `in_stock` | The page's availability value as sent (`IN_STOCK`, `OUT_OF_STOCK`) and a boolean for it. |
| `fulfillment` | Any of `shipping`, `pickup`, `delivery`, from the card's badges. Empty for a product the club does not currently fulfil: an out-of-stock card carries none. |
| `snap_eligible` | True when the card shows SNAP eligibility. Null when the page sends no flag. |
| `image_url` | The photo, a JPEG on `i5.samsclubimages.com` with the page's size parameters removed. |

Example row (real values, 2026-10-17):

```json
{
  "query": "tv",
  "position": 3,
  "sponsored": false,
  "product_id": "15505523006",
  "name": "VIZIO 50\" Class Quantum 4K QLED Smart TV",
  "url": "https://www.samsclub.com/ip/VIZIO-50-Class-Quantum-4K-QLED-Smart-TV/15505523006",
  "image_url": "https://i5.samsclubimages.com/asr/d412384f-1ad9-4ecd-b48f-13cb5ca85760.a63e0a539f4e7c077f8d70c2c518f556.jpeg",
  "product_type": "TV & Monitor Mounts",
  "category_ids": ["100001", "1086", "1087", "1088", "15190201"],
  "currency": "USD",
  "price": 208.0,
  "price_range_min": null,
  "price_range_max": null,
  "original_price": 248.0,
  "on_sale": true,
  "discount_amount": 40.0,
  "discount_percent": 16.1,
  "unit_price": null,
  "price_visibility": null,
  "promotions": ["$40 off"],
  "has_options": false,
  "order_limit": 5,
  "rating": 4.37,
  "review_count": 1766,
  "availability": "IN_STOCK",
  "in_stock": true,
  "fulfillment": ["shipping", "pickup", "delivery"],
  "snap_eligible": false
}
```

#### Honest nulls

A field the page did not send is `null`, never `0` or `""`. `promotions`,
`category_ids` and `fulfillment` are empty lists when there are none.

- **No brand.** The list sends none (the brand is in the product name), so the
  rows do not carry one.
- **No stock levels.** `in_stock` says what the card says, nothing more.
- **`price` can be null.** A few cards say "See price in cart"; the price is
  not in the list at all.

### Pricing

Pay per event:

- **$0.002** per run (`actor-start`), charged only after your input is
  validated, so a run that fails on bad input costs nothing.
- **$0.00062** per product row written (`product-scraped`), i.e. $0.62 per
  1,000 products. A row that is not written is not charged: rows filtered by
  `onlyOnSale`, and repeats of a product another term already returned, are free.

### How it fetches

It reads the product list that samsclub.com's own search and category pages
embed in their page data. Measured 2026-10-17: HTTP 200 HTML (0.6 to 0.9 MB a
page) from a plain home connection and from Apify datacenter addresses, no key
or cookie needed. A refusal (403, 429, 5xx), a 200 that is not a Sam's Club page
(the site's "Let us know you're not a robot" page) or whose list did not load,
or a dropped connection is treated as the address being blocked, and the actor
rotates to a fresh one and retries.

Things worth knowing, all measured:

- **Page 1 is asked for with no `page` parameter.** A request for `page=1`
  from a client with no cookie yet is redirected to the bot-check page every
  time; `page=2` and no `page` are not. The actor never sends `page=1`.
- **The sort is checked, not trusted.** Sam's Club ignores a sort name it does
  not know (`best_seller`, `rating_high`, `new`...) and answers in relevance
  order. The page says which sort it applied, and a run that did not get the
  sort it asked for fails that source rather than return the wrong order. The
  price sorts follow the current price, but not to the cent: the order comes from
  the site's search index, whose price can differ from the card's. In 90 `tv`
  products `price_low` had no pair out of order and `price_high` one or two
  (a card asking you to sign in for its price at a page boundary; 1198.00 listed
  after 1197.99).
- **A search does not always return search results.** `shoes` is redirected to
  `/browse/shoes/1090111`; `tires`, `grill`, `toys`, `iphone` and `gift card`
  to `/cp/...` landing pages, `membership` to the join page. The actor reads
  whatever list the site lands on, all its pages; a landing page with no list
  fails at no charge. `query` stays the term you typed. The redirect drops the
  sort, so the actor reads the first page again from the list when you asked
  for one.
- **Depth.** About 45 products per page and no page-size setting that behaves
  (`ps` changes the size unpredictably, so it is not used). The deepest list
  measured has 85 pages (a department of 3,807 products). A page past the last
  holds only the five sponsored cards every page carries, and the actor stops
  there; the last page is also recognised from `maxPage`. At most 10,000
  products per term or page.
- **Totals drift.** The same search reported 136 and then 140 results, because
  the count includes the sponsored cards.
- **Filters.** `facet=Brand:Samsung` (or `Brand:Samsung||Brand:LG`) and
  `cat_id=1087` narrow a result; a second `facet=` is ignored by the site. They
  are kept from a start URL. An unknown facet value leaves nothing but the
  sponsored cards, which are not returned.

If Sam's Club changes its page data, the run fails with a message instead of
returning empty rows.

# Actor input Schema

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

What you would type into the Sam's Club search box, one term per line (for example paper towels, tv or coffee). Each term is read separately. A term Sam's Club recognises is sent to its category list and that whole list is read (shoes goes to /browse/shoes/1090111); a term it sends to a page with no product list (tires, grill, toys, membership) fails at no charge. A product that two terms both find is returned once, under the first.

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

Sam's Club pages to read in full, for example https://www.samsclub.com/browse/televisions/1087 or https://www.samsclub.com/search?q=tv\&facet=Brand%3ASamsung. A filter copied from the site (a facet= parameter, or cat_id on a search) is kept; other parameters are dropped. Use a /browse/<name>/<id> category or a /search?q=... URL: /cp/ landing pages, product pages (/ip/...) and the membership pages are not accepted. Leave empty to use search terms only.

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

Read at most this many products for each search term or page. Sam's Club lists about 45 per page, so 100 reads three pages. Up to 10,000 are accepted.

## `onlyOnSale` (type: `boolean`):

Return only products whose current price is below their was-price (the on_sale field). Products that are not reduced are read but not returned and not charged, and do not count toward Max products.

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

The order Sam's Club lists the products in, the same choices as its own sort menu. 'Relevance' is its default. The price sorts follow the current price of each product, but not to the cent: the site orders by its search index, so an occasional pair is out of order. A sorted search has no sponsored cards.

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

Use Apify's automatic datacenter proxy (the default). samsclub.com answered Apify datacenter addresses with HTTP 200 when this actor was built; the residential group was not needed. A refused request is retried from a fresh address.

## Actor input object example

```json
{
  "searchTerms": [
    "paper towels"
  ],
  "startUrls": [],
  "maxItems": 100,
  "onlyOnSale": false,
  "sort": "relevance",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `items` (type: `string`):

No description

# 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": [
        "paper towels"
    ],
    "startUrls": [],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("superslowsloth/samsclub-products").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": ["paper towels"],
    "startUrls": [],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("superslowsloth/samsclub-products").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": [
    "paper towels"
  ],
  "startUrls": [],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call superslowsloth/samsclub-products --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,superslowsloth/samsclub-products"
        }
    }
}
```

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/q6PlekZ9FRKRVo2Pg/builds/4RfJZrG2rBkE4rKgg/openapi.json
