# Shopify Scraper - Products, Variants, Prices & Stock (`offgrid-labs/shopify-store-products`) Actor

Shopify scraper and Shopify product scraper for any public Shopify store: all Shopify products with every variant's SKU, price, compare-at price and stock, from the store's own products.json. Built for Shopify price monitoring and catalog export. $1.00 per 1,000 products.

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

## Pricing

from $1.00 / 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.

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, variants, prices and stock from any Shopify store

**TL;DR:** Shopify Scraper exports the full product catalog of any public Shopify store (every variant's SKU, price, compare-at price and stock status, plus vendor, type, tags, images and description) as JSON, CSV or Excel. It is built for e-commerce teams, price-monitoring and product-research workflows, and AI agents that need clean Shopify product data. You pay **$1.00 per 1,000 products** (all variants included); sites that aren't on Shopify cost nothing.

It reads the public `products.json` feed that Shopify storefronts publish, so there is no browser, no login and no CAPTCHA. Paste store URLs, click **Start**, download the dataset.

### What data you get

One dataset row per product. Variants are nested, so one row = one product = one charged result.

| Field | What it is | Example |
|---|---|---|
| `store`, `storeName` | Store hostname and name | `www.deathwishcoffee.com`, `Death Wish Coffee` |
| `productId`, `handle`, `url` | Shopify product ID, URL slug, product page | `10200955748407` |
| `title`, `vendor`, `productType`, `tags` | Catalog attributes set by the merchant | `Knit Happens Holiday Sweater`, `Roody`, `Apparel` |
| `priceMin`, `priceMax`, `currency` | Lowest / highest variant price, ISO currency | `59.99`, `59.99`, `USD` |
| `compareAtPriceMax`, `onSale` | Original ("compare at") price and sale flag | `null`, `false` |
| `available`, `variantCount` | In stock (any variant) and number of variants | `true`, `6` |
| `variants[]` | `variantId`, `title`, `sku`, `price`, `compareAtPrice`, `available`, `option1-3`, `grams`, `requiresShipping`, `imageUrl` | `{"sku":"USDWGD02","price":59.99}` |
| `totalInventory`, `variants[].inventoryQuantity` | Stock counts, only with **Include stock quantities** and only if the store publishes them | number or `null` |
| `options[]` | Option names and values | `Size: S, M, L, XL` |
| `imageUrl`, `images[]` | Main image and all image URLs | `https://cdn.shopify.com/...` |
| `description` | Description as plain text (optional) | `Ugly sweater. Uglier cookies...` |
| `collection` | Collection handle the product came from | `sale` |
| `createdAt`, `updatedAt`, `publishedAt`, `scrapedAt` | ISO 8601 UTC timestamps | `2026-11-02T14:52:06.000Z` |
| `sourceUrl` | The `products.json` page the row came from | `https://.../products.json?limit=20&page=1` |

### Use cases

- **Competitor price monitoring**: schedule daily runs, compare `priceMin`, `compareAtPriceMax` and `onSale` with yesterday, and alert on price cuts and sales.
- **Stock and sell-out tracking**: watch `available` per variant, or `totalInventory` when stock counts are published.
- **Product research and dropshipping**: see what a store sells, which vendors it carries and how it prices them.
- **Catalog migration and feeds**: export a catalog (yours, or a supplier's with permission) to CSV or Google Sheets.
- **Market analysis**: compare assortments, price ranges and new launches (`createdAt`, `publishedAt`) across many stores.

### How to scrape a Shopify store

1. Enter one or more **Shopify store URLs**: a domain (`allbirds.com`), a homepage, or a collection URL (`https://store.com/collections/sale`).
2. Set **Max products per store** (default 1,000).
3. Optional: limit to **Collections**, filter by **Keyword**, keep **Only in-stock products**, or turn on **Include stock quantities**.
4. Click **Start**, then download JSON, CSV, Excel or HTML, or read the dataset through the API.

#### Shopify price monitoring

Create an Apify **Schedule** (for example daily at 06:00) with your store list. Compare each run with the previous one on `productId` + `variants[].variantId`: a lower `price` is a price cut, `onSale: true` is a sale, `available: false` is a sell-out. Send the changes to Slack, email or a Google Sheet with Apify integrations or a webhook.

### Input example

```json
{
  "storeUrls": ["https://www.deathwishcoffee.com", "https://www.allbirds.com/collections/mens"],
  "maxProductsPerStore": 500,
  "onlyAvailable": false,
  "includeDescription": true,
  "includeInventory": false
}
```

### Output example

```json
{
  "store": "www.deathwishcoffee.com",
  "storeName": "Death Wish Coffee",
  "productId": 10200955748407,
  "handle": "knit-happens-holiday-sweater",
  "title": "Knit Happens Holiday Sweater",
  "url": "https://www.deathwishcoffee.com/products/knit-happens-holiday-sweater",
  "vendor": "Roody",
  "productType": "Apparel",
  "tags": ["Apparel", "Gingerdead", "Men", "Merch", "Unisex", "Women"],
  "priceMin": 59.99,
  "priceMax": 59.99,
  "compareAtPriceMax": null,
  "onSale": false,
  "currency": "USD",
  "available": true,
  "variantCount": 6,
  "variants": [
    { "variantId": 50230280224823, "title": "S", "sku": "USDWGD02", "price": 59.99, "compareAtPrice": null, "available": true, "option1": "S", "option2": null, "option3": null, "grams": 510, "requiresShipping": true, "imageUrl": null }
  ],
  "options": [{ "name": "Size", "values": ["S", "M", "L", "XL", "2XL", "3XL"] }],
  "imageUrl": "https://cdn.shopify.com/s/files/1/0271/7209/files/...Front2_Web.jpg",
  "images": ["https://cdn.shopify.com/s/files/1/0271/7209/files/...Front2_Web.jpg"],
  "description": "Ugly sweater. Uglier cookies. ...",
  "collection": null,
  "createdAt": "2026-09-29T00:32:14.000Z",
  "updatedAt": "2026-11-03T00:50:39.000Z",
  "publishedAt": "2026-11-02T14:52:06.000Z",
  "sourceUrl": "https://www.deathwishcoffee.com/products.json?limit=20&page=1",
  "scrapedAt": "2026-11-03T00:50:39.173Z"
}
```

The dataset has two views: **Overview - one row per product** (image, title, vendor, price range, stock, sale flag, link) and **All fields - products with variants**.

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

Pay per event: you pay only for products saved. Compute and proxy costs are included. The price is the same on every Apify plan.

| Event | Charged when | Price |
|---|---|---|
| `result` | One product saved (all variants included) | **$1.00 per 1,000** ($0.001 each) |
| `inventory` | Optional: stock counts found for one product (**Include stock quantities** on) | $1.00 per 1,000 |
| `apify-actor-start` | Once per run (Apify's standard start fee) | $0.00005 |

**Worked examples**

- **1,000 products** = 1,000 × $0.001 = **$1.00** (+ $0.00005 start fee).
- A 400-product store, scraped once: **$0.40**. With stock quantities found for all 400: $0.80.
- Daily price monitoring of one 400-product store for 30 days: 12,000 products = **$12.00 per month**.
- Products removed by your filters, non-Shopify sites and stores blocked by robots.txt are not charged (only the start fee).

Set **Maximum cost per run** in the run options and the scraper stops cleanly at that limit.

### How it compares

| | This actor | Writing your own `products.json` script | Browser-based page scraping |
|---|---|---|---|
| Setup | Paste URLs, run | Code pagination, parsing, retries | Selectors per theme; breaks on redesigns |
| Variants, SKUs, compare-at prices | Normalized fields | You map them | Often only the visible price |
| Rate limits (HTTP 429) | Automatic slow-down and retry | You build backoff | Heavy page loads |
| Non-Shopify / blocked sites | Skipped, not charged | Your error handling | Your error handling |
| Output | Dataset: JSON, CSV, Excel, API, integrations | Whatever you build | Whatever you build |
| Cost | $1.00 per 1,000 products, no compute bill | Your time + hosting | Browser compute per page |

### FAQ

#### How do I scrape all products from a Shopify store?

Add the store's domain to **Shopify store URLs** and set **Max products per store** above the catalog size (up to 100,000). The scraper pages through `products.json` until the catalog ends or the limit is reached.

#### How do I scrape only one Shopify collection?

Paste the collection URL (`https://store.com/collections/sale`) or put the handle (`sale`) in **Collections**. The `collection` field shows where each product came from.

#### Can I get Shopify inventory quantities (stock levels)?

Yes, optionally. Turn on **Include stock quantities**: the scraper reads each product's public storefront data (`/products/<handle>.js`). Many stores publish a per-variant `inventoryQuantity` there, summed into `totalInventory`. Stores that hide counts return `null` and are not charged the `inventory` event. Some stores publish capped or placeholder numbers; you get what the store publishes. Without the option you still get `available` (in stock yes/no) per variant.

#### How do I know if a website is a Shopify store?

Just add it. Sites that don't serve a Shopify `products.json` feed are skipped with a log message and are not charged. To detect the platform of many sites at once, use our Tech Stack Detector.

#### Which Shopify stores are not supported?

Password-protected stores, headless storefronts that disable the `products.json` feed, and stores whose robots.txt disallows `/products.json` (skipped by default).

#### Why is `currency` sometimes null?

Currency comes from the store's public `meta.json`. If a store hides it, prices are still in the store's default currency.

#### I see HTTP 429 messages in the log. Is that a problem?

No. Shopify rate-limits fast requests; the scraper slows down and retries automatically. For very large batches, enable Apify Proxy under **Advanced**.

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

The scraper only reads product data the store publishes publicly, does not log in, and by default respects robots.txt. You are responsible for using the data in line with each store's terms and applicable law (for example, don't republish copyrighted images or descriptions without permission).

#### Can an AI agent use this Shopify scraper?

Yes. Through the Apify MCP server an agent (Claude, ChatGPT, Cursor and other MCP clients) can find it with `search-actors`, read its input schema and run it. See **Integrations** below.

#### Found a bug or need a field?

Open an issue on the Issues tab; we aim to respond within 24 hours.

### Integrations

- **API**: `POST https://api.apify.com/v2/acts/offgrid-labs~shopify-store-products/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>` with the input JSON as the body returns the products directly (for runs under 5 minutes; for bigger runs start a run and read its dataset).
- **Python / JavaScript**: `pip install apify-client` / `npm install apify-client`, then `client.actor("offgrid-labs/shopify-store-products").call(run_input={...})`.
- **No-code**: Apify's integrations for **Make**, **Zapier** and **n8n**, plus Google Sheets, Slack, webhooks and Schedules.
- **MCP / AI agents**: add `https://mcp.apify.com?tools=offgrid-labs/shopify-store-products` to your MCP client to expose this actor as a tool.

### Changelog

- **2026-11-03** · README restructured (data table, cost examples, comparison, FAQ, integrations); input field descriptions rewritten with units, defaults and examples for AI agents; output schema and dataset views described; Store title "Shopify Scraper - Products, Variants, Prices & Stock". Removed an incorrect line about plan discounts: the price is a flat $1.00 per 1,000 products on every plan.
- **2026-11-03** · Optional stock quantities (`includeInventory`, `inventory` event) and `totalInventory` field; output schema with Overview and All fields views.
- **2026-11-03** · First release: `products.json` feed, collections, keyword and in-stock filters, robots.txt check, HTTP 429 backoff.

### Related actors

| Actor | What it does |
|---|---|
| Tech Stack Detector | Detect whether a site runs Shopify, WooCommerce, Magento or other technologies (194 signatures) |
| Company Registry Lookup (KYB) | Company records from official business registers, to enrich the business behind a store |
| ATS Jobs Scraper | Open jobs from Greenhouse, Lever, Ashby and Workable boards |

***

```
// OFFGRID LABS ─────────────────────────────────────────────
// status: monitored 24/7 by a self-hosted agent swarm
// health checks: every 6h · auto-repair + rollback on failure
// sources: official public APIs and open data only
// support: open an issue — human-reviewed response within 24h
// ───────────────────────────────────────────────────────────
```

# Actor input Schema

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

Required. Shopify stores to scrape, one per line. Accepts a bare domain (allbirds.com), a homepage URL (https://www.deathwishcoffee.com) or a collection URL (https://store.com/collections/sale - scrapes only that collection). Sites that are not Shopify are skipped and not charged.

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

Maximum number of products saved per store (or per store + collection). One product = one dataset row with all its variants = one charged result. Default 1000; range 1-100000. Example: 500

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

Optional. Only scrape these collection handles in every store: the part after /collections/ in the URL. Leave empty to scrape the whole catalog. Examples: sale, new-arrivals, mens

## `keyword` (type: `string`):

Optional. Keep only products whose title, vendor, product type or tags contain this text (case-insensitive substring). Filtered-out products are not charged. Example: hoodie

## `onlyAvailable` (type: `boolean`):

If true, keep only products with at least one variant available for sale. Default false (all products, in stock or not).

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

If true (default), adds the product description converted to plain text in the description field. Set false for smaller datasets.

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

Optional, default false. Adds inventoryQuantity per variant and totalInventory per product when the store publishes stock counts in its public product data (/products/<handle>.js; many stores do, some don't). One extra request per product. Charged as an extra inventory event ($1.00 per 1,000) only for products where counts were found.

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

If true (default, recommended), skips stores whose robots.txt disallows /products.json. Skipped stores are not charged.

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

Optional. Usually not needed (default: no proxy). Enable Apify Proxy only if a store keeps answering HTTP 429 (rate limited) for large catalogs.

## Actor input object example

```json
{
  "storeUrls": [
    "https://www.deathwishcoffee.com"
  ],
  "maxProductsPerStore": 10,
  "onlyAvailable": false,
  "includeDescription": true,
  "includeInventory": false,
  "respectRobotsTxt": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

All product rows with every field, including the nested variants array.

## `overview` (type: `string`):

Compact table: image, title, vendor, price range, stock, sale flag and product link.

# 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.deathwishcoffee.com"
    ],
    "maxProductsPerStore": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("offgrid-labs/shopify-store-products").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.deathwishcoffee.com"],
    "maxProductsPerStore": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("offgrid-labs/shopify-store-products").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.deathwishcoffee.com"
  ],
  "maxProductsPerStore": 10
}' |
apify call offgrid-labs/shopify-store-products --silent --output-dataset

```

## MCP server setup

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

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/2Iod2nW4WNFgpEqUZ/builds/VVkGo99YAKdx8zAXz/openapi.json
