# Shopify Products Scraper (`tidyharvest/shopify-products-scraper`) Actor

Scrape products from any Shopify store, collection or product URL: prices, compare-at and sale flag, variants with SKU and availability, optional stock counts and barcodes, images, tags, vendor, currency. Filters and a flat per-variant mode. $0.75 per 1,000 products.

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

## Pricing

Pay per event

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 Products Scraper

Scrape every product from **any Shopify store, on any custom domain**: titles, prices, compare-at (sale) prices,
variants with SKU and availability, images, options, tags, vendor, product type, descriptions and dates. Add
**exact stock counts** and barcodes when the store exposes them. No login, no API key, no browser: the actor reads
the public storefront JSON every Shopify store serves, so it is fast and cheap.

**$0.75 per 1,000 products** (less on paid Apify plans). Variant mode is billed **per product, not per variant**,
and there is no per-run start fee.

### What you get

- **Whole catalogs, collections or single products.** Paste a domain (`allbirds.com`), a collection URL
  (`https://kith.com/collections/footwear`) or a product URL. Mix stores in one run.
- **Two output shapes.** One row per product with variants nested, or one flat row per variant (size/color) for
  price monitoring and spreadsheets.
- **Filters before you pay.** Keyword, vendor/brand, product type, tag, price range, in stock only, on sale only,
  updated after a date. Filtered-out products are never charged.
- **Stock counts (optional).** Per-variant `inventoryQuantity`, `inventoryPolicy`, barcode/GTIN and quantity rules
  from each product's detail JSON, plus `totalInventory` per product. Charged only when stock counts were found.
- **Store currency and name** from the store's own `meta.json`, on every row.
- **Clean failure reporting.** Non-Shopify sites, password-protected stores, stores that disable `products.json` and
  stores whose robots.txt disallows it are skipped and listed in the run status and `SUMMARY`, while the other stores
  in the run finish normally.
- **Polite by design.** Each store is throttled below Shopify's ~2 requests/second limit, 429s are retried with
  backoff, and the store's robots.txt is honoured.

### Input examples

Whole catalog of two stores, 500 products max:

```json
{ "startUrls": ["allbirds.com", "kith.com"], "maxItems": 500 }
```

Sale items under $80 from one collection, one row per variant (price monitoring):

```json
{
  "startUrls": ["https://www.allbirds.com/collections/mens"],
  "outputMode": "variants",
  "onSaleOnly": true,
  "maxPrice": 80
}
```

Nike products in stock at a store, with stock counts:

```json
{ "startUrls": ["kith.com"], "vendors": ["Nike"], "inStockOnly": true, "includeInventory": true, "maxItems": 200 }
```

Daily change tracking: only products updated since yesterday:

```json
{ "startUrls": ["allbirds.com"], "updatedAfter": "2026-10-14" }
```

| Field | What it does |
|---|---|
| `startUrls` | Domains, store URLs, collection URLs or product URLs, one per line |
| `collections` | Collection handles to scrape on every store (e.g. `sale`); empty = whole catalog |
| `maxItemsPerStore` / `maxItems` | Caps per store and for the whole run |
| `outputMode` | `products` (nested variants) or `variants` (flat) |
| `keywords`, `vendors`, `productTypes`, `tags` | Text and exact-match filters |
| `minPrice`, `maxPrice`, `inStockOnly`, `onSaleOnly` | Variant-level filters (product kept if any variant matches) |
| `updatedAfter` | Keep products updated on or after a date |
| `includeInventory` | Add stock counts, barcode, inventory policy (one extra request per product) |
| `includeBodyHtml` | Keep the raw description HTML (plain text is always included) |
| `proxyConfiguration` | Optional Apify Proxy for the rare store that blocks requests |

### Output sample (product mode, trimmed; stock values illustrative)

```json
{
  "storeDomain": "www.allbirds.com",
  "storeName": "Allbirds",
  "productId": 4870548979792,
  "handle": "mens-tree-runners",
  "url": "https://www.allbirds.com/products/mens-tree-runners",
  "title": "Men's Tree Runner - Jet Black (White Sole)",
  "vendor": "Allbirds",
  "productType": "Shoes",
  "tags": ["allbirds::gender => mens", "allbirds::material => tree"],
  "description": "The Allbirds Tree Runner is a breathable and lightweight sneaker ...",
  "descriptionHtml": "<p>The Allbirds Tree Runner is ...</p>",
  "createdAt": "2021-01-29T13:57:15-08:00",
  "updatedAt": "2026-10-15T16:56:06-07:00",
  "publishedAt": "2026-09-25T16:57:46-07:00",
  "currency": "USD",
  "minPrice": 100.0,
  "maxPrice": 100.0,
  "compareAtMinPrice": null,
  "onSale": false,
  "available": true,
  "variantCount": 7,
  "totalInventory": 412,
  "imageUrl": "https://cdn.shopify.com/s/files/1/1104/4168/products/....png",
  "images": [{ "src": "https://cdn.shopify.com/...", "alt": null, "width": 4000, "height": 4000, "variantIds": [] }],
  "options": [{ "name": "Size", "values": ["8", "9", "10", "11", "12", "13", "14"] }],
  "variants": [{
    "variantId": 33179624669264, "title": "8", "sku": "TR3MJBW080", "price": 100.0, "compareAtPrice": null,
    "onSale": false, "available": true, "option1": "8", "grams": 680, "barcode": "194966057063",
    "inventoryQuantity": 61, "inventoryPolicy": "deny",
    "url": "https://www.allbirds.com/products/mens-tree-runners?variant=33179624669264"
  }]
}
```

In variant mode each row carries the product fields plus the variant's own `variantId`, `variantTitle`, `sku`,
`price`, `compareAtPrice`, `onSale`, `available`, `option1..3`, `optionNames`, stock fields and a variant `url`.
`totalInventory`, `inventoryQuantity` and `barcode` are filled only when the store exposes them (with
`includeInventory` on, or when the store's product JSON already includes them).

A `SUMMARY` record in the run's key-value store lists, per store, the store name, currency, published product count,
products scanned and returned, requests made, and every skipped store with the reason (`not_shopify`,
`password_protected`, `products_json_disabled`, `robots_disallowed`, `blocked`, `unreachable`).

### Pricing

| Event | Price (FREE plan) | Paid plans |
|---|---|---|
| Product scraped | $0.00075 ($0.75 / 1,000) | down to $0.60 / 1,000 |
| Inventory enriched (optional) | $0.0005 ($0.50 / 1,000) | down to $0.40 / 1,000 |

No start fee. In variant mode you pay per product, so a shoe with 13 sizes costs one product.

### Limits and notes

- Works only on Shopify stores that leave the standard public `products.json` on (the vast majority). Headless
  storefronts that turn it off are reported, not charged.
- Shopify's storefront lists only published products; draft and hidden products are not visible to anyone.
- Speed is ~250 products per request at about 1.6 requests/second per store, roughly 20,000 products a minute
  per store; several stores run in parallel. Inventory enrichment adds one request per product (about 100 products
  a minute per store).
- Prices are in the store's default currency.
- Only public catalog data is read. No customer, order or checkout data is touched.

# Actor input Schema

## `startUrls` (type: `array`):

One per line. Any Shopify store on any custom domain: a domain (allbirds.com), a store URL, a collection URL (https://kith.com/collections/footwear) or a product URL (https://www.allbirds.com/products/mens-tree-runners). Non-Shopify sites are skipped and reported, not crashed on.

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

Only scrape these collections of every store above, e.g. 'sale' or 'mens'. Empty = the whole catalog.

## `maxItemsPerStore` (type: `integer`):

Stop after this many products from each store. 0 or empty = the whole catalog.

## `maxItems` (type: `integer`):

Hard cap on products for the whole run (also caps your cost). 0 or empty = no limit.

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

Variant mode emits one flat row per size/color with its own price, SKU and stock. Billing stays per product either way.

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

Keep a product only if its title, description, vendor, type, tags, variant titles or SKUs contain one of these.

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

Keep only these vendors (exact name, case-insensitive), e.g. Nike.

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

Keep only these product types (exact, case-insensitive), e.g. Shoes.

## `tags` (type: `array`):

Keep a product only if it has one of these tags (exact, case-insensitive).

## `minPrice` (type: `integer`):

In the store's currency. A product is kept when at least one variant is in range.

## `maxPrice` (type: `integer`):

In the store's currency.

## `inStockOnly` (type: `boolean`):

Keep only products (or variants) that are available to buy.

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

Keep only products (or variants) whose compare-at price is above the current price.

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

Keep only products updated on or after this date (YYYY-MM-DD). Handy for daily change tracking.

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

Fetch each product's detail JSON for exact per-variant stock quantity, barcode/GTIN, inventory policy and quantity rules when the store exposes them. One extra request per product; charged only for products where stock counts were found.

## `includeBodyHtml` (type: `boolean`):

Plain-text description is always included; turn this off to drop the raw HTML and keep rows small.

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

Not needed for most stores. Turn on Apify Proxy only if a store blocks requests (HTTP 403/430).

## Actor input object example

```json
{
  "startUrls": [
    "allbirds.com"
  ],
  "maxItems": 100,
  "outputMode": "products",
  "inStockOnly": false,
  "onSaleOnly": false,
  "includeInventory": false,
  "includeBodyHtml": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

One item per product (variants nested), or one item per variant in variant mode.

# 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 = {
    "startUrls": [
        "allbirds.com"
    ],
    "maxItems": 100,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

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

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

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,tidyharvest/shopify-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/9MxvVxDdCVOv1lXp4/builds/15qiUB0PRXRr0FU2u/openapi.json
