# Shopify Scraper - Products & Price Monitor (`wrenfield-data/shopify-store-scraper`) Actor

Scrape any Shopify store's products (prices, compare-at and discount %, variants, SKUs, stock, images, tags, dates) from its public JSON feed. Monitor mode compares with the previous run and returns only new, removed, price-changed and stock-changed products.

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

## Pricing

from $0.75 / 1,000 product results

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 & Price Monitor - Products, Variants, Stock Changes

**Scrape the full product catalog of any Shopify store and track competitors' price and stock changes.** Paste store domains such as `allbirds.com` and get every product with **title, vendor, product type, tags, created/updated/published dates, price range, compare-at price and discount %, availability, images and every variant (SKU, price, compare-at, stock, options)**. Turn on **monitor mode** to get only the products that are **new, removed, price-changed or stock-changed** since the last run.

It reads the store's own public JSON feed (`/products.json`), so it is fast, cheap and needs no browser, no login and no API key.

- ✅ **Any Shopify store**: brand domains (`gymshark.com`), `*.myshopify.com` or a pasted collection or product URL. Domains that aren't Shopify stores get a **free** error row explaining why (not Shopify, password-protected, feed disabled, unreachable).
- ✅ **Change monitor built in**: snapshots are kept in your own key-value store, one per store. Schedule it daily and get a clean change feed with **previous and current values**.
- ✅ **Collections and keywords**: limit the scrape to collection handles (e.g. `mens-shoes`) or keywords that match the title, vendor, product type or tags.
- ✅ **Inventory quantities when the store exposes them**, plus barcodes (GTIN/UPC). Checked automatically per store.
- ✅ **Polite by design**: one request at a time per store with jittered pauses. A proxy is used only if a store rate-limits (HTTP 429/430).
- ✅ **Pay only for results**: error rows, retries and rate-limited requests are free.

💸 **$1 per 1,000 products** with variants, prices and stock. In monitor mode, **$3 per 1,000 changes** (price drops, restocks, new or removed products). Try it on Apify's free plan ($5 of monthly credit, no credit card).

#### Sample output

| Store | Product | Price | Compare at | Type |
|---|---|---|---|---|
| allbirds.com | Women's Allbirds Flip Flop - Dust… | 25 | 50 | Shoes |
| allbirds.com | Men's Canvas Runner NZ - Deep Nav… | 100 | – | Shoes |
| allbirds.com | Women's Canvas Runner NZ - Deep N… | 100 | – | Shoes |

<sub>Real rows from the prefilled example run (Oct 19, 2026), 5 of 30+ fields shown. Export as JSON, CSV, Excel or HTML, or read them through the API.</sub>

### Who uses it

- **Ecommerce sellers and brands**: watch competitors' prices, discounts, new launches and sell-outs.
- **Competitor-tracking and pricing teams**: daily price/stock change feeds into Sheets, Slack, BI or a database.
- **Dropshippers and product researchers**: find what a store sells, how it is priced and what is on sale.
- **Agencies and data teams**: catalog exports and assortment analysis across many stores.

### How to use

1. Add one or more **Shopify stores** (domains or URLs).
2. Optional: add **collection handles**, a **keyword filter** and **max products per store**.
3. For change tracking, turn on **Monitor mode** and schedule the Actor (for example daily).
4. Click **Start**. Download the results as JSON, CSV or Excel, or read them through the API.

### Input example

```json
{
    "stores": ["allbirds.com", "https://www.gymshark.com/collections/new-releases", "kyliecosmetics.com"],
    "collections": [],
    "keywords": ["runner", "hoodie"],
    "maxProductsPerStore": 1000,
    "monitorMode": false,
    "includeInventory": false
}
```

### Output: product rows (`type = product`)

```json
{
    "type": "product",
    "store": "allbirds.com",
    "storeName": "Allbirds",
    "storeUrl": "https://allbirds.com",
    "currency": "USD",
    "productId": 7340901859408,
    "title": "Women's Allbirds Flip Flop - Dusty Pink",
    "handle": "womens-allbirds-flip-flop-dusty-pink",
    "url": "https://allbirds.com/products/womens-allbirds-flip-flop-dusty-pink",
    "vendor": "Allbirds",
    "productType": "Shoes",
    "tags": ["allbirds::gender => womens", "allbirds::material => sugar"],
    "createdAt": "2026-03-27T14:03:50-07:00",
    "updatedAt": "2026-10-08T18:17:32-07:00",
    "publishedAt": "2026-09-25T16:58:13-07:00",
    "price": 25.0, "priceMin": 25.0, "priceMax": 25.0,
    "compareAtPrice": 50.0, "discountPercent": 50.0, "onSale": true,
    "available": true, "variantsCount": 7, "availableVariantsCount": 4,
    "image": "https://cdn.shopify.com/s/files/1/1104/4168/files/A12513_...png",
    "images": ["https://cdn.shopify.com/..."],
    "options": [{"name": "Size", "values": ["5", "6", "7", "8", "9", "10", "11"]}],
    "variants": [
        {"id": 42146889039952, "title": "5", "sku": "A12513W050", "price": 25.0, "compareAtPrice": 50.0,
         "available": false, "options": {"Size": "5"}, "grams": 455, "requiresShipping": true}
    ],
    "description": "Sun on your feet. Comfort underneath...",
    "scrapedAt": "2026-10-09T01:22:00Z"
}
```

With **Inventory quantities & barcodes** on, variants also get `barcode` and, if the store exposes it, `inventoryQuantity` and `inventoryPolicy`, and the product gets a total `inventoryQuantity`. Many stores hide quantities. The Actor checks once per store and skips the extra requests when they are hidden. Prices are in the store's currency (`currency`).

### Monitor mode: change rows (`type = change`)

Each run compares the store with its saved snapshot and outputs one row per change:

| `changeType` | When | `previous` / `current` |
|---|---|---|
| `new` | Product appeared since the last run | `current` has price, compare-at, availability, dates, tags; the full product is in `product` |
| `removed` | Product is gone (double-checked on its product page) | `previous` has the last price and availability |
| `price_changed` | Any variant's price or compare-at price changed | `priceMin`/`priceMax`, `priceChangePercent`, `priceDirection`, and `variantChanges` with old/new price per variant |
| `stock_changed` | Product or a variant went in or out of stock (optionally: inventory quantity changed) | `available`, `availableVariantsCount`, `inventoryQuantity`; `stockEvent` = `back_in_stock`, `out_of_stock` or `variant_stock_changed` |

```json
{
    "type": "change", "changeType": "price_changed", "store": "allbirds.com",
    "title": "Women's Allbirds Flip Flop - Dusty Pink", "url": "https://allbirds.com/products/womens-allbirds-flip-flop-dusty-pink",
    "priceDirection": "down", "priceChangePercent": -28.57,
    "previous": {"priceMin": 35.0, "priceMax": 35.0},
    "current": {"priceMin": 25.0, "priceMax": 25.0, "compareAtPrice": 50.0, "discountPercent": 50.0},
    "variantChanges": [{"variantId": 42146889039952, "variantTitle": "5", "sku": "A12513W050", "previousPrice": 35.0, "currentPrice": 25.0, "changePercent": -28.57}],
    "detectedAt": "2026-10-09T01:30:43Z", "previousRunAt": "2026-10-08T01:30:00Z"
}
```

**How monitoring works**

- Snapshots live in a **named key-value store** in your account (default `shopify-store-monitor`), one record per store. Different collection or keyword filters get separate snapshots automatically. Use **Monitor ID** to keep two monitors of the same store apart.
- On the **first run** there is nothing to compare with: the Actor saves the baseline and outputs the full product list (turn that off with *Output full products on the first monitor run*).
- **Removed** products are reported only when the whole catalog (or collection) was read in both runs, so set *Max products per store* above the store's size. When a scan is cut short, new products are still detected by their creation date.
- If any page fails, the snapshot is **not** updated, so the next run still compares against good data. You never get false "removed" rows from a network error.

### Errors are free

Rows with `type = error` are never charged. `errorType` is one of `not_shopify`, `password_protected`, `feed_disabled`, `unreachable`, `collection_not_found` (with a list of available collections), `product_not_found`, `fetch_failed` or `invalid_input`.

### Pricing

This Actor uses **pay-per-event** pricing: you pay per product row and per change row. Platform usage and proxy are included. See the **Pricing** tab for the current prices. Error rows, retries and rate-limited requests are free. Set a **maximum cost per run** in the run options: the Actor stops cleanly at that limit and keeps everything it collected.

### Performance

Measured on the Apify platform (October 2026), see the dev notes for run IDs:

- Whole-catalog scrape: about 250 products per request, typically **1,000 products in 10-20 seconds** per store.
- Several stores run in parallel (default 5), each fetched politely one page at a time.
- Inventory mode adds one request per product (about 1 s each per store at the default delay).

### Related tools

Find Shopify stores with the tech stack detector, then compare their prices across Google Shopping and Walmart.

- [Google Shopping Price Comparison](https://apify.com/wrenfield-data/google-shopping-price-comparison): compare seller prices for any product or UPC
- [Tech Stack Detector](https://apify.com/wrenfield-data/website-tech-stack-detector): see which CMS, ecommerce, analytics and CRM tools a site uses
- [Walmart Scraper](https://apify.com/wrenfield-data/walmart-product-scraper): Walmart prices, stock, sellers and reviews

All of these are maintained by the same developer and export flat JSON, CSV or Excel, so you can feed one tool's dataset into the next.

### FAQ

**Does it work on every Shopify store?**
It works on stores that keep Shopify's public product feed on, which is the default and most stores. Stores that are password-protected or that block the feed get a free error row.

**How many products can I get?**
The whole public catalog. Shopify returns up to 250 products per page and the Actor pages through all of them.

**Why is `inventoryQuantity` missing?**
Most stores don't publish stock quantities; you still get `available` per variant. When a store does publish them, turn on *Inventory quantities & barcodes*.

**Can I get the price in another currency or market?**
You get the store's default currency (`currency`). For international storefronts on their own domain (e.g. `uk.gymshark.com`), add that domain.

**Do I need a proxy?**
No. Requests go directly to the store. The proxy setting is a fallback that is used only for stores that rate-limit (HTTP 429/430).

**Can I use it from Make, Zapier, n8n, Python or an AI agent?**
Yes. Use the Apify API, the Python/JavaScript clients, schedules and webhooks, any Apify integration or the Apify MCP server.

**Is it legal to scrape Shopify stores?**
The Actor reads only public product data that every Shopify storefront publishes at `/products.json`, with no login and no personal data. Each store's own terms of service and your local laws still apply, and some stores forbid automated access. Use the data responsibly (for example price comparison and market research) and don't overload stores. The Actor paces its requests for that reason. If you're unsure, ask a lawyer.

# Actor input Schema

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

Store domains or URLs, one per line, e.g. <code>allbirds.com</code>, <code>https://www.gymshark.com</code>. You can also paste a collection URL (<code>/collections/mens</code>) or a product URL (<code>/products/handle</code>) to scrape just that collection or product. Domains that are not Shopify stores get a free error row.

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

Only scrape these collections in every store, e.g. <code>mens-shoes</code> (the part after <code>/collections/</code> in the URL). Leave empty for the whole catalog.

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

Keep only products whose title, handle, vendor, product type or tags contain at least one of these words (case-insensitive).

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

Maximum products per store (after the keyword filter). Shopify returns up to 250 products per request. In monitor mode, use a limit above the store's catalog size so removed products can be detected.

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

Compare each store with the previous run and output only <b>new</b>, <b>removed</b>, <b>price-changed</b> and <b>stock-changed</b> products, with previous and current values. Snapshots are kept in a named key-value store, one record per store. Schedule the Actor (e.g. daily) to get a change feed.

## `outputProductsOnFirstRun` (type: `boolean`):

On the first monitor run for a store there is nothing to compare with. When on, the full product list is output (billed as products); when off, the first run only saves the baseline.

## `monitorStoreName` (type: `string`):

Named key-value store in your account that keeps the snapshots. Use a different name to keep separate monitors.

## `monitorId` (type: `string`):

Optional label that separates snapshots when you monitor the same store with different tasks. Collections and keyword filters already get their own snapshot.

## `includeInventory` (type: `boolean`):

Fetch each product's <code>/products/\<handle>.js</code> to add variant barcodes and <code>inventoryQuantity</code> when the store exposes it. Checked once per store; skipped automatically if the store hides quantities. Adds one request per product.

## `trackQuantityChanges` (type: `boolean`):

Monitor mode with inventory on: also output a <code>stock_changed</code> row when a variant's inventory quantity changes (not only when it goes in or out of stock). Useful for sales-velocity tracking, but produces many more change rows.

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

Add the product description as plain text (HTML removed, max 5,000 characters).

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

Requests go directly to the store first. The proxy is used only for a store that rate-limits the Actor (HTTP 429/430). Default: Apify datacenter proxy.

## `requestDelaySecs` (type: `number`):

Minimum pause between requests to the same store (with jitter). The default of 1 second matches the Crawl-delay most Shopify stores publish in robots.txt. The delay grows automatically if a store rate-limits.

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

How many different stores are scraped at the same time. Each store is always fetched one page at a time.

## `maxRetries` (type: `integer`):

Retries with backoff for rate limits, timeouts and server errors.

## Actor input object example

```json
{
  "stores": [
    "allbirds.com",
    "kyliecosmetics.com"
  ],
  "maxProductsPerStore": 20,
  "monitorMode": false,
  "outputProductsOnFirstRun": true,
  "monitorStoreName": "shopify-store-monitor",
  "includeInventory": false,
  "trackQuantityChanges": false,
  "includeDescription": true,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "requestDelaySecs": 1,
  "maxConcurrency": 5,
  "maxRetries": 6
}
```

# Actor output Schema

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

No description

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

No description

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

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

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

```

## MCP server setup

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