# Shopify Scraper - Products & Price Change Monitor (`gazidev/shopify-products-monitor`) Actor

Scrape products, variants, prices, stock and images from any Shopify store via its public products.json. Monitor mode reports only price drops/rises, back-in-stock, out-of-stock and new/removed products. Built for schedules and webhooks.

- **URL**: https://apify.com/gazidev/shopify-products-monitor.md
- **Developed by:** [Cemal Atakli](https://apify.com/gazidev) (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 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

## Shopify Scraper – Products & Price Change Monitor

Scrape **every product from any Shopify store**: titles, vendors, product types, tags, images, descriptions and **all variants** with SKU, price, compare-at price and stock availability. You can also switch to **monitor mode**, which compares each run with the previous one and returns **only what changed**: price drops and rises, back in stock, out of stock, and new or removed products. Schedule it, add a webhook, and you have a competitor price tracker.

- **$0.30 per 1,000 products.** That is 3.3× cheaper than the most popular Shopify scraper on the Store. Non-Shopify sites, errors and blocked stores are free.
- **Monitor mode: pay only for changes.** Each change costs $0.001, and unchanged products cost nothing. The first run saves a baseline for free.
- **Many stores per run.** Give it domains or collection URLs, and the stores are processed in parallel.
- **Clean, flat rows.** One row per product with derived fields: `minPrice`, `maxPrice`, `onSale`, `discountPct`, `available`, `inStockVariants` and the product `url`.
- **Store summary.** One row per store with product count, price range, median price, share of products on sale, average discount, top vendors, product types and tags.
- **Fast, cheap and polite.** It uses only plain HTTP requests to the public `/products.json` feed, with no browser and no proxy. Each store gets at most 1 request per second, and robots.txt is respected.

### What can I use it for?

- **Competitor price monitoring:** get a Slack or email alert when a competitor drops a price or starts a sale
- **Stock and restock alerts:** see when a product sells out or comes back in stock
- **New product tracking:** spot competitors' new launches the day they go live, and see what was discontinued
- **Market research and catalogue analysis:** compare price ranges, discount depth, vendors and product types across many DTC brands
- **Dropshipping and product research:** export full catalogues with images, variants and SKUs
- **Price comparison sites and feeds:** keep product data fresh on a schedule
- **AI agents:** a "what does this Shopify store sell, and at what price?" tool (see below)

Need a list of Shopify stores first? Use our [Wappalyzer Alternative – Bulk Tech Stack Detector](https://apify.com/gazidev/tech-stack-detector) to check which domains run Shopify.

### Input

| Field | Description |
|---|---|
| `storeUrls` | Store domains (`allbirds.com`) or collection URLs (`https://store.com/collections/sale`) |
| `mode` | `scrape` (all products) or `monitor` (only changes since the last run) |
| `maxProductsPerStore` | Product limit per store, 0 = whole catalogue (default 250). In monitor mode, set it above the catalogue size so that removed products can be detected |
| `includeVariants` | Add the `variants` array (default on) |
| `includeDescription` | Add the description as plain text (default on) |
| `includeStoreSummary` | One summary row per store in scrape mode (default on, $0.002 per store) |
| `fetchStoreInfo` | Read `/meta.json` for the store name and currency (default on) |
| `currency` | Optional currency label override (informational) |
| `monitorKey` | Name of the key-value store that keeps the monitor state. Use different names for separate monitors |
| `maxStores`, `respectRobotsTxt`, `maxConcurrency`, `requestTimeoutSecs`, `proxyConfiguration` | Advanced |

Scrape example:

```json
{
  "storeUrls": ["allbirds.com", "gymshark.com", "https://www.deathwishcoffee.com/collections/coffee"],
  "mode": "scrape",
  "maxProductsPerStore": 250
}
```

Monitor example (run it on a schedule):

```json
{
  "storeUrls": ["allbirds.com", "colourpop.com"],
  "mode": "monitor",
  "maxProductsPerStore": 0,
  "monitorKey": "competitors-shoes"
}
```

### Output

Every row has a `type`: `product`, `summary`, `change`, `baseline` or `error`. The Output tab has three tables: **Products**, **Changes** and **Store summaries**.

**Product** (scrape mode, shortened):

```json
{
  "type": "product",
  "store": "partakefoods.com",
  "storeName": "Partake Foods",
  "productId": 7783508934754,
  "title": "Mini Chocolate Chip & Grahams",
  "url": "https://partakefoods.com/products/mini-chocolate-chip-grahams",
  "vendor": "Partake Foods",
  "productType": null,
  "tags": ["Snack Pack", "type_Snack Packs"],
  "minPrice": 54.99, "maxPrice": 109.98, "currency": "USD",
  "onSale": false, "discountPct": 0,
  "available": true, "variantCount": 2, "inStockVariants": 2,
  "image": "https://cdn.shopify.com/s/files/1/0012/2296/7353/files/MiniCC_Grahams_12pack_transparent.png",
  "createdAt": "2026-08-28T16:19:17-04:00",
  "variants": [
    {"variantId": 43267709108322, "title": "6 Boxes", "sku": "52889-1", "price": 54.99,
     "compareAtPrice": null, "available": true, "option1": "6 Boxes", "grams": 0}
  ]
}
```

**Change** (monitor mode; from a test run where the saved state was edited):

```json
{
  "type": "change",
  "changeType": "PRICE_DOWN",
  "store": "partakefoods.com",
  "storeName": "Partake Foods",
  "productId": 7783508934754,
  "title": "Mini Chocolate Chip & Grahams",
  "variantId": 43267709108322,
  "variantTitle": "6 Boxes",
  "sku": "52889-1",
  "oldPrice": 59.99, "newPrice": 54.99, "priceDiff": -5.0, "priceDiffPct": -8.33,
  "oldCompareAtPrice": null, "newCompareAtPrice": null,
  "oldAvailable": true, "newAvailable": true,
  "currency": "USD",
  "url": "https://partakefoods.com/products/mini-chocolate-chip-grahams",
  "previousCheckAt": "2026-10-01T08:57:44+00:00",
  "detectedAt": "2026-10-01T08:58:17+00:00"
}
```

The change types are `PRICE_DOWN`, `PRICE_UP`, `BACK_IN_STOCK`, `OUT_OF_STOCK`, `NEW_PRODUCT` and `REMOVED`. Price and stock changes are tracked **per variant**, by size or colour. A variant can produce two rows, one for price and one for stock.

**Store summary** (scrape mode): `productCount`, `catalogComplete`, `inStockProducts`, `minPrice`/`medianPrice`/`maxPrice`, `onSaleProducts`, `discountShare` (% on sale), `avgDiscountPct`, `topVendors`, `topProductTypes`, `topTags` and `newestProductAt`.

**Errors** are free rows such as `{"store": "example.com", "error": "NOT_SHOPIFY"}`. The other codes are `PRODUCTS_JSON_DISABLED`, `PASSWORD_PROTECTED`, `ROBOTS_DISALLOWED`, `RATE_LIMITED`, `BLOCKED` and `COLLECTION_NOT_FOUND`. A per-store status report is saved in the run's key-value store as `SUMMARY`.

### How monitor mode works

1. **First run:** the Actor reads the catalogue and saves a compact snapshot (product, variant, price, compare-at price, availability) to a **named key-value store** in your account. The default name is `shopify-products-monitor-state`, or you can set `monitorKey`. You get one free `baseline` row per store.
2. **Every later run:** the Actor compares the new data with the snapshot and outputs **only the differences**, then saves the new snapshot. If nothing changed, the dataset is empty and you pay nothing per product.
3. `REMOVED` is reported only when the whole catalogue was read, so a product limit never causes false "removed" alerts.
4. If your *Maximum cost per run* stops the run early, the changes that did not fit are kept and reported on the next run.

#### Recipe: Slack alerts on competitor price drops

1. Create a task with `mode: "monitor"` and your competitors' stores.
2. **Schedule** it, for example every 6 hours (Console → Schedules).
3. Add a **webhook** on *Run succeeded* to Zapier or Make, or use the Apify Slack integration. Fetch the run's dataset items with `?view=changes` and post every `PRICE_DOWN` or `BACK_IN_STOCK` row to Slack.

### Pricing

| | This Actor | trovevault/shopify-products-scraper | autofacts/shopify | webdatalabs |
|---|---|---|---|---|
| Per 1,000 products | **$0.30** | $1.00 | $1.20 | $10 |
| Change-only monitor mode | **Yes, $1 per 1,000 changes; unchanged items free** | No | No | No |
| Store summary row | Yes, $0.002 per store | No | No | No |
| Non-Shopify sites and errors | Free | – | – | – |

Competitor prices are from the Apify Store as of 2026-10-01. Example: 10 competitor stores × 500 products cost **$1.50** to scrape. Monitoring them every day, with about 30 changes per day, costs about **$0.03 per day**.

### FAQ

**Which stores work?** Most public Shopify stores, since Shopify exposes `/products.json` by default. Some stores turn it off (`PRODUCTS_JSON_DISABLED`), are password-protected, or throttle all automated traffic (`RATE_LIMITED`). Those rows are free.

**Are inventory quantities included?** No. Shopify's public feed shows *whether* a variant is available, not how many units are left. Barcodes and cost prices are not public either.

**Why is a collection URL skipped with ROBOTS_DISALLOWED?** Some stores' robots.txt disallows `/collections/*/products*`, and we respect it. Use the store domain instead, which reads the whole catalogue, and filter by `productType` or `tags`.

**Are prices in my currency?** No, prices are the store's own base prices. `currency` comes from the store's `/meta.json`.

**How fast is it?** About 250 products per second-long request. Each store is paced at 1 request per second, or slower when its robots.txt Crawl-delay asks for it. Several stores run in parallel.

**Can I monitor several separate lists?** Yes. Use a different `monitorKey` for each list or task.

**Is this legal?** The Actor reads only the public product feed that Shopify serves to every visitor and search engine. That feed contains product data and no personal data. It never touches cart, checkout or account pages, it honours robots.txt `Disallow` and Crawl-delay, and it rate-limits itself. Check that your use complies with the laws and terms that apply to you. Store owners who want to opt out can disallow `/products.json` in robots.txt or email iletisim.ortalamabiri@gmail.com.

### Use with AI agents / Apify MCP

The input is small (a list of domains) and the rows are compact JSON, so the Actor works well as an agent tool. Through the [Apify MCP server](https://mcp.apify.com) (`https://mcp.apify.com?actors=gazidev/shopify-products-monitor`), Claude, ChatGPT, Cursor and other MCP clients can call it directly. For example: *"list the 20 cheapest in-stock products on gymshark.com"* or *"what changed in my competitors' stores since yesterday?"*. Over the API: `POST https://api.apify.com/v2/acts/gazidev~shopify-products-monitor/run-sync-get-dataset-items` with the input JSON.

### Categories

E-commerce · Automation · Lead generation · Developer tools

# Actor input Schema

## `storeUrls` (type: `array`):

Shopify store domains such as `allbirds.com`, or collection URLs such as `https://www.gymshark.com/collections/mens` to scrape only one collection. `https://` is added automatically. Sites that are not Shopify stores return a free `NOT_SHOPIFY` row.

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

**Scrape**: return every product, with variants, prices, stock and images. **Monitor**: compare with the previous run and return only changes (price drop or rise, back in stock, out of stock, new product, removed product). The first monitor run saves a baseline. Schedule monitor runs and add a webhook to get alerts.

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

Stop after this many products per store (0 = whole catalogue). In monitor mode, set this higher than the store's catalogue size: removed products are detected only when the whole catalogue was read.

## `includeVariants` (type: `boolean`):

Add a `variants` array to every product: variant ID, title, SKU, price, compare-at price, availability, options, weight and image.

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

Add the product description as plain text (HTML removed).

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

Add one summary row per store: product count, price range, median price, share of products on sale, average discount, top vendors, product types and tags. Charged as one `store-summary` event per store. Monitor mode never adds summary rows.

## `fetchStoreInfo` (type: `boolean`):

Read the store's public `/meta.json` (one extra request) to add the store name and currency to every row.

## `currency` (type: `string`):

Informational only: overrides the currency code written to rows (e.g. `USD`). Prices are always the store's own prices. Leave empty to use the store's currency from `/meta.json`.

## `monitorKey` (type: `string`):

Name of the key-value store in your account that keeps the previous state for monitor mode (letters, digits, hyphens). Use different names for independent monitors. Default: `shopify-products-monitor-state`.

## `maxStores` (type: `integer`):

Process only the first N stores (0 = all). Useful for a cheap test of a long list.

## `respectRobotsTxt` (type: `boolean`):

Skip stores whose robots.txt disallows `/products.json`, and honour Crawl-delay (up to 10 s). Requests to one store are always at least 1 second apart.

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

How many stores are scraped at the same time. Each store gets one request at a time.

## `requestTimeoutSecs` (type: `integer`):

Per-request timeout. Failed and rate-limited (429) requests are retried with backoff.

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

Optional. Not needed for most stores. Apify datacenter proxy is on by default because many Shopify stores rate-limit cloud IPs.

## Actor input object example

```json
{
  "storeUrls": [
    "allbirds.com"
  ],
  "mode": "scrape",
  "maxProductsPerStore": 20,
  "includeVariants": true,
  "includeDescription": true,
  "includeStoreSummary": true,
  "fetchStoreInfo": true,
  "maxStores": 0,
  "respectRobotsTxt": true,
  "maxConcurrency": 5,
  "requestTimeoutSecs": 30,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

No description

## `changes` (type: `string`):

No description

## `summaries` (type: `string`):

No description

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

No description

## `runSummary` (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 = {
    "storeUrls": [
        "allbirds.com"
    ],
    "maxProductsPerStore": 20,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

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

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

```

## MCP server setup

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

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/llH2UwBGfrWO147iU/builds/fY4HUqaN43svyQvo4/openapi.json
