# Shopify Products Scraper & Price Monitor + WooCommerce (`nat-labs/shopify-products-scraper`) Actor

Every product of any Shopify or WooCommerce store: prices, sale prices, stock, variants, SKUs, images. Price & stock change alerts (new, removed, cheaper, back in stock). Whole catalogs, collections, many stores per run. $0.60 per 1,000. No login, no proxy setup.

- **URL**: https://apify.com/nat-labs/shopify-products-scraper.md
- **Developed by:** [Nat | Data Tools](https://apify.com/nat-labs) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.30 / 1,000 products

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

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

## Shopify Products Scraper & Price Monitor (+ WooCommerce)

Get **every product of any Shopify store** as clean JSON, CSV or Excel: titles, prices, sale prices and discounts, stock per variant, sizes and colours, SKUs, barcodes, images, vendors, tags and dates. Paste store, collection or product URLs, as many as you like. **WooCommerce stores** and any shop with schema.org product data work too, detected automatically.

Turn on **price & stock monitoring** and each scheduled run delivers only what changed: **new products, removed products, price drops, price rises, back in stock, sold out**, with the old and new price.

**$0.60 per 1,000 products** (down to $0.30 on higher plans). No start fee, no proxy bill (rotating IPs included), no login. **Failed, blocked and "not a store" URLs are never charged.**

### Why this Shopify scraper

| | This Actor | Typical Shopify scrapers |
|---|---|---|
| Price | **$0.60 / 1,000 products**, $0.30 on Business plans; platform usage included | $0.80–1.20 / 1,000, often plus a start fee per run |
| Platforms | **Shopify, WooCommerce and any store with schema.org product data** (headless Shopify too), auto-detected | Shopify only |
| Change alerts | **New, removed, price down / up, back in stock, sold out**, product and variant level, old vs new price | Re-download the whole catalog every time |
| Monitoring cost | **$0.05 per 1,000 products checked** + $1 per 1,000 changes found | Full price for every product, every run |
| Output | One row per product **or one flat row per variant** (Excel-ready), plus optional store summary | Nested JSON only |
| Filters | Keywords, vendors, product types, price range, in stock, on sale, updated / published since — **applied before billing** | Filter after you've paid |
| Many stores | Hundreds of stores, collections and products in one run, deduplicated | One store per run |
| Polite & legal | Uses only the public catalog endpoints stores publish, **obeys every store's robots.txt**, paced requests, no cart probing | Cart / checkout tricks for "real inventory" |
| Reliability | Rotating datacenter IPs **included** (Shopify throttles single IPs), automatic back-off, partial results instead of failed runs | 429 errors and failed runs on big catalogs |
| Spending | Stops cleanly at your run's **maximum cost** | Overruns |

### What you can do

- **Competitor price tracking**: schedule daily runs on competitors' stores and get only the price drops, rises and new launches.
- **Restock alerts**: watch sold-out products and sizes and get notified the moment they are back in stock.
- **Dropshipping and product research**: export whole catalogs with prices, compare-at prices, vendors and images.
- **Catalog migration and feeds**: pull every product and variant with SKUs, barcodes, options and weights into a spreadsheet.
- **Market research**: store summary rows give catalog size, price quartiles, % on sale and the newest launches of each store.
- **Brand monitoring**: find which multi-brand retailers carry your vendor and at what price.
- **AI agents**: call it from Claude, ChatGPT or Cursor through Apify's AI integrations. Bare domains like `allbirds.com` work.
- **Your own stack**: run it from the Apify API, the Python or JavaScript client, Make, Zapier, n8n, Google Sheets, Slack, webhooks or a schedule; export JSON, CSV or Excel.

### Quick start

#### 1. A whole store

```json
{ "startUrls": ["https://allbirds.com"], "maxProductsPerStore": 0 }
```

#### 2. Collections, products and many stores at once

```json
{
    "startUrls": [
        "https://kith.com/collections/kith-sale",
        "https://www.allbirds.com/products/mens-tree-runners",
        "colourpop.com",
        "nalgene.com"
    ],
    "maxProductsPerStore": 500
}
```

#### 3. Only what you need (filters run before billing)

```json
{
    "startUrls": ["kith.com"],
    "keywords": ["hoodie", "crewneck"],
    "maxPrice": 200,
    "onlyAvailable": true,
    "onlyOnSale": true
}
```

#### 4. One row per variant for Excel / Google Sheets

```json
{ "startUrls": ["https://www.allbirds.com/collections/womens"], "outputMode": "variants" }
```

#### 5. Daily price & stock monitor

```json
{
    "startUrls": ["competitor-one.com", "https://competitor-two.com/collections/new"],
    "maxProductsPerStore": 0,
    "onlyChanges": true,
    "monitorName": "competitors-daily",
    "priceChangeMinPercent": 2
}
```

Schedule it daily (or hourly) and connect a webhook, Slack or email integration. The first run records the catalog (and returns it, or set `"baselineMode": "silent"`); every later run returns only the changes.

### Output

#### Product row

```json
{
    "type": "product",
    "store": "allbirds.com",
    "storeUrl": "https://www.allbirds.com",
    "platform": "shopify",
    "productId": 7340901859408,
    "handle": "womens-allbirds-flip-flop-dusty-pink",
    "url": "https://www.allbirds.com/products/womens-allbirds-flip-flop-dusty-pink",
    "title": "Women's Allbirds Flip Flop - Dusty Pink",
    "vendor": "Allbirds",
    "productType": "Shoes",
    "tags": ["allbirds::gender => womens", "allbirds::material => sugar"],
    "description": "Sun on your feet. Comfort underneath. …",
    "createdAt": "2026-03-27T14:03:50-07:00",
    "publishedAt": "2026-09-25T16:58:13-07:00",
    "updatedAt": "2026-10-18T04:44:02-07:00",
    "currency": "USD",
    "price": 25,
    "priceMin": 25,
    "priceMax": 25,
    "compareAtPrice": 50,
    "onSale": true,
    "discountPercent": 50,
    "available": true,
    "variantCount": 7,
    "availableVariantCount": 1,
    "options": [{ "name": "Size", "values": ["5", "6", "7", "8", "9", "10", "11"] }],
    "variants": [
        { "variantId": 42146889039952, "title": "5", "sku": "A12513W050", "barcode": null, "price": 25, "compareAtPrice": 50, "discountPercent": 50, "available": false, "options": ["5"], "weightGrams": 455, "url": "https://www.allbirds.com/products/womens-allbirds-flip-flop-dusty-pink?variant=42146889039952" }
    ],
    "images": ["https://cdn.shopify.com/s/files/1/1104/4168/files/A12513_26Q2_Allbirds-Flip-Flop-Dusty-Pink_PDP_LEFT.png"],
    "imageCount": 5,
    "featuredImage": "https://cdn.shopify.com/s/files/1/1104/4168/files/A12513_26Q2_Allbirds-Flip-Flop-Dusty-Pink_PDP_LEFT.png",
    "scrapedAt": "2026-10-18T12:00:00.000Z"
}
```

WooCommerce rows add `categories`, `sku`, `rating` and `reviewCount`. Rows from schema.org product pages add `rating`, `reviewCount` and barcodes (GTIN) when the store publishes them.

#### Change row (monitoring)

```json
{
    "type": "change",
    "changeType": "price_down",
    "changeTypes": ["price_down", "back_in_stock"],
    "store": "allbirds.com",
    "title": "Women's Canvas Cruiser - Warm White",
    "url": "https://www.allbirds.com/products/womens-canvas-cruiser-warm-white",
    "oldPrice": 100,
    "newPrice": 75,
    "priceChange": -25,
    "priceChangePercent": -25,
    "wasAvailable": false,
    "available": true,
    "variantChanges": [{ "variantId": "4211…", "title": "8", "change": "back_in_stock" }],
    "firstSeenAt": "2026-10-01T06:00:00.000Z",
    "detectedAt": "2026-10-18T06:00:00.000Z",
    "product": { "…": "the full current product row" }
}
```

`changeType` is one of `new`, `removed`, `price_down`, `price_up`, `back_in_stock`, `out_of_stock`, `variants_changed` (a size or colour changed price or stock while the product as a whole didn't).

#### Variant row and store summary

`outputMode: "variants"` gives one flat row per variant: the product fields plus `variantId`, `variantTitle`, `variantSku`, `barcode`, `variantPrice`, `variantCompareAtPrice`, `variantAvailable`, `inventoryQuantity` (when the store publishes it), `option1`–`option3`, `weightGrams`, `variantUrl`.

`includeStoreSummary: true` adds one row per store or collection: `publishedProductCount`, `productsScraped`, `variantCount`, `vendorCount`, `topVendors`, `topProductTypes`, `topTags`, price min / p25 / median / p75 / max, `onSalePercent`, `availablePercent`, `newestProducts`.

### Pricing

Pay per event. **Platform usage is included**: no start fee, no compute or proxy bill on top.

| Event | What it is | Price |
|---|---|---|
| Product | One product row (or one variant row in variants mode) from a store's catalog | **$0.60 / 1,000** |
| Product page | One product read from its own web page, for stores without a catalog feed (headless Shopify, some WooCommerce sites); also each product page re-checked in monitoring | $1.00 / 1,000 |
| Product checked | Monitoring: one catalog product compared with the last run | **$0.05 / 1,000** |
| Change | Monitoring: one change row (new, removed, price, stock) | $1.00 / 1,000 |
| Store summary | One summary row per store or collection (optional) | $0.003 |

Lower rates apply automatically on higher Apify plans (products down to $0.30 / 1,000). **Never charged:** URLs that fail, are blocked, password-protected or not a store; products removed by your filters; duplicates; pages without product data.

Examples: a 692-product store = **$0.42**. 50 competitor stores × 500 products = 25,000 products = **$15**. Daily monitoring of 10 stores × 2,000 products for a month ≈ 600,000 products checked = **$30**, plus $1 per 1,000 changes found.

The Actor checks your run's **maximum cost** before delivering and stops cleanly when it is reached, so you never pay more than you set.

**Free Apify plan:** up to 500 products (or changes) from 10 stores per run, a limit set by the developer, not by Apify. Any paid plan removes it.

### Good to know

- **Shopify lists at most 25,000 products per listing** (100 pages of 250). For bigger stores, add collection URLs.
- **Stock is "available / sold out"**, as the store shows it to shoppers. Exact inventory counts appear only when the store publishes them; this Actor never adds items to a cart to probe stock.
- **Markets and currencies**: add the market path (`https://store.com/en-gb`) to get that market's prices. Otherwise you get the store's default currency.
- **robots.txt is always obeyed.** If a store disallows its catalog endpoints, the Actor falls back to product pages; if those are disallowed too, the URL is reported (free) in the run's `STATISTICS` record.
- **Some stores block automated traffic** at their firewall. Those URLs are reported as `blocked` and not charged.
- **Removed products** are only reported when the whole catalog (or collection) was read, so set **Max products per URL** to 0 for monitoring.

### Is it legal to scrape Shopify stores?

This Actor reads only the product information stores publish to every visitor: Shopify's public `products.json` catalog feed, the WooCommerce Store API, and schema.org product data on product pages. It does not log in, does not touch carts or checkouts, does not solve CAPTCHAs, obeys each store's robots.txt, paces its requests, and collects no personal data. Check the terms of the stores you scrape for your own use case; for commercial redistribution, consult a lawyer.

### FAQ

**Does it work with any Shopify store?** Yes, any public store (custom domain or `.myshopify.com`). Password-protected stores are reported as such and not charged. Headless Shopify stores (Hydrogen) are read through their product pages.

**Can I get only new products?** Yes: turn on monitoring and filter the change rows by `changeType: new`, or use **Published within** (`7d`) for a one-off list of new launches.

**How do I get alerts?** Schedule the Actor with monitoring on and add an integration (email, Slack, webhook, Zapier, Make). Runs with no changes return an empty dataset.

**Why are some stores slower?** Requests to one store are paced (about 2 per second, or the store's own robots.txt Crawl-delay) so stores are never overloaded. Many stores run in parallel.

**What happens when a store changes something?** Each run reports per-URL status in `STATISTICS` and fails loudly instead of returning silent empties. Open an issue in the Issues tab; it's answered within 24 hours.

# Actor input Schema

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

One per line (a bare domain like <code>allbirds.com</code> is fine). A store home page (<code>https://allbirds.com</code> or just <code>allbirds.com</code>) gets the whole catalog; a collection (<code>…/collections/sale</code>) or WooCommerce category (<code>…/product-category/bottles/</code>) gets only those products; a product URL gets one product. Market paths such as <code>/en-gb</code> return that market's prices. The platform (Shopify, WooCommerce, or any store with schema.org product data) is detected for you.

## `maxProductsPerStore` (type: `integer`):

Stop each store, collection or category after this many products (after filters). 0 = the whole catalog. The form starts at 100 so a first try stays cheap; API calls without this field get 1000.

## `outputMode` (type: `string`):

<code>products</code>: one row per product with a <code>variants</code> list (sizes, colours, SKUs, prices, stock). <code>variants</code>: one flat row per variant, ready for Excel and Google Sheets (each row is charged as one product).

## `includeDescription` (type: `boolean`):

Product description as plain text (HTML removed). Turn off for smaller datasets.

## `includeStoreSummary` (type: `boolean`):

One <code>type: store-summary</code> row per URL: catalog size, price quartiles, % on sale, % in stock, top vendors, product types and tags, newest launches. Charged as one <b>store summary</b>.

## `keywords` (type: `array`):

Keep products whose title, tags, vendor, product type or category contains any of these words, e.g. <code>hoodie</code>, <code>organic</code>.

## `excludeKeywords` (type: `array`):

Drop products whose title, tags, vendor, product type or category contains any of these words.

## `vendors` (type: `array`):

Keep only these vendors (exact name, case-insensitive). Useful for multi-brand retailers.

## `productTypes` (type: `array`):

Keep only these product types (Shopify) or categories (WooCommerce), exact name, case-insensitive.

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

Keep products with at least one variant at or above this price (store currency).

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

Keep products with at least one variant at or below this price (store currency).

## `onlyAvailable` (type: `boolean`):

Keep only products with at least one variant available to buy.

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

Keep only products with a compare-at (was) price above the current price.

## `updatedSince` (type: `string`):

Shopify only: products updated in this window (<code>24h</code>, <code>7d</code>, <code>2w</code>, <code>1m</code>) or since a date (<code>2026-10-01</code>).

## `publishedSince` (type: `string`):

Shopify only: new launches published in this window (<code>7d</code>) or since a date.

## `onlyChanges` (type: `boolean`):

For scheduled runs: compare each store with the previous run of the same <b>Monitor name</b> and deliver one <code>type: change</code> row per product that is <b>new, removed, cheaper, dearer, back in stock or sold out</b> (with old/new price and variant-level details). Unchanged products are not delivered. Each product compared costs a tiny <b>product checked</b> fee; each change row is one <b>change</b>.

## `monitorName` (type: `string`):

Separate memory per schedule, e.g. <code>competitors-daily</code>. Runs with the same name compare against each other.

## `baselineMode` (type: `string`):

<code>deliver</code>: the first run returns every product (charged as products) and records them. <code>silent</code>: the first run only records the catalog (charged as products checked) and later runs deliver changes.

## `priceChangeMinPercent` (type: `integer`):

Ignore price moves smaller than this percentage. 0 = report every change.

## `includeRemoved` (type: `boolean`):

Add a <code>changeType: removed</code> row when a product disappears from the catalog. Only reported when the whole catalog was read (Max products per URL = 0 or large enough).

## `includeStockChanges` (type: `boolean`):

Report <code>back_in_stock</code> and <code>out_of_stock</code> (product and variant level).

## `platform` (type: `string`):

Leave on <b>auto</b>. Force a platform only if detection picks the wrong one.

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

Stop after this many product / variant / change rows across all URLs. 0 = no limit. The run also stops cleanly at your run's maximum cost.

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

How many URLs are processed at once. Requests to one store are always paced (about 2 per second, or the store's robots.txt Crawl-delay).

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

Default: Apify Proxy (shared datacenter IPs, included in the price). Shopify throttles single IPs across all its stores, so keep it on for reliable runs; turn it off only for a few small stores.

## Actor input object example

```json
{
  "startUrls": [
    "https://allbirds.com"
  ],
  "maxProductsPerStore": 100,
  "outputMode": "products",
  "includeDescription": true,
  "includeStoreSummary": false,
  "onlyAvailable": false,
  "onlyOnSale": false,
  "onlyChanges": false,
  "monitorName": "default",
  "baselineMode": "deliver",
  "priceChangeMinPercent": 0,
  "includeRemoved": true,
  "includeStockChanges": true,
  "platform": "auto",
  "maxItems": 0,
  "maxConcurrency": 5,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

Product rows (type=product), variant rows (type=variant), change rows in monitoring mode (type=change) and store summaries (type=store-summary).

## `statistics` (type: `string`):

Per-run counters, URLs that failed with reasons, skipped pages, requests and bytes.

# 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 = {
    "startUrls": [
        "https://allbirds.com"
    ],
    "maxProductsPerStore": 100,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("nat-labs/shopify-products-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 = {
    "startUrls": ["https://allbirds.com"],
    "maxProductsPerStore": 100,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("nat-labs/shopify-products-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 '{
  "startUrls": [
    "https://allbirds.com"
  ],
  "maxProductsPerStore": 100,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call nat-labs/shopify-products-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,nat-labs/shopify-products-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/graxsru4akifPqQG5/builds/iApSglk5ACHndefdj/openapi.json
