# Shopify Store Scraper: Products, Prices & Price Changes (`moggedhq/shopify-store-products-scraper`) Actor

Track any Shopify store's products with price, compare-at price, discount, variants, SKUs, stock and new products, plus price changes and restocks between runs, for competitor monitoring. About $0.001 per product: 1,000 cost $1. No login. Includes collection and product URLs.

- **URL**: https://apify.com/moggedhq/shopify-store-products-scraper.md
- **Developed by:** [mogged](https://apify.com/moggedhq) (community)
- **Categories:** E-commerce, Automation
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.80 / 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 Store Scraper: Products, Prices & Price Changes

For ecommerce brands, price analysts and resellers who monitor competitor catalogues every day. Get public products, variant prices, discounts, SKUs, barcodes when published, stock availability and persistent price-change history.

About **$0.001 per delivered product**: 40 products cost $0.04, or 1,000 cost $1, plus Apify's $0.00005 minimum start charge. Variant details are included in that product price. Bronze, Silver and Gold discounts apply. In change-only mode, unchanged products have no product charge.

The prefilled two-store example returned **40 products in 9 seconds** in an actual 512 MB cloud test on 29 October 2026. All four source requests succeeded. Both stores returned confirmed USD currency. Store response times vary.

### What you get

| Field | Meaning |
| --- | --- |
| `verdict` | Readable product, price, availability and discount summary |
| `recordType` | `PRODUCT`, free `NOTE`, or free store `SUMMARY` |
| `store`, `productId`, `handle`, `url` | Store scope and stable public product identity |
| `title`, `vendor`, `productType`, `tags` | Merchant-supplied catalogue classification |
| `price`, `priceMax` | Lowest and highest published variant price, in major currency units |
| `compareAtPrice`, `discountPercent` | Compare-at value of the same lowest-price variant and its calculated discount |
| `onSale`, `discountedVariantCount` | Whether any variant is discounted, and how many |
| `currency`, `currencySource` | Currency explicitly found on the storefront, or `null` |
| `available`, `availableVariantCount` | Whether any variant is available and how many are available |
| `variantCount`, `variants` | Variant count and optional nested variant details |
| `createdAt`, `publishedAt`, `updatedAt` | Public product timestamps, normalized to UTC when supplied |
| `images`, `scrapedAt` | Public image URLs and the observation timestamp |
| `changeType`, `changes`, `previousPrice` | Primary watch change, all simultaneous change types and prior price |
| `variantChanges` | IDs and changed fields for variants in a watch |
| `priceSource` | Catalogue decimal JSON or product Ajax JSON |

Each variant contains `variantId`, `title`, `options`, `price`, `compareAtPrice`, `sku`, `barcode`, `available`, `inventoryQuantity` and `inventoryPolicy`. A missing value stays `null`. Stock quantities are source-reported public values, not independently verified warehouse counts. Many stores expose availability without a quantity or barcode.

The product-level discount never compares the cheapest size with the compare-at price of an unrelated, more expensive size. Use `onSale` to find a discount on any size, then inspect the matching variant for its price.

### Start in three steps

1. Enter one or more public store roots, or paste individual product or collection links.
2. Choose the maximum number of products examined per store and whether to include variants.
3. Run, inspect the free store summary, and export the product dataset as JSON, CSV or Excel.

```json
{
  "storeUrls": [
    "https://www.allbirds.com",
    "https://www.deathwishcoffee.com"
  ],
  "maxProductsPerStore": 20,
  "includeVariants": false,
  "proxyConfiguration": { "useApifyProxy": true }
}
```

This is the prefilled example: up to 40 charged product rows, about $0.04 before plan discounts. Enable variants for slower, deeper per-product detail. JSON preserves nested values. For a flat export, use the Products and prices dataset view and filter `recordType` to `PRODUCT`.

### Input reference

| Input | Default | Purpose |
| --- | --- | --- |
| `storeUrls` | None in API input | Public store or locale roots; the form prefills two stores |
| `collections` | Empty | Optional collection handles applied to each store root |
| `productUrls` | Empty | Product or collection links, including collection product paths |
| `maxProductsPerStore` | `20` | Current products examined per store, from 1 to 10,000 |
| `includeVariants` | `false` | Include nested variants and request public product details |
| `onlyChangedSinceLastRun` | `false` | Return only new or changed products for a persistent watch |
| `watchId` | `default` | Stable monitoring name; letters, numbers, underscores and hyphens |
| `proxyConfiguration` | Datacenter proxy | Custom proxy settings or explicitly disabled proxy |

URL lists accept plain strings. URL objects containing `url` also work when supplied by an integration. Bare public domains are accepted. HTTPS, normal domain names and standard ports are required. Credentials, internal addresses and unrelated URL paths are rejected with a free note.

Store roots select the full public catalogue unless `collections` supplies a narrower scope. Product links select those products. Collection links work directly in `productUrls`. Duplicate URLs, overlapping collections and overlapping product links produce one row and one product charge per store and product ID.

```json
{
  "storeUrls": ["https://www.deathwishcoffee.com"],
  "collections": ["coffee"],
  "maxProductsPerStore": 100,
  "includeVariants": true
}
```

For selected products, omit `storeUrls`:

```json
{
  "productUrls": [
    "https://www.deathwishcoffee.com/products/death-wish-stir-stick-set"
  ],
  "includeVariants": true
}
```

### Monitor prices every day

Create a task with `onlyChangedSinceLastRun: true` and a memorable `watchId`, such as `coffee-daily`. Schedule that task daily in Apify. Keep the same store roots, product URLs, collections, product cap and variant setting. Use another watch ID for a different scope.

```json
{
  "storeUrls": ["https://www.deathwishcoffee.com"],
  "maxProductsPerStore": 1000,
  "includeVariants": true,
  "onlyChangedSinceLastRun": true,
  "watchId": "coffee-daily"
}
```

The first run establishes the baseline and returns observed products as `new`. The next run returns changed products only. An unchanged catalogue produces a free summary and no product charges, apart from the platform start fee. Each changed product is charged once even when several variants change together.

Watch changes include `new`, `priceUp`, `priceDown`, `backInStock`, `soldOut` and `removed`. Additional explicit types cover `discountChanged`, `variantsChanged`, `currencyChanged` and `metadataChanged`. `changes` retains simultaneous signals, so a price drop and stockout are visible together. `previousPrice` is the prior observed product minimum, not a historical price obtained from the merchant.

A change to a non-cheapest variant still qualifies when variants are included. Ordinary inventory-quantity churn and the merchant's continually updated timestamps do not create paid change rows. Confirmed currency changes are flagged separately rather than called price rises or falls. Missing currency discovery never creates a paid change alert; monetary comparisons resume when the currency is confirmed.

A removal is emitted only after a complete successful scan of the unchanged scope. Capped catalogues, failed pages, incomplete detail reads and spending-limit stops never prove that an unseen product disappeared. A product leaving a selected collection means removal from that collection scope, not necessarily deletion from the whole store. Removed rows use `price: null`, preserve `previousPrice`, and represent an observation about catalogue membership.

Run each watch sequentially. If its previous run is unfinished, reboot that original run or use another watch ID. Do not launch the same watch simultaneously. Watch state is isolated by account and Actor, and continues across tasks and container restarts.

### Example from a real cloud run

Observed on 29 October 2026, with longer image lists and optional fields omitted here for readability:

```json
{
  "verdict": "Death Wish Stir Stick Set: USD 8.00, in stock.",
  "recordType": "PRODUCT",
  "store": "https://www.deathwishcoffee.com",
  "productId": "7499727863863",
  "handle": "death-wish-stir-stick-set",
  "title": "Death Wish Stir Stick Set",
  "vendor": "Planet Apparel",
  "price": 8,
  "compareAtPrice": null,
  "discountPercent": 0,
  "currency": "USD",
  "available": true,
  "variantCount": 1,
  "variants": [{
    "variantId": "42351252209719",
    "price": 8,
    "compareAtPrice": null,
    "sku": "USTIRDW26",
    "barcode": null,
    "available": true
  }],
  "changeType": null,
  "previousPrice": null
}
```

Prices and stock can change after this observation. Read the current run's `scrapedAt` before using a number in a report or alert.

### Free summaries and source failures

Every store receives a free `SUMMARY` dataset row with observed products, delivered rows, sale count, median price, new products since the previous watch run, unchanged count, currency and coverage status. The key-value store's `SUMMARY` record combines all stores with request counts and the spending-limit result.

A capped sample is clearly marked `CAPPED` and `fullScan: false`. A blocked, unavailable, password-protected or unsupported store produces a free note, with no product charge. Invalid values are rejected by input validation or returned as a free note by the Actor. Check summary status as well as the platform's run status: `SUCCEEDED` means the request was handled, and does not promise that every merchant exposed data.

The Actor respects the run's maximum charge and stops cleanly when another product cannot fit. It saves the useful row before charging, deduplicates delivery through the durable dataset and reconciles charges after a platform reboot. Retries and restart recovery do not add duplicate product charges.

### Pricing examples

| Delivered products | List product charge |
| --- | ---: |
| 20 | $0.02 |
| 40 | $0.04 |
| 100 | $0.10 |
| 1,000 | $1.00 |
| 10,000 | $10.00 |

Add the $0.00005 minimum start fee per run. Bronze products cost $0.00095, Silver $0.0009 and Gold or higher $0.0008. A 40-product Bronze run therefore costs $0.03805 including the start fee. Current Apify pricing is authoritative.

The cap counts products examined, including unchanged products, rather than promising a requested number of changes. For example, examining 100 products and finding three changes delivers three paid product rows. Free notes and summaries never count as paid products. No separate fee is charged for a variant, request, retry or barcode.

### API and integrations

Start an asynchronous run using your own Apify API token:

```bash
curl -X POST \
  'https://api.apify.com/v2/acts/moggedhq~shopify-store-products-scraper/runs' \
  -H 'Authorization: Bearer YOUR_APIFY_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{"storeUrls":["https://www.allbirds.com"],"maxProductsPerStore":20}'
```

Use the returned run's `defaultDatasetId` to download `/v2/datasets/DATASET_ID/items`. Read `/v2/key-value-stores/STORE_ID/records/SUMMARY` using the run's `defaultKeyValueStoreId` for coverage and diagnostics.

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('moggedhq/shopify-store-products-scraper').call({
  storeUrls: ['https://www.allbirds.com'],
  maxProductsPerStore: 20,
  includeVariants: true
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
const products = items.filter(row => row.recordType === 'PRODUCT');
console.log(products);
```

Connect scheduled tasks to Make, Zapier, n8n, Sheets or your warehouse. Trigger an alert from product rows whose `changes` contain `priceDown` or `backInStock`. Keep credentials in your integration's secret storage.

### Use with AI agents (MCP)

Connect your assistant to [Apify MCP](https://mcp.apify.com). Ask it to run `moggedhq/shopify-store-products-scraper` with the store URLs, product cap and variant setting, then read product rows and the summary. For daily monitoring, provide a stable watch ID and request change-only mode. Tell the agent to preserve your maximum charge and to report partial coverage.

### Limits and FAQ

**Does every Shopify store work?** No. Traditional public Shopify catalogue endpoints are supported. Headless frontends, custom routing, password protection and merchant access controls can prevent access. The Actor does not log in or solve challenges.

**Where do currency and prices come from?** Prices come from public catalogue or product JSON. Currency comes from explicit storefront HTML values; it remains `null` if unconfirmed. Preserve locale roots such as `/en-gb` when supplied by a store. This is not a ZIP-specific, checkout, tax, shipping, subscription or member-price service.

**Can I search every Shopify store by keyword?** No. Supply known stores or product links. This Actor monitors chosen competitors; it does not query a worldwide marketplace or collect merchant contacts.

**Will I get exact stock, reviews or sales?** Only public availability and any explicitly exposed variant quantities. No sales estimates, customer records, reviews, hidden inventory, cart calls or checkout requests are used. Missing barcodes and timestamps remain `null`. Public JSON can list products whose shopping page requires membership; catalogue availability does not establish purchase eligibility.

**How are stores paced?** One request at a time per host, roughly 1.1 seconds between starts, with fresh proxy sessions for retries. Up to two different hosts advance concurrently. Datacenter proxy is the default; residential proxy is used only when explicitly selected. Long Retry-After instructions defer the store to a later run.

**What happens after a spending-limit stop?** Delivered rows remain available. The previous watch baseline is retained so undelivered changes can be seen later. Increase the next run's limit to finish the intended scan. Some already delivered changes can appear again in that new run because it is a new delivery, not a reboot.

**Can I reuse a watch with different filters?** Use a different watch ID. Scope changes are rejected with a free note to prevent misleading comparisons. Stored snapshots are observations from your earlier runs, not historical backfill.

Review the target merchant's access terms and use the public data within your permissions. Shopify store scraper, Shopify products and Shopify price changes are the data covered here.

# Actor input Schema

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

Public Shopify storefront roots, such as https://www.allbirds.com. Accepts plain URL strings. Store subfolder markets are preserved.

## `collections` (type: `array`):

Optional collection handles, for example coffee. Applied to each supplied store. Empty reads the public catalogue.

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

Optional product links or collection links. Accepts public /products/handle and /collections/handle URLs. Products overlapping catalogue results are delivered once per store.

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

Maximum current products examined per store, from 1 to 10000. A capped scan never labels unseen products removed.

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

Include each public variant price, compare-at price, SKU, barcode when available, option values and availability. No additional product charge.

## `onlyChangedSinceLastRun` (type: `boolean`):

Use a persistent watchId to return new products, price changes, discount changes, variant changes, restocks and sold-out products. First run returns new products. Scope must remain unchanged.

## `watchId` (type: `string`):

A stable name for a sequential monitoring job, such as competitor-daily. Keep the same input scope and run one watch at a time. Watches are isolated per account and Actor.

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

Datacenter proxy is the default. A custom configuration is respected. Residential access is only used if you explicitly select it.

## Actor input object example

```json
{
  "storeUrls": [
    "https://www.allbirds.com",
    "https://www.deathwishcoffee.com"
  ],
  "maxProductsPerStore": 20,
  "includeVariants": false,
  "onlyChangedSinceLastRun": false,
  "watchId": "default",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

No description

## `summary` (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": [
        "https://www.allbirds.com",
        "https://www.deathwishcoffee.com"
    ],
    "maxProductsPerStore": 20,
    "includeVariants": false,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("moggedhq/shopify-store-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 = {
    "storeUrls": [
        "https://www.allbirds.com",
        "https://www.deathwishcoffee.com",
    ],
    "maxProductsPerStore": 20,
    "includeVariants": False,
    "proxyConfiguration": { "useApifyProxy": True },
}

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

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,moggedhq/shopify-store-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/BsEn7F8Gj3ZmZJS6y/builds/wOQ6DXDAmKrmfNP3H/openapi.json
