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

Scrape every public product from any Shopify store, custom domains included: title, vendor, type, tags, prices, compare-at/sale flags, availability, variants with SKUs, options, images and description. Store, collection or product URLs. Pay only for products returned.

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

## Pricing

from $0.90 / 1,000 product scrapeds

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 Products Scraper (Prices, Variants, SKUs, Stock)

Enter Shopify store domains (custom domains like `allbirds.com` work), collection URLs or product URLs. You get **one clean row per product** with title, vendor, type, tags, min/max price, compare-at price, sale flag and discount %, availability, every variant (SKU, barcode, price, options, weight, stock flag), images and description. The scraper reads each store's public JSON endpoints (`/products.json`, `/collections/<handle>/products.json`, `/products/<handle>.js|.json`, `/meta.json`), so it needs no browser and no proxies, and runs are fast. In our test it scraped 2,214 products from 12 stores in under 8 seconds.

**You pay only for real products.** Non-Shopify sites, password-protected stores, 404s, blocked stores, invalid inputs, robots.txt-disallowed URLs and duplicates are written free, each with a reason code.

### Use cases

- **Competitor price monitoring:** track prices, compare-at prices, `onSale` and `maxDiscountPercent` across rival stores.
- **Catalog research and dropshipping:** pull full catalogs (vendors, product types, tags, variants) to find gaps and trends.
- **Stock tracking:** `available` and `availableVariantCount` per product, and `available` per variant.
- **Lead lists:** use the optional store summary row (store name, currency, country, product count, vendors, price range) to qualify Shopify merchants.

### Input

| Field | Default | Notes |
|---|---|---|
| `stores` | – | Store domains or URLs, e.g. `gymshark.com`, `https://colourpop.com`, `myshop.myshopify.com`. If `www.` is blocked or headless, the bare domain is tried (and the other way round). |
| `collectionUrls` | – | e.g. `https://colourpop.com/collections/lips`. Only that collection is scraped. |
| `productUrls` | – | e.g. `https://www.allbirds.com/products/mens-tree-runners`. These rows also include `updatedAt`, barcodes and `inventoryQuantity` when the store exposes them. |
| `maxProductsPerStore` | 1000 | Cap per store or collection. 0 = no cap (Shopify itself allows at most 25,000 per listing). |
| `keyword` | – | Comma-separated. Keeps products whose title, vendor, type, handle or tags contain any term (case-insensitive substring). It doesn't apply to product URLs. Filtered-out products are not charged. |
| `includeVariants` | true | Turn off for smaller rows. Prices and availability are still summarized. |
| `includeStoreSummary` | false | One free summary row per store. |
| `includeFailedRows` | true | Free rows for inputs that returned no products. |
| `maxChargedProducts` | 0 (no limit) | Stop after this many charged products. |
| `maxConcurrency` / `requestDelayMs` | 5 / 500 | Stores run in parallel. Requests to the same store are paced, and the delay doubles automatically if the store answers 429. |

```json
{ "stores": ["allbirds.com", "gymshark.com"], "collectionUrls": ["https://colourpop.com/collections/lips"], "maxProductsPerStore": 500, "keyword": "runner" }
```

### Output (one row per product, abridged)

```json
{
  "rowType": "product", "status": "OK", "store": "kyliecosmetics.com", "storeName": "Kylie Cosmetics", "currency": "USD",
  "productId": 9168000876786, "handle": "kylies-sunset-glam-look-bundle", "title": "Kylie's Sunset Glam Look Bundle",
  "url": "https://kyliecosmetics.com/products/kylies-sunset-glam-look-bundle",
  "vendor": "kylie-cosmetics", "productType": "Bundle", "tags": ["..."],
  "priceMin": 208.8, "priceMax": 208.8, "compareAtPrice": 232, "onSale": true, "maxDiscountPercent": 10,
  "available": true, "variantCount": 1, "availableVariantCount": 1, "skus": ["..."],
  "options": [{ "name": "Title", "values": ["Default Title"] }],
  "variants": [{ "id": 49799067631858, "title": "Default Title", "sku": "...", "price": 208.8, "compareAtPrice": 232, "onSale": true, "available": true, "grams": 0, "options": ["Default Title"], "imageUrl": null }],
  "imageUrl": "https://cdn.shopify.com/...", "images": ["..."], "imageCount": 10,
  "bodyText": "...", "bodyHtml": "...", "createdAt": "...", "publishedAt": "...", "updatedAt": "...",
  "collection": null, "source": "store", "charged": true, "errorCode": null, "error": null
}
```

Every row has the same fields. Free rows have `charged: false`, a `status`/`errorCode` and an `error` message: `NOT_SHOPIFY`, `PASSWORD_PROTECTED`, `NOT_FOUND`, `SHOP_UNAVAILABLE`, `PRODUCTS_JSON_DISABLED`, `BLOCKED`, `RATE_LIMITED`, `ROBOTS_DISALLOWED`, `NO_PRODUCTS`, `NO_MATCHES`, `DUPLICATE`, `INVALID_INPUT`, `PAGINATION_LIMIT`, or network codes such as `TIMEOUT` and `DNS_ERROR`. A run summary is saved to the key-value store as `RUN_SUMMARY`.

### Pricing (pay per event)

One event, **`product-scraped`**. It is charged once per unique product written with real data.

| Tier | Per product | Per 1,000 products |
|---|---|---|
| FREE | $0.0015 | $1.50 |
| BRONZE | $0.0013 | $1.30 |
| SILVER | $0.0011 | $1.10 |
| GOLD | $0.0009 | $0.90 |

**Free:** everything in the free-row list above, plus products removed by your keyword filter. Apify's standard actor-start event ($0.00005) applies. Set a max total charge or `maxChargedProducts` to cap spend. The run stops cleanly at your limit and never charges past it.

### Limitations

- **Public catalog only.** Unpublished, hidden or B2B-only products aren't visible. Store listings don't include exact stock counts. `inventoryQuantity` appears only on product-URL rows, and only if the store exposes it (most don't).
- **Shopify caps listings at 25,000 products** per endpoint. For bigger stores, add collection URLs (each has its own 25,000 cap). Products seen in several collections are charged once.
- Some Shopify stores disable `/products.json` (`PRODUCTS_JSON_DISABLED`). Others sit behind bot walls such as Cloudflare, Vercel or Akamai (`BLOCKED`). Headless stores built on Hydrogen or custom front ends often fail on the custom domain. Try their `*.myshopify.com` address. These rows are free.
- `currency` is the store's default currency (from `/meta.json`). Prices are those served to an anonymous visitor from the run's region, without market- or customer-specific pricing.
- robots.txt is respected. Stores that disallow the endpoints are written free as `ROBOTS_DISALLOWED`.
- Recommended memory: 512 MB (default). 256 MB works for small runs.

# Actor input Schema

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

Store domains or URLs, one per line. Custom domains (allbirds.com) and \*.myshopify.com both work. Every public product in the store is scraped (up to the per-store limit). A URL containing /collections/<handle> or /products/<handle> is treated as a collection or product URL automatically.

## `collectionUrls` (type: `array`):

Scrape only these collections, e.g. https://colourpop.com/collections/lips. Each collection is its own listing with its own per-store limit.

## `productUrls` (type: `array`):

Single products, e.g. https://www.allbirds.com/products/mens-tree-runners. Product URLs also return variant inventory counts and barcodes when the store exposes them. The keyword filter isn't applied to these.

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

Stop each store or collection listing after this many products. 0 = no limit (Shopify itself caps any single listing at 25,000 products).

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

Keep only products whose title, vendor, product type, handle or tags contain this text (case-insensitive). Separate several keywords with commas to match any of them. Filtered-out products are not charged.

## `includeVariants` (type: `boolean`):

Each product row gets a variants array (id, title, SKU, price, compare-at price, availability, options, grams, image). Price range, sale and availability fields are filled either way.

## `includeStoreSummary` (type: `boolean`):

One extra free row per store (rowType = store): product count, vendors, product types, price range, currency, country and myshopify domain when the store publishes them.

## `maxChargedProducts` (type: `integer`):

Stop after this many products in total. 0 = no limit (the run's Max total charge still applies).

## `includeFailedRows` (type: `boolean`):

Adds a free row with a reason code (NOT_SHOPIFY, PASSWORD_PROTECTED, NOT_FOUND, BLOCKED, ROBOTS_DISALLOWED, INVALID_INPUT...) so every input is accounted for.

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

Pages of one store are always fetched one at a time; this sets how many stores run at once.

## `requestDelayMs` (type: `integer`):

Polite per-store pacing. On HTTP 429 the Actor waits (Retry-After or exponential backoff) and slows that store down automatically.

## `requestTimeoutSecs` (type: `integer`):

Maximum seconds to wait for each HTTP request before it is retried or marked failed.

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

Optional, usually not needed. Apify datacenter proxy or your own proxy URLs. Residential proxy isn't supported.

## Actor input object example

```json
{
  "stores": [
    "allbirds.com",
    "https://www.deathwishcoffee.com"
  ],
  "maxProductsPerStore": 1000,
  "includeVariants": true,
  "includeStoreSummary": false,
  "maxChargedProducts": 0,
  "includeFailedRows": true,
  "maxConcurrency": 5,
  "requestDelayMs": 500,
  "requestTimeoutSecs": 30,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

Title, vendor, type, price range, sale and stock flags, image and URL per product.

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

Every row incl. variants, options, images, tags and description.

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

Totals: products scraped (charged), duplicates, filtered and failed inputs by reason (not charged).

# 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",
        "https://www.deathwishcoffee.com"
    ],
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

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

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

```

## MCP server setup

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