# Shopify Products Scraper - Any Store Catalogue (`fulox/shopify-products-scraper`) Actor

Scrape any Shopify store's public products.json: one row per product or per variant with SKU, price, compare-at price and discount. Weekly changed-only runs charge nothing for unchanged products; on-sale filter is free. No login. $0.40 per 1,000 products; blocked stores are free.

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

## Pricing

from $0.40 / 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 Products Scraper - Any Store Catalogue

Read the full product catalogue of any Shopify store from its public `products.json`, for dropshippers, price-comparison and competitor-research teams: SKU, price, compare-at price, discount, availability, image, tags and timestamps in one typed row per product or per variant.

### What you get

- Pay per product from $0.40 per 1,000, 55% below both Shopify catalogue scrapers with users.
- One row per variant (`rows: "variant"`) for sheets: every SKU, size and colour on its own line; one charge per product for each 10 variant rows (most products have fewer, so one charge).
- Changed-only mode (`onlyChangedSinceLastRun`): a weekly run returns only products updated since your last run, and unchanged products cost nothing.
- Sale detection: `discountPercent` on every row from the compare-at price, and `onlyOnSale` drops full-price products before the charge.

### Example output

```json
{
  "domain": "allbirds.com",
  "productId": 7340901859408,
  "handle": "womens-allbirds-flip-flop-dusty-pink",
  "title": "Women's Allbirds Flip Flop - Dusty Pink",
  "vendor": "Allbirds",
  "productType": "Shoes",
  "tags": ["loop::returnable => true", "shoprunner"],
  "variantId": 42146889039952,
  "variantTitle": "5",
  "sku": "A12513W050",
  "price": 50,
  "compareAtPrice": null,
  "discountPercent": null,
  "available": true,
  "variantCount": 7,
  "imageUrl": "https://cdn.shopify.com/s/files/1/1104/4168/files/A12513_26Q2_Allbirds-Flip-Flop-Dusty-Pink_PDP_LEFT.png",
  "createdAt": "2026-03-27T14:03:50-07:00",
  "updatedAt": "2026-10-30T16:57:17-07:00",
  "publishedAt": "2026-09-25T16:58:13-07:00",
  "url": "https://allbirds.com/products/womens-allbirds-flip-flop-dusty-pink"
}
```

On a product row, `price` and `compareAtPrice` come from the cheapest variant and `available` is true when any variant is in stock. On a variant row they belong to that variant, and `url` ends in `?variant=<id>`.

### How to use it

1. Paste one or more store domains (for example `allbirds.com` or `https://www.kith.com`) and choose how many products per store.
2. Click Start.
3. Download the dataset as JSON, CSV or Excel, or connect it to Google Sheets, Make, Zapier or n8n.

### Input

| Field | What it does | Default |
|---|---|---|
| `domains` | Store domains or URLs, up to 100 per run | `allbirds.com` |
| `maxProductsPerStore` | Products listed per store, 1 to 5,000 (a run reads at most 5,000 in total) | 500 (25 in the prefilled example) |
| `rows` | `product` for one row per product, `variant` for one row per variant | `product` |
| `onlyOnSale` | Keep only products with a variant whose compare-at price is above its price | off |
| `onlyChangedSinceLastRun` | Return only products updated after the newest one an earlier run delivered for that store | off |

### Pricing

$0.40 per 1,000 products ($0.0004 each). In product mode a product is one charge. In variant mode it is one charge per 10 variant rows or part of them, so a product with 7 variants is one charge and one with 25 variants is three. Stores that fail, are blocked, or return nothing cost nothing, and neither do products dropped by `onlyOnSale` or changed-only mode. Set a maximum cost per run to stay in control.

### Limitations

- Only stores that publish `products.json` work. A store that closes it, is password-protected or is not on Shopify returns a free `store_failed` row with the HTTP status.
- Some stores sit behind bot protection and refuse requests from data-center addresses. They return a free `store_blocked` row; this version uses no proxy.
- Public catalogue data only: no inventory counts, collections, reviews or storefront pages.
- Changed-only mode needs a named storage on your account. If your account cannot open one, the run stops with a free `not_processed` row instead of charging for products you have already seen. The reference point is kept per store and per set of settings (`onlyOnSale`, `maxProductsPerStore`), so tasks with different settings do not affect each other. It moves forward only for a store whose changed products were all delivered: after a run that stopped early (for example your cost cap), the stores it did not finish are listed again next time, and their products delivered in the stopped run are charged again. Rule: after a delivered run the reference point is the newest `updated_at` delivered, even if `maxProductsPerStore` cut the list, so the next run never charges those products again; changed products beyond the cut are not returned later, so set `maxProductsPerStore` above the number of products that change between runs.
- Redirects to a different website are not followed (the store fails free); only a redirect to the same host or its `www.` version is.
- Prices are read as numbers in the store's own currency; the currency is not in `products.json`.

### FAQ

**How fresh is the data?** Every run reads the live `products.json`.

**Can I run it every week?** Yes. Turn on `onlyChangedSinceLastRun` and schedule the Actor; each run returns only what changed.

**Why is my store missing?** Look for a `store_blocked` or `store_failed` row in the dataset; its `reason` says why.

**Can I get one row per size and colour?** Set `rows` to `variant`.

### Changelog

- 2026-10-31: first version.

# Actor input Schema

## `domains` (type: `array`):

Shopify stores to read, one per line, for example allbirds.com or https://www.kith.com. Up to 100 stores per run. Each store's public products.json is read.

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

Stop listing a store after this many products. A run reads at most 5,000 products in total.

## `rows` (type: `string`):

product: one row per product with its cheapest variant. variant: one row per variant (every SKU, size and colour). Product rows: one charge per product. Variant rows: one charge per product for each 10 variant rows or part of them (a product with 7 variants is one charge, 25 variants is three).

## `onlyOnSale` (type: `boolean`):

Keep only products with at least one variant whose compare-at price is above its price. Full-price products and products with no price are dropped before they are charged and cost nothing. Maximum products per store counts products read, before this filter.

## `onlyChangedSinceLastRun` (type: `boolean`):

Return only products updated after the newest product an earlier run delivered for the same store. Unchanged products cost nothing. The first run for a store returns everything.

## Actor input object example

```json
{
  "domains": [
    "allbirds.com"
  ],
  "maxProductsPerStore": 25,
  "rows": "product",
  "onlyOnSale": false,
  "onlyChangedSinceLastRun": false
}
```

# Actor output Schema

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

One row per product (or per variant), plus free rows that explain a blocked or failed store or an empty run.

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

Counts for the run: stores listed, blocked and failed, products delivered and charged, rows written, stop 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 = {
    "domains": [
        "allbirds.com"
    ],
    "maxProductsPerStore": 25
};

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

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

```

## MCP server setup

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