# Shopify Store Scraper & API - Products, Prices, Variants (`rel8ble/shopify-store-products-scraper`) Actor

Shopify store scraper and products API: give it Shopify store, collection or product URLs. One row per product or variant: title, price, compare-at price, discount, stock flag, variants with SKU and options, vendor, type, tags, images, dates. No login; failed stores free. $0.50 per 1,000 results.

- **URL**: https://apify.com/rel8ble/shopify-store-products-scraper.md
- **Developed by:** [Giovanni Rich](https://apify.com/rel8ble) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.50 / 1,000 results

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 Store Scraper & API - Products, Prices, Variants

**Shopify store scraper** for stores you do not own: give it store domains, collection URLs or product URLs and get every product as clean JSON, CSV or Excel: title, vendor, product type, tags, price, compare-at price, discount %, in-stock flag, every variant with its SKU, price, options and stock flag, all images, description and dates. Use it as a **Shopify products scraper**, a **Shopify price monitoring** tool, or an unofficial **Shopify products API** for **competitor price tracking** and catalog exports, on any store built on Shopify (own domain or `*.myshopify.com`), in bulk.

It reads the public product feed every Shopify storefront serves (`/products.json`) over plain HTTP, with **no headless browser, no login and no API key**, so a 10,000-product store takes about a minute and a half.

### What you get

**Product mode** (`outputMode: "product"`, default), one dataset item per product:

- **Store**: `storeDomain`, `storeName`, `storeCountry`, `currency` (the store's own currency, read once from its `/meta.json`).
- **Identity**: `productId`, `title`, `handle`, `url`, `vendor`, `productType`, `tags` (array).
- **Price**: `price` (lowest variant price), `priceMin`, `priceMax`, `compareAtPrice` (strike-through price), `discountPercent`, `onSale`. All numbers.
- **Stock**: `available` (true when any variant can be bought), `variantsCount`.
- **Variants**: `variants[]` with `variantId`, `title`, `sku`, `price`, `compareAtPrice`, `discountPercent`, `available`, `option1` / `option2` / `option3`, `optionValues` (`{"Size": "8"}`), `grams`, `requiresShipping`, `taxable`, `barcode` (when the store exposes it), `imageUrl`, `url` (with `?variant=`), `createdAt`, `updatedAt`. Plus `options[]` (name and all values).
- **Content**: `imageUrl`, `images[]`, `description` (plain text, HTML stripped), optional `descriptionHtml`.
- **Dates**: `createdAt`, `publishedAt`, `updatedAt`, `scrapedAt` (ISO 8601, UTC).

**Variant mode** (`outputMode: "variant"`), one flat item per variant: `variantId`, `title` (product), `variantTitle`, `sku`, `price`, `compareAtPrice`, `discountPercent`, `available`, `option1-3`, `optionValues`, `grams`, `barcode`, `imageUrl`, `url`, `productUrl`, plus the product's `productId`, `handle`, `vendor`, `productType`, `tags` and the store fields. No nested arrays, so it opens cleanly in Excel or Google Sheets.

A `RUN_SUMMARY` record in the key-value store lists every store with its name, currency, published product count, products saved, pages read and stop reason, and every **failed store with the reason** (not a Shopify store, password-protected, feed disabled, access denied, domain does not resolve).

### Sample output

Real rows from a run on `www.allbirds.com`, product mode (from `sample-output.json`):

| storeName | title | productType | price | compareAtPrice | currency | available | variantsCount |
|---|---|---|---|---|---|---|---|
| Allbirds | Women's Allbirds Flip Flop - Dusty Pink | Shoes | 25 | 50 | USD | true | 7 |
| Allbirds | Men's Canvas Runner NZ - Deep Navy Stripes (Blizzard Sole) | Shoes | 100 | null | USD | true | 13 |
| Allbirds | Women's Canvas Runner NZ - Deep Navy Stripes (Blizzard Sole) | Shoes | 100 | null | USD | true | 13 |

### Use cases

- **Competitor price monitoring**: schedule variant mode daily and track `price`, `compareAtPrice` and `available` per SKU.
- **Catalog export / migration**: pull a whole store with `descriptionHtml`, images, options and variants.
- **Product and dropshipping research**: see what a store sells, at what price, what is new (`createdAt`) and what is discounted.
- **Stock tracking**: `onlyAvailable` plus a schedule shows what sold out and what came back.
- **New arrivals feed**: `updatedAfter: "1 day"` on a schedule returns only new and changed products.
- **AI agents**: one call returns a store's structured catalog an agent can search, compare and summarise.

### Pricing

$0.50 per 1,000 results (pay per result; failed requests and failed stores are never charged). Half the $1.00 per 1,000 charged by the top Shopify product scrapers on the Store.

### FAQ

**Is it legal to scrape Shopify stores?**
It collects only public product data the store itself publishes for every visitor, no personal data and nothing behind a login. You're responsible for using it in line with each store's terms and applicable law. This is not legal advice.

**Does it need a login, API key or store admin access?**
No. Plain HTTPS requests to public URLs only. If a site is not on Shopify, or its feed is off, it is listed under `failed` in `RUN_SUMMARY` with the reason, and you pay nothing for it.

**How fast is it and how many results can I get?**
Every published product in the store (`maxProductsPerStore: 0`), 250 per request; a 10,000-product store takes about 90 seconds. Paste hundreds of stores for bulk runs. It does not return inventory quantities, sales or reviews (Shopify does not publish them), only an in-stock flag per variant.

**Can I scrape one collection or one product, one row per variant?**
Yes. Paste `/collections/<name>` or `/products/<name>` URLs and mix them with whole stores. Set `outputMode: "variant"` for one flat row per size / colour (each variant row is one billed result).

**Can I schedule it or use it via API, and what about proxies?**
Yes: Apify schedules, the REST API, Make, Zapier, n8n and MCP. The default datacenter proxy works for most stores; rate limits (HTTP 429 / 430) are retried on a fresh IP, and a store that answers 403 is retried once over a direct connection. If it still fails with "access denied", switch to the RESIDENTIAL group.

### How to use

1. Paste one or more **Shopify stores, collections or products**, one per line: `allbirds.com` (whole store), `https://www.allbirds.com/collections/mens` (one collection) or `https://www.allbirds.com/products/mens-tree-runners` (one product).
2. Pick the **Output**: one row per product (variants nested) or one row per variant (flat, best for price and stock monitoring). Set **Max products per store** (default 250; 0 = the whole store) and any filters: in stock only, on sale only, title keywords, vendors, product types, updated after a date.
3. Click **Start**. Download the results as JSON, CSV, Excel or HTML from the **Output** tab, or pull them through the Apify API, Make, Zapier, n8n or an MCP client.

### Input example

```json
{
    "storeUrls": ["allbirds.com", "www.deathwishcoffee.com", "https://www.taylorstitch.com/collections/mens-shirts"],
    "outputMode": "product",
    "maxProductsPerStore": 0
}
```

Price monitoring, one row per variant, discounted and in-stock items only:

```json
{
    "storeUrls": ["gymshark.com", "ugmonk.com"],
    "outputMode": "variant",
    "maxProductsPerStore": 500,
    "onlyAvailable": true,
    "onSaleOnly": true
}
```

### Output example

Real item from a cloud run (`www.allbirds.com`, product mode). The product has 7 variants and 5 images; 2 of each are shown here:

```json
{
    "storeDomain": "www.allbirds.com",
    "storeName": "Allbirds",
    "storeCountry": "US",
    "productId": "7340901859408",
    "title": "Women's Allbirds Flip Flop - Dusty Pink",
    "handle": "womens-allbirds-flip-flop-dusty-pink",
    "url": "https://www.allbirds.com/products/womens-allbirds-flip-flop-dusty-pink",
    "vendor": "Allbirds",
    "productType": "Shoes",
    "tags": ["allbirds::carbon-score => undefined", "allbirds::cfId => color-a4d5d0f6620dc304306f503bf67bc5f9", "allbirds::complete => true", "allbirds::edition => limited", "allbirds::gender => womens", "allbirds::hue => pink", "allbirds::master => womens-allbirds-flip-flop", "allbirds::material => sugar", "allbirds::price-tier => tier-3", "allbirds::silhouette => zeffer", "loop::returnable => true", "OOS DNS", "shoprunner", "WC WAVE 1 TRUCK #1-#16 7.30.2026", "YBlocklist", "YGroup_ygroup_womens-allbirds-flip-flop"],
    "price": 25,
    "priceMin": 25,
    "priceMax": 25,
    "compareAtPrice": 50,
    "currency": "USD",
    "discountPercent": 50,
    "onSale": true,
    "available": true,
    "variantsCount": 7,
    "variants": [
        {
            "variantId": "42146889039952",
            "title": "5",
            "sku": "A12513W050",
            "price": 25,
            "compareAtPrice": 50,
            "discountPercent": 50,
            "available": false,
            "option1": "5",
            "option2": null,
            "option3": null,
            "optionValues": { "Size": "5" },
            "grams": 455,
            "requiresShipping": true,
            "taxable": true,
            "barcode": null,
            "imageUrl": "https://cdn.shopify.com/s/files/1/1104/4168/files/A12513_26Q2_Allbirds-Flip-Flop-Dusty-Pink_PDP_LEFT.png?v=1774646345",
            "url": "https://www.allbirds.com/products/womens-allbirds-flip-flop-dusty-pink?variant=42146889039952",
            "createdAt": "2026-03-27T21:03:50.000Z",
            "updatedAt": "2026-10-10T17:37:35.000Z"
        },
        {
            "variantId": "42146889072720",
            "title": "6",
            "sku": "A12513W060",
            "price": 25,
            "compareAtPrice": 50,
            "discountPercent": 50,
            "available": false,
            "option1": "6",
            "option2": null,
            "option3": null,
            "optionValues": { "Size": "6" },
            "grams": 492,
            "requiresShipping": true,
            "taxable": true,
            "barcode": null,
            "imageUrl": "https://cdn.shopify.com/s/files/1/1104/4168/files/A12513_26Q2_Allbirds-Flip-Flop-Dusty-Pink_PDP_LEFT.png?v=1774646345",
            "url": "https://www.allbirds.com/products/womens-allbirds-flip-flop-dusty-pink?variant=42146889072720",
            "createdAt": "2026-03-27T21:03:50.000Z",
            "updatedAt": "2026-10-10T17:37:35.000Z"
        }
    ],
    "options": [
        {
            "name": "Size",
            "position": 1,
            "values": ["5", "6", "7", "8", "9", "10", "11"]
        }
    ],
    "imageUrl": "https://cdn.shopify.com/s/files/1/1104/4168/files/A12513_26Q2_Allbirds-Flip-Flop-Dusty-Pink_PDP_LEFT.png?v=1774646345",
    "images": [
        "https://cdn.shopify.com/s/files/1/1104/4168/files/A12513_26Q2_Allbirds-Flip-Flop-Dusty-Pink_PDP_LEFT.png?v=1774646345",
        "https://cdn.shopify.com/s/files/1/1104/4168/files/A12513_26Q2_Allbirds-Flip-Flop-Dusty-Pink_PDP_BACK.png?v=1774646345"
    ],
    "description": "Sun on your feet. Comfort underneath. Light, easy, and made for warm weather, these flip flops bring everyday comfort to sunny days, beach walks, and everything in between. Just slip them on and go.",
    "createdAt": "2026-03-27T21:03:50.000Z",
    "publishedAt": "2026-09-25T23:58:13.000Z",
    "updatedAt": "2026-10-10T17:37:35.000Z",
    "scrapedAt": "2026-10-10T17:37:35.646Z"
}
```

### Integrations

- **API**: start runs and read datasets over the Apify REST API or the JavaScript / Python clients.
- **MCP / AI agents**: call it from Claude, ChatGPT or any MCP client through the Apify MCP server; inputs and outputs are documented field by field.
- **No-code**: Make, Zapier, n8n, Google Sheets, Airtable, Slack, webhooks.
- **Scheduling**: run hourly, daily or weekly in Apify Schedules to track prices and stock.

### Limits (read before large runs)

- It reads what the store publishes in its feed: published products only, no inventory quantities, no sales numbers, no reviews, no metafields.
- Prices are in the store's default currency (`currency`). Stores that sell in several markets show converted prices on the site; the feed has the base price.
- A few stores turn the feed off (headless storefronts) or sit behind strict bot protection. They come back as failed with the reason and are not charged. For "access denied (HTTP 403)" switch the proxy to the RESIDENTIAL group.
- `barcode` and `weight` are only in the single-product feed, so they are filled for product URLs and empty for whole-store runs. `grams` is always filled.
- Filters are applied after reading, so a filtered run still reads the whole store or collection (fast, and you only pay for what passes).

### How it works (for developers)

Every Shopify storefront serves `/products.json?limit=250&page=N` (and `/collections/<handle>/products.json`), 250 products per page, until a page comes back short or empty. The actor reads `/meta.json` once per store for the store name, currency, country and published product count (falling back to the homepage when a store does not serve it), then pages through the feed and maps each product: prices and compare-at prices become numbers, the cheapest variant sets `price`, HTML is stripped from the description, dates are converted to UTC. Product URLs use `/products/<handle>.js` (stock flags, barcodes) merged with `/products/<handle>.json` (update date, weights). A 404, a non-JSON answer, a redirect to `/password` or a 403 marks the store as failed with a plain reason.

Run it locally:

```bash
npm install
npm test                 # parser tests on saved real fixtures (store feed, collection, product .js/.json, meta, 404, 403)
node src/main.js         # input in storage/key_value_stores/default/INPUT.json
```

# Actor input Schema

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

Required. One per line, any mix of: a store domain or URL ("allbirds.com", "https://www.deathwishcoffee.com") = every product in that store; a collection URL ("https://www.allbirds.com/collections/mens") = only that collection; a product URL ("https://www.allbirds.com/products/mens-tree-runners") = that one product. Works for any store built on Shopify, on its own domain or on \*.myshopify.com. Paste hundreds of stores for bulk runs. Sites that are not Shopify, password-protected stores and stores that block their product feed are listed as failed in the run summary and are never charged.

## `outputMode` (type: `string`):

Optional. "product" (default) = one result per product with all its variants nested in a variants array; best for catalog exports and product research. "variant" = one flat result per variant (size / colour) with its own price, SKU and in-stock flag; best for price and stock monitoring in spreadsheets. In variant mode every variant row is one billed result.

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

Optional. Products to save per store or collection URL, integer. Default 250 (one feed page, a few seconds). 0 or empty = every product in the store. Counted after the filters below. Typical cost: 250 products = $0.125, a 5,000-product store = $2.50.

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

Optional boolean. true = keep only products with at least one variant in stock (in variant mode: only in-stock variants). Default false.

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

Optional boolean. true = keep only products where a variant has a compare-at (strike-through) price above its price (in variant mode: only discounted variants). Default false.

## `titleKeywords` (type: `array`):

Optional. Keep only products whose title contains at least one of these words or phrases, case-insensitive: "hoodie", "gift card". Empty = all products.

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

Optional. Keep only products whose vendor equals one of these, case-insensitive: "Nike", "Allbirds". Empty = all vendors. Useful for multi-brand stores.

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

Optional. Keep only products whose Shopify product type equals one of these, case-insensitive: "Shoes", "Socks". Empty = all types. Product types are set by each store, so check a first run for the exact names.

## `updatedAfter` (type: `string`):

Optional. Keep only products the store changed on or after this date: an absolute date "2026-09-01" or a relative one "7 days", "2 weeks", "1 month". Use with a schedule to collect only new and changed products. Empty = no date filter.

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

Optional boolean. false (default) = description as plain text only. true = also add descriptionHtml with the original HTML (larger output), for re-importing products into another store.

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

Optional, advanced. Parallel requests across stores, integer 1-50. Default 10. Pages of one store are always read one after another, so a store is never hit in parallel.

## `maxRequestRetries` (type: `integer`):

Optional, advanced. Retries per page on a new IP when Shopify rate-limits (HTTP 429 / 430) or times out, integer 0-20. Default 6. Waits grow between attempts.

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

Optional. Apify Proxy (datacenter) is the default and works for most Shopify stores. A store that answers HTTP 403 to the proxy is retried once over a direct connection automatically. If it still comes back as failed with "access denied (HTTP 403)", switch to the RESIDENTIAL group for that store.

## Actor input object example

```json
{
  "storeUrls": [
    "www.allbirds.com"
  ],
  "outputMode": "product",
  "maxProductsPerStore": 50,
  "onlyAvailable": false,
  "onSaleOnly": false,
  "includeDescriptionHtml": false,
  "maxConcurrency": 10,
  "maxRequestRetries": 6,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

All products (or variants) found: store, product ID, title, price, compare-at price, discount, in-stock flag, variants with SKU and options, vendor, type, tags, images, description, dates and URL.

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

Per-store counts, store name, currency, published product count, stop reasons and the list of failed stores with the reason.

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

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

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

```

## MCP server setup

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