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

Scrape the full product catalog of any Shopify store, even past Shopify's 25,000-product limit: variants, prices, compare-at prices, availability, stock counts, images. Changes only mode returns new products, price drops and restocks since the last run. Plain HTTP, no proxy needed.

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

## Pricing

from $0.50 / 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

### What does Shopify Store Scraper do?

Shopify Store Scraper exports the **full product catalog of any Shopify store**: every product with its variants, prices, compare-at prices, availability, SKUs, images, tags and description, plus **stock counts per variant** when the store publishes them. Give it one domain or a thousand. It works as a **Shopify products API** without an API key, a login or a browser.

It also reads what other Shopify scrapers miss. Shopify's public products feed (`/products.json`) stops at **25,000 products** and answers `Page * Limit exceeds the 25000 limit` past that point. On a large store, a scraper that only reads the feed returns the first 25,000 products and stops without telling you. This Actor detects the cap, lists every product from the store's sitemap and reads the missing ones one by one, so the catalog comes out whole.

| Store (measured 17 to 19 Oct 2026) | Products in the sitemap | Read by `/products.json` | Lost by a feed-only scraper |
|---|---|---|---|
| jbhifi.com.au | 103,943 | 25,000 | 76% |
| windsorstore.com | 51,659 | 25,000 | 52% |
| kith.com | 34,687 | 25,000 | 28% |
| culturekings.com.au | 32,131 | 25,000 | 22% |

On kith.com this Actor returned 34,982 products in one run: 25,000 from the feed and 9,982 from the sitemap.

### Why use Shopify Store Scraper?

**Competitor price monitoring.** Switch Mode to **Changes only**, schedule a daily run and get only what moved since the last one: new products, price drops and increases, compare-at price changes, back in stock, out of stock, variants added or removed, and removed products. Each change comes with its before and after values. Quiet days cost little more than the start fee.

**Catalog research and product sourcing.** Export a brand's whole range with prices and options to compare assortments, spot discounts (`onSale`, `maxDiscountPercent`) or import products into a spreadsheet.

**Stock tracking.** About a third of Shopify stores publish exact stock per variant on their product pages. Turn on **Include stock, barcode and weight** to read it, with the barcode (GTIN, EAN, UPC) for matching products across stores.

What you get on top of the data:

- whole stores, single collections or single product URLs, custom domains and `.myshopify.com` addresses;
- a clear status per store when a site is not a Shopify store, is password protected or has closed its feed, instead of a silent empty result;
- a webhook called at the end of the run, for Slack, Discord, Make, Zapier or your own endpoint;
- Apify scheduling, API access, integrations and proxy rotation.

### What data can Shopify Store Scraper extract?

| Field | Description |
|---|---|
| `title`, `handle`, `url` | Product name, handle and page |
| `vendor`, `productType`, `tags` | Brand, type and tags set by the store |
| `priceMin`, `priceMax`, `currency` | Price range across variants, in the store currency |
| `compareAtPriceMax`, `onSale`, `maxDiscountPercent` | Crossed-out price and discount |
| `available`, `availableVariantsCount` | Whether the product and how many variants can be bought |
| `totalInventory` | Sum of published stock counts (null when the store publishes none) |
| `variants` | Each variant: `variantId`, `sku`, `barcode`, `price`, `compareAtPrice`, `available`, `inventoryQuantity`, options, weight, image |
| `options`, `images`, `description` | Options with their values, image URLs, plain text description |
| `createdAt`, `publishedAt`, `updatedAt` | Product dates |
| `changeType`, `changes` | In Changes only mode: `new`, `changed` or `removed`, and the list of changes |

### How to scrape a Shopify store

1. Open the Input tab and type one or more store domains, for example `allbirds.com`.
2. Leave **Max products per store** at 0 for the whole catalog, or set a number for a quick test.
3. Press **Start**.
4. Download the products from the Output tab as JSON, CSV, Excel or HTML, or read them through the API.

To monitor prices, set Mode to **Changes only**, run it once to record the snapshot, then schedule it (daily is typical).

### How much does it cost to scrape a Shopify store?

You pay per product returned, plus a small start fee.

| Event | Free plan | Bronze | Silver | Gold and above |
|---|---|---|---|---|
| Product, per 1,000 | $1.00 | $0.85 | $0.70 | $0.50 |
| Detailed product (stock, barcode), per 1,000 | $2.00 | $1.70 | $1.40 | $1.00 |
| Actor start | $0.002 | $0.002 | $0.002 | $0.002 |

A 1,000-product store costs about $1.00 on the Free plan. The 35,000-product Kith catalog costs about $35 on the Free plan and $17.50 on Gold. In Changes only mode you pay only for the products that changed, and the first run, which records the snapshot, charges nothing beyond the start. Sites that are not Shopify stores and products read twice through overlapping collections are never charged.

### Input

See the Input tab for every option. The fields that matter:

- **Stores, collections or products**: one per line. A domain reads the whole store, a `/collections/<handle>` URL reads one collection, a `/products/<handle>` URL reads one product with its stock.
- **Mode**: Full catalog, or Changes only.
- **Max products per store**: 0 for everything.
- **Include stock, barcode and weight**: one extra request per product.
- **Webhook URL**: called once at the end of a run that returned at least one product.

```json
{
    "stores": ["allbirds.com", "https://kith.com/collections/mens"],
    "mode": "catalog",
    "maxProductsPerStore": 0,
    "includeDetails": false
}
```

### Output

You can download the dataset in various formats such as JSON, HTML, CSV, or Excel. One item per product:

```json
{
    "storeDomain": "allbirds.com",
    "storeName": "Allbirds",
    "currency": "USD",
    "productId": 6987654321098,
    "title": "Men's Tree Runners",
    "url": "https://allbirds.com/products/mens-tree-runners",
    "vendor": "Allbirds",
    "productType": "Shoes",
    "priceMin": 98.0,
    "priceMax": 98.0,
    "compareAtPriceMax": 120.0,
    "onSale": true,
    "maxDiscountPercent": 18,
    "available": true,
    "variantsCount": 8,
    "availableVariantsCount": 6,
    "totalInventory": 142,
    "variants": [
        {"variantId": 40123456789012, "title": "10", "sku": "TR-M-10", "price": 98.0,
         "compareAtPrice": 120.0, "available": true, "inventoryQuantity": 25}
    ],
    "source": "products-feed",
    "changeType": null,
    "changes": []
}
```

In Changes only mode, a changed product carries its changes:

```json
{
    "changeType": "changed",
    "title": "Example Lip Gloss",
    "changes": [
        {"type": "price-drop", "variantId": 40123456789099, "variantTitle": "Default Title",
         "before": 14.0, "after": 11.0}
    ]
}
```

The `SUMMARY` record of the run's key-value store gives one line per store: status, products read, products read beyond 25,000, and whether the catalog was read completely.

### Tips and advanced options

- **Changes only needs whole catalogs.** A product is reported new or removed only after a run has read the store to the end, so keep Max products per store at 0 for monitoring. A run cut short never reports products as removed.
- **Stock moves with every sale.** With details on, each sale changes `inventoryQuantity` and comes back as a change. Leave details off if you only watch prices and availability.
- **Several monitors on one store.** Give each one its own Monitor name.
- **Large stores take longer, not more per product.** Reading past 25,000 goes through product pages, about 10 per second over Apify proxy IPs: 10,000 extra products take about 16 minutes.

### FAQ, disclaimers and support

#### Is it legal to scrape Shopify stores?

This Actor reads the public product data that Shopify stores publish for their own storefronts. Our Actors are ethical and do not extract any private user data, such as email addresses, gender, or location. They only extract what the user has chosen to share publicly. We therefore believe that our Actors, when used for ethical purposes by Apify users, are safe. However, you should be aware that your results could contain personal data. Personal data is protected by the GDPR in the European Union and by other regulations around the world. You should not scrape personal data unless you have a legitimate reason to do so. If you're unsure whether your reason is legitimate, consult your lawyers.

#### Why does a store return nothing?

The SUMMARY record says why: `not-shopify` (a headless storefront or another platform), `password-protected` (a store before launch), `products-hidden` (the store closed its public feed and product pages), or `blocked`. None of these is charged.

#### Does it work with headless Shopify stores?

Often. Some brands run a headless front on `www.` and keep the Shopify storefront on the bare domain (Fashion Nova does). The Actor tries the address you gave, then the domain without `www.`, then the `.myshopify.com` address.

#### How do I call it from my code?

Use the API tab for ready-made examples in Python, JavaScript and cURL, or the Apify integrations for Make, Zapier, n8n and Google Sheets.

Something wrong or missing? Open an issue in the Issues tab and it gets fixed.

# Actor input Schema

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

One per line. A domain (<code>allbirds.com</code>) or store URL reads the whole catalog. A collection URL (<code>https://kith.com/collections/mens</code>) reads that collection. A product URL (<code>https://allbirds.com/products/mens-tree-runners</code>) reads one product with its stock. Works with custom domains and <code>.myshopify.com</code> addresses; a site that is not a Shopify store is reported in the log and not charged.

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

<b>Full catalog</b> returns every product on every run.<br><br><b>Changes only</b> keeps a snapshot of each store between runs and returns only products that are new, changed (price, compare-at price, availability, stock, variants added or removed) or removed since the last run, each with a <code>changes</code> list of before and after values. The first run records the snapshot and returns nothing. Products are only reported new or removed once a run has read the whole catalog, so keep <b>Max products per store</b> at 0 for monitoring.

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

Stop each store or collection after this many products. <code>0</code> reads the whole catalog, beyond Shopify's 25,000 limit included. A product URL always counts as one.

## `includeDetails` (type: `boolean`):

Reads each product's own page as well: stock quantity per variant when the store publishes it (about a third of stores do), barcode (GTIN/EAN/UPC), whether stock is tracked. One extra request per product, charged as a detailed product instead of a product. Products read through the sitemap beyond 25,000 and product URLs always come with these fields.

## `reportStockCountChanges` (type: `boolean`):

With <b>Include stock, barcode and weight</b> on, also report a product when its stock count moves (for example 2207 to 2206). Off by default: a stock count moves with every sale, so each sale would be a returned and charged change. Going out of stock and coming back in stock are always reported.

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

The snapshot is kept per store, collection or product URL. Give a name to run several independent monitors on the same store, for example one daily and one weekly.

## `webhookUrl` (type: `string`):

If set, the Actor POSTs a small JSON summary to this URL at the end of the run, <b>only when at least one product was returned</b>: <code>actor</code>, <code>runId</code>, <code>datasetId</code>, <code>mode</code>, <code>items</code>, <code>changes</code> (count per new, changed, removed) and one line per store. With Changes only and a daily schedule, that is a notification when a competitor changes a price.<br><br>Use the final URL: a redirect is not followed, except a 307 or 308, which gets the same POST once. An unreachable webhook is logged as a warning and never fails the run. Stored encrypted, since a webhook URL often carries its own access token.

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

Leave as is. Shopify answers its products feed at full speed, but limits product pages to about one every two seconds per IP from cloud servers. Past 25,000 products the Actor reads product pages one by one, so it spreads them over 24 Apify proxy IPs and moves a lane to a new IP when the store slows it down. Without a proxy the Actor still works, only slower beyond 25,000 products. The per-product price is the same whatever you choose.

## Actor input object example

```json
{
  "stores": [
    "allbirds.com"
  ],
  "mode": "catalog",
  "maxProductsPerStore": 100,
  "includeDetails": false,
  "reportStockCountChanges": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

All products in the default dataset.

## `summary` (type: `string`):

Status of each store, collection or product URL: products read, catalog complete or not, products read beyond the 25,000 limit.

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

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

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

```

## MCP server setup

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