# Shopify Store Scraper: Products, Prices & Stock (`cleanpull/shopify-store-scraper`) Actor

Scrape every product and variant from any Shopify store: prices by country, stock, sales estimates, best-seller rank, store apps, cross-store comparison and changes-only competitor monitoring. From $0.80 per 1,000 products; compact JSON for AI agents.

- **URL**: https://apify.com/cleanpull/shopify-store-scraper.md
- **Developed by:** [CleanPull](https://apify.com/cleanpull) (community)
- **Categories:** E-commerce, Marketing, Automation
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.60 / 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 & Stock

Get every product and variant from any Shopify store: prices, compare-at prices and discounts, availability, SKUs, options, images, tags, vendors and dates. Go further than a catalogue: **exact stock and estimated sales per day** (where the store publishes stock), **the store's own best-seller rank**, **prices in other countries and currencies**, a **store summary with the apps it runs**, **cross-store price comparison**, and a **changes-only monitoring mode** for new products, price moves, discounts, sell-outs and restocks. Fast HTTP requests to Shopify's public storefront data, no browser, from **$0.80 per 1,000 products**.

### What you can do with it

- **Copy a catalogue**: "Every product and variant from gymshark.com, as one row per variant for a spreadsheet."
- **Watch competitors**: schedule daily runs with `monitorMode` and get only what changed: new products, price drops and rises, discounts starting or ending, items selling out or coming back, sizes restocking.
- **See what sells**: turn on `includeStock` and run daily. Where a store publishes stock, each run gives units sold since the last run, sales per day and days of stock left per variant, and `bestSellerRank` shows the store's own best-selling order.
- **Compare international pricing**: `markets: ["GB", "DE", "CA"]` gives each product's price in those countries and currencies, for stores that sell abroad with Shopify Markets.
- **Profile a store**: `storeSummary` returns catalogue size, price range and median, share on sale, new products in the last 7 and 30 days, top vendors and product types, the theme, the apps it runs (reviews, email and SMS, subscriptions, loyalty, payments, support, analytics…) and its social profiles.
- **Find the cheapest seller**: list several stores with `compareAcrossStores` and get every product they share (matched by SKU, else by title) with the cheapest store and the price spread.
- **Feed an AI agent or a sheet**: choose only the fields you need, or get flat rows.

### Why this actor

- **More than products.json.** Stock and sales estimates, best-seller rank, prices by market, store analytics with app detection, cross-store comparison and change monitoring, in one actor and one run.
- **Works on more stores.** Stores that redirect to `www`, use locale paths, or run a headless front are found through their Shopify address. A store that blocks its product feed is read through its sitemap and product pages instead. A site that is not Shopify is reported, not returned empty.
- **Pay for what you use.** Products are $0.80 per 1,000; stock, market prices and store summaries are add-ons billed only when switched on and only where the store gives the data (no charge for hidden stock or a product not sold in a market); unchanged products in monitoring cost $0.15 per 1,000 (plus $0.50 when stock tracking is on).
- **Compatible input.** `domains`, `maxProducts`, `includeInventoryDetails` and `startUrls` (as strings or `{ "url": … }`) from other Shopify actors work as they are.
- **Honest data.** Stock is reported only where the store publishes it (null otherwise, never a guess), a compare-at price of 0 means no discount, and a product not sold in a market has a null price there.

### Input

| Field                                         | Type                     | What it does                                                                                                                                                            |
| --------------------------------------------- | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `stores`                                      | array of domains or URLs | Stores to read in full (up to `maxProductsPerStore`)                                                                                                                    |
| `startUrls`                                   | array of URLs            | Collection pages (only that collection) or product pages (only that product)                                                                                            |
| `maxProductsPerStore`                         | integer                  | Products per store (default 1,000; up to 25,000)                                                                                                                        |
| `maxItems`                                    | integer                  | Stop after this many records in the run. In monitoring mode, unchanged products checked are billed in bulk and not capped by it                                         |
| `minPrice`, `maxPrice`                        | numbers                  | Price range                                                                                                                                                             |
| `inStockOnly`, `onSaleOnly`                   | boolean                  | Only available products / only discounted products                                                                                                                      |
| `vendors`, `productTypes`, `tags`, `keywords` | arrays                   | Filters by vendor, type, any tag, or title words                                                                                                                        |
| `publishedWithinDays`                         | integer                  | New arrivals only                                                                                                                                                       |
| `includeStock`                                | boolean                  | Exact stock per variant (where published) and, from the second run, units sold, sales per day, days of stock left                                                       |
| `markets`                                     | array of country codes   | Prices in those countries and their currencies (`GB`, `DE`, `CA`, `AU`…)                                                                                                |
| `bestSellerRank`, `bestSellerDepth`           | boolean, integer         | The store's best-selling order, with movement since the last run                                                                                                        |
| `storeSummary`                                | boolean                  | One store record: catalogue analytics, theme, apps, social profiles, top sellers                                                                                        |
| `compareAcrossStores`                         | boolean                  | The same product across stores, cheapest store and spread                                                                                                               |
| `monitorMode`, `monitorName`                  | boolean, string          | Changes only, compared with the last run under the same name                                                                                                            |
| `changeTypes`                                 | array                    | `new-product`, `removed-product`, `price-drop`, `price-rise`, `discount-started`, `discount-ended`, `sold-out`, `back-in-stock`, `variants-changed`, `best-seller-move` |
| `priceChangePercent`, `rankChangeMin`         | numbers                  | Ignore smaller price or rank moves                                                                                                                                      |
| `outputFormat`                                | `products`, `variants`   | One record per product, or one flat row per variant                                                                                                                     |
| `includeDescriptionHtml`                      | boolean                  | Also return description HTML (text is always included)                                                                                                                  |
| `fields`                                      | array of strings         | Return only these fields (dotted paths reach into lists)                                                                                                                |

#### Example inputs

A full catalogue as spreadsheet rows:

```json
{ "stores": ["gymshark.com"], "outputFormat": "variants" }
```

A daily competitor watch with sales estimates (schedule it once a day):

```json
{
  "stores": ["colourpop.com", "allbirds.com"],
  "monitorMode": true,
  "monitorName": "beauty-competitors",
  "includeStock": true,
  "bestSellerRank": true,
  "changeTypes": [
    "new-product",
    "price-drop",
    "price-rise",
    "discount-started",
    "sold-out",
    "back-in-stock",
    "best-seller-move"
  ]
}
```

International pricing for one collection:

```json
{
  "startUrls": ["https://kith.com/collections/footwear"],
  "markets": ["GB", "DE", "CA", "JP"]
}
```

### Output

A product (shortened; a real record from 5 October 2026, run with stock, rank and UK prices):

```json
{
  "recordType": "product",
  "storeDomain": "colourpop.com",
  "storeName": "ColourPop",
  "currency": "USD",
  "productId": 1495817420882,
  "title": "Pencil Sharpener",
  "handle": "pencil-sharpener",
  "url": "https://colourpop.com/products/pencil-sharpener",
  "vendor": "ColourPop",
  "productType": "Makeup Tools & Accessories",
  "priceMin": 5,
  "priceMax": 5,
  "onSale": false,
  "available": true,
  "variantCount": 1,
  "totalInventory": 3750,
  "bestSellerRank": 76,
  "marketPrices": { "GB": { "currency": "GBP", "priceMin": 4, "priceMax": 4 } },
  "publishedAt": "2026-10-02T11:11:31-07:00",
  "variants": [
    {
      "variantId": 12951787864146,
      "title": "Default Title",
      "sku": "Tool-PencilSharpener",
      "price": 5,
      "compareAtPrice": null,
      "available": true,
      "inventoryQuantity": 3750,
      "inventoryPolicy": "deny",
      "unitsSoldSinceLastRun": null,
      "salesPerDay": null,
      "marketPrices": { "GB": { "price": 4, "compareAtPrice": null, "currency": "GBP" } }
    }
  ]
}
```

Sales fields fill in from the second run under the same `monitorName`. In monitoring mode each record also has `changeTypes`, `previousPriceMin`, `priceChangePercent`, `variantChanges` (price, availability and compare-at changes per variant) and `previouslySeenAt`.

A store summary (`storeSummary`, same store, first 120 products read):

```json
{
  "recordType": "store",
  "storeName": "ColourPop",
  "country": "US",
  "currency": "USD",
  "myshopifyDomain": "colourpop-prd.myshopify.com",
  "productCount": 120,
  "publishedProductsCount": 1049,
  "collectionCount": 189,
  "priceMin": 5,
  "priceMedian": 15,
  "priceMax": 159,
  "inStockShare": 85.8,
  "newProductsLast7Days": 31,
  "newProductsLast30Days": 74,
  "theme": { "name": "Colourpop/Main", "schemaName": "Colourpop", "themeStoreId": null },
  "apps": [
    { "name": "Okendo", "category": "reviews" },
    { "name": "Klaviyo", "category": "email-sms" },
    { "name": "Attentive", "category": "email-sms" },
    { "name": "Gorgias", "category": "support" },
    { "name": "Klarna", "category": "payments" }
  ],
  "socialProfiles": {
    "instagram": "https://instagram.com/colourpopcosmetics",
    "tiktok": "https://www.tiktok.com/@colourpopco"
  },
  "bestSellers": [
    { "rank": 1, "productId": 8091207302, "handle": "flexitarian", "title": "Flexitarian" }
  ]
}
```

Comparison records (`compareAcrossStores`) carry `matchKey`, `matchedBy` (`sku` or `title`), `storeCount`, `cheapestStore`, `cheapestPrice`, `highestPrice`, `priceSpreadPercent` and every store's offer. Every run also writes `RUN_SUMMARY` to the key-value store: the outcome (`COMPLETE`, `PARTIAL`, `NO_CHANGES`, `EMPTY` or `FAILED`), counts, warnings and the reason for any failed store.

### Pricing

Pay only for results delivered. Stores that fail are never charged.

| Event                                                    | Free and Bronze | Silver | Gold  |
| -------------------------------------------------------- | --------------- | ------ | ----- |
| Product (all its variants)                               | $0.80           | $0.70  | $0.60 |
| Change (monitoring mode: one changed product)            | $1.00           | $0.90  | $0.80 |
| Unchanged product checked (monitoring mode)              | $0.15           | $0.13  | $0.12 |
| Stock and sales tracking (add-on per product)            | $0.50           | $0.45  | $0.40 |
| Price in another market (add-on per product per country) | $0.30           | $0.27  | $0.24 |
| Store summary (per store)                                | $3.00           | $2.70  | $2.40 |
| Cross-store comparison group                             | $1.00           | $0.90  | $0.80 |

Prices are per 1,000; your tier depends on your Apify plan. Each run also has a $0.00005 start fee. The variants output format is billed per product, not per row. The first monitoring run (every product new) is billed at the product price, not the change price. Cross-store comparisons run in catalogue mode only.

### How sales estimates work

Shopify stores that track stock often publish the count per variant. Each run records it under your `monitorName`; the next run reads the drop as units sold (and a rise as a restock), divided by the days between runs. Returns, cancellations and stock moved between warehouses are not visible, so treat it as an estimate. Stores that hide stock get `null`, never a guess.

### Limits

- Only what a Shopify store publishes to every visitor: no orders, revenue or customer data. Stores can hide stock, the best-selling sort or market prices; the fields are then null and the run says so in its warnings.
- Shopify serves up to 25,000 products a store through its product feed.
- Best-seller rank follows the store's own sort over its catalogue (or its default collection, which some stores redirect to).
- Two runs with the same `monitorName` and store at the same time share one memory; schedule them apart.
- Monitoring chosen collections or product URLs reports changes to those products, but not products leaving a collection; monitor the whole store for removals.
- A market a store does not sell to separately (same currency as home) is skipped with a warning; `UK` is read as `GB`.

### Changelog

- **0.1** (October 2026): catalogues by store, collection or product; filters; stock and sales estimates; best-seller rank; prices by market; store summaries with app detection; cross-store comparison; changes-only monitoring; flat variant rows and field selection.

# Actor input Schema

## `stores` (type: `array`):

Shopify stores by domain or URL, one per line, for example "colourpop.com". Each store's whole catalogue is read (up to Max products per store). Headless stores are found through their Shopify address.

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

Optional. Collection pages (brand.com/collections/sale) read only that collection; product pages (brand.com/products/x) read only that product. Mix stores freely.

## `domains` (type: `array`):

Optional. Same as Stores; accepted so inputs from other Shopify actors work unchanged.

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

Products read per store (Shopify serves 250 a page, up to 25,000).

## `maxProducts` (type: `integer`):

Optional. Same as Max products per store.

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

Optional. Stop after this many records in total (a cost cap). In monitoring mode, unchanged products checked are billed in bulk and are not capped by it.

## `minPrice` (type: `number`):

Optional. Only products with a variant at or above this price.

## `maxPrice` (type: `number`):

Optional. Only products with a variant at or below this price.

## `inStockOnly` (type: `boolean`):

Only products with at least one variant available.

## `onSaleOnly` (type: `boolean`):

Only products with a variant below its compare-at price.

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

Optional. Only these vendors (as the store names them).

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

Optional. Only these product types.

## `tags` (type: `array`):

Optional. Only products with any of these tags.

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

Optional. Only products whose title contains any of these words.

## `publishedWithinDays` (type: `integer`):

Optional. Only products published in the last N days (new arrivals).

## `includeStock` (type: `boolean`):

Exact stock per variant where the store publishes it, and, from the second run on, units sold since the last run, sales per day and days of stock left. One extra request per product; billed per product.

## `includeInventoryDetails` (type: `boolean`):

Optional. Same as Stock and sales.

## `markets` (type: `array`):

Optional. Country codes (GB, DE, CA, AU…). For stores selling internationally with Shopify Markets, each product's prices in those countries and currencies. One extra request per product and country. A country the store does not sell to separately is skipped (not billed); UK is read as GB.

## `bestSellerRank` (type: `boolean`):

Rank products by the store's own best-selling order, with the change since the last run.

## `bestSellerDepth` (type: `integer`):

How many top sellers to rank.

## `storeSummary` (type: `boolean`):

One record per store: catalogue size, price range and median, share on sale, average discount, new products in 7 and 30 days, top vendors, types and tags, theme, apps detected (reviews, email, subscriptions, payments…), social profiles and top sellers.

## `compareAcrossStores` (type: `boolean`):

Find the same product in several stores (by SKU, else by title) and report who sells it cheapest and the price spread.

## `monitorMode` (type: `boolean`):

Remember each run and return only products that changed since the last run under the same Monitor name. The first run reports every product as new. Unchanged products cost a fraction of a result.

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

Runs with the same name compare with each other, and share stock history for sales estimates.

## `changeTypes` (type: `array`):

Which changes count.

## `priceChangePercent` (type: `number`):

Ignore price moves smaller than this percentage.

## `rankChangeMin` (type: `integer`):

Report a best-seller move only when the rank changes by at least this much.

## `outputFormat` (type: `string`):

Products keep variants as a list; variants gives one plain row per variant for CSV and Excel.

## `includeDescriptionHtml` (type: `boolean`):

Also return the product description as HTML (plain text is always included).

## `fields` (type: `array`):

Optional. Keep only these fields, for example \["title", "priceMin", "variants.sku"]. Dotted paths reach into lists.

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

Optional. The actor picks proxies itself (direct, then datacenter, then residential only if a store blocks the others).

## Actor input object example

```json
{
  "stores": [
    "colourpop.com"
  ],
  "maxProductsPerStore": 1000,
  "inStockOnly": false,
  "onSaleOnly": false,
  "includeStock": false,
  "bestSellerRank": false,
  "bestSellerDepth": 100,
  "storeSummary": false,
  "compareAcrossStores": false,
  "monitorMode": false,
  "monitorName": "default",
  "changeTypes": [
    "new-product",
    "removed-product",
    "price-drop",
    "price-rise",
    "discount-started",
    "discount-ended",
    "sold-out",
    "back-in-stock",
    "variants-changed",
    "best-seller-move"
  ],
  "priceChangePercent": 0,
  "rankChangeMin": 5,
  "outputFormat": "products",
  "includeDescriptionHtml": false
}
```

# Actor output Schema

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

Products (or variant rows), store summaries, comparisons and changes.

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

No description

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

No description

## `stores` (type: `string`):

No description

## `comparison` (type: `string`):

No description

## `runSummary` (type: `string`):

Outcome (COMPLETE, PARTIAL, NO_CHANGES, EMPTY, FAILED), counts, warnings and any failed stores.

# 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 = {
    "stores": [
        "colourpop.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("cleanpull/shopify-store-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 = { "stores": ["colourpop.com"] }

# Run the Actor and wait for it to finish
run = client.actor("cleanpull/shopify-store-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 '{
  "stores": [
    "colourpop.com"
  ]
}' |
apify call cleanpull/shopify-store-scraper --silent --output-dataset

```

## MCP server setup

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