# Shopify Product Scraper — Catalog, Variants, Prices & Discounts (`tidyfeed/shopify-product-scraper`) Actor

Export the full product catalog of any Shopify store as a clean, spreadsheet-ready table. Variants, prices, compare-at prices, calculated discount %, stock status, SKUs and images.

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

## Pricing

from $10.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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Shopify Product Scraper — Catalog, Variants, Prices & Discounts

**Paste a store address. Get its entire catalog as a spreadsheet-ready table.**

Every product, every variant, with prices as real numbers, a calculated discount %, stock status, SKUs and images. Add several stores to compare competitors side by side in one run.

No API key. No login. No Shopify partner account. No proxies.

***

### The difference, on a real product

Shopify publishes its product feed in a shape that is awkward to use. Here is what a live Allbirds variant looks like in the raw feed:

```json
{
  "options": [{ "name": "Size", "position": 1 }],     ← name lives up here
  "variants": [{
    "option1": "10.5",                                 ← value lives down here
    "price": "91.00",                                  ← a string
    "compare_at_price": "130.00"                       ← another string
  }]
}
```

Three problems: the option **name** and its **value** are in different objects, prices are text rather than numbers, and there is no discount figure anywhere.

Here is the same variant as this actor returns it:

| option1Name | option1Value | price | compareAtPrice | discountPercent | onSale | available |
|---|---|---|---|---|---|---|
| Size | 10.5 | 91 | 130 | **30** | true | true |

Paste that into a spreadsheet and it sorts, filters and sums immediately. No formulas, no lookup table, no cleanup pass.

***

### What you get

One row per variant, so every size and colour carries its own price and stock flag.

| Column | Example |
|---|---|
| `storeDomain` | `allbirds.com` |
| `productTitle` | Women's Strider - Rugged Beige |
| `option1Name` / `option1Value` | Size / 10.5 |
| `option2Name` / `option2Value` | Colour / Navy — blank when a product has one option |
| `sku` | A11848W105 |
| `price` | 91 |
| `compareAtPrice` | 130 |
| **`discountPercent`** / `onSale` | **30** / true |
| `available` | true |
| `vendor`, `productType` | Allbirds, Shoes |
| `tags` | the store's own tags, comma-joined |
| `productUrl`, `imageUrl`, `imageCount` | direct links |
| `publishedAt`, `createdAt`, `updatedAt` | ISO timestamps |

Prefer one row per product? Switch **Row format** and you get a price range, variant count and combined SKU list instead.

***

### What it costs

**One charge per product returned. Every variant of that product is included at no extra cost.**

That is the same billing unit the rest of this category uses, so the price comparison is direct — the difference is what arrives for the money.

| Your run | You pay |
|---|---|
| A 200-product store | $2.01 |
| A 1,000-product catalog | $10.01 |
| Three competitors at 500 products each | $15.01 |

**Products your filters exclude are not billed.** Set *Minimum discount %* to 50, point it at a 250-product catalog, and if 87 products qualify you are charged for 87 — not 250.

***

### It follows headless stores to their real shop

A growing number of brands run a custom front end — Next.js, Hydrogen, a bespoke storefront — on their own domain. Ask `brand.com/products.json` and you get a web page instead of a feed, which is where most scrapers stop and report "not a Shopify store".

These stores still name their Shopify shop in their own markup. This actor reads that address and pulls the catalog from there.

```
kotn.com    → custom front end → reads kotn-ss15.myshopify.com → 250 products
mejuri.com  → custom front end → reads mejuri-us.myshopify.com → 250 products
```

Both of those return nothing on the brand domain. Both come back with a full catalog here.

***

### Typical uses

- **Competitor price monitoring** — schedule a daily run across several stores and watch prices move.
- **Sale tracking** — set *Minimum discount %* to 20 and see only what a competitor has actually marked down.
- **Catalog migration or audit** — pull an entire catalog into a spreadsheet before a replatform.
- **Assortment research** — compare how two brands price the same product type.
- **Dropshipping and sourcing** — export a supplier's catalog with SKUs and stock.
- **Feeding an AI agent** — callable over the API and through MCP, so Claude, Cursor and other assistants can pull live catalog data mid-task.

***

### Input

| Field | What it does |
|---|---|
| **Shopify store URLs** | One or more addresses. A bare domain, a homepage, a product page or a `.myshopify.com` address all work. |
| **Max products per store** | Stops after N products per store. Default 1000. |
| **Row format** | One row per variant (default), or one row per product with a price range. |
| **Include product description** | Adds the description as plain text with the HTML stripped out. Off by default. |
| **Skip sold-out items** | Keeps only what is currently in stock. |
| **Minimum discount %** | Keeps only items discounted by at least this much. |

```json
{
  "storeUrls": ["https://gymshark.com", "allbirds.com"],
  "maxProductsPerStore": 1000,
  "outputMode": "variant",
  "onlyAvailable": true,
  "minDiscountPercent": 20
}
```

That run returns every in-stock item discounted 20% or more, across both stores.

***

### How it works

Shopify serves a public product feed at `/products.json` on every storefront. This actor reads that feed, walks it 250 products at a time, and reshapes the result into a flat table.

Because it uses the store's own public feed:

- **it is fast** — a 1,000-product catalog finishes in well under a minute,
- **it needs no proxies** — nothing to configure, nothing extra to pay for,
- **it does not break when a theme changes** — there is no HTML parsing involved.

Runs are resilient across stores: if one address fails, the others carry on and the run still succeeds, with a per-store line saying exactly what happened and why.

***

### Honest limits

Worth knowing before you run it:

- **Password-protected and pre-launch stores return nothing.** There is no public feed to read.
- **A handful of stores disable the feed.** Rare, but it happens; the run reports which stores came back empty and why.
- **A few brand domains sit behind bot protection** that challenges every request. The run reports those instead of failing, and the store's `.myshopify.com` address usually works in their place.
- **Currency is not published in the feed.** Prices are in the store's own default currency.
- **Inventory counts are not published either** — Shopify exposes an in-stock/out-of-stock flag, not a quantity.
- **Non-Shopify sites are detected and skipped** with a clear message rather than a failed run.

***

### Output example

```json
{
  "storeDomain": "allbirds.com",
  "productTitle": "Women's Strider - Rugged Beige (Stony Cream Sole)",
  "handle": "womens-strider-rugged-beige",
  "productUrl": "https://allbirds.com/products/womens-strider-rugged-beige",
  "vendor": "Allbirds",
  "productType": "Shoes",
  "sku": "A11848W105",
  "option1Name": "Size",
  "option1Value": "10.5",
  "price": 91,
  "compareAtPrice": 130,
  "discountPercent": 30,
  "onSale": true,
  "available": true,
  "imageUrl": "https://cdn.shopify.com/s/files/1/1104/4168/files/A11847…jpg",
  "imageCount": 5,
  "updatedAt": "2026-08-21T04:54:55-07:00"
}
```

Export as CSV, Excel, JSON or XML, or pull it straight from the API.

***

### Scheduling

Prices change. Set a schedule to run this daily or weekly, and each run stores a dated snapshot you can compare — that is all a price-monitoring pipeline really needs.

***

### Legal

This actor reads only data that a Shopify store publishes publicly, at an address the store itself serves to anyone. It collects no personal data, requires no login, and bypasses no access control. You remain responsible for how you use the exported data.

***

Questions, or a store that behaves oddly? Open an issue on the actor — issues are read and answered.

# Actor input Schema

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

One or more Shopify store addresses. Paste the plain domain — the homepage, a product page or a myshopify.com address all work. Add several stores to compare competitors in a single run.

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

Stop after this many products from each store. Large catalogs run in pages of 250, so a round number here keeps runs predictable.

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

One row per variant gives you every size and colour with its own price and stock flag. One row per product collapses that into a price range — handy for a quick catalog overview.

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

Adds the product description as plain text, with the HTML stripped out. Leave this off if you are heading straight to a spreadsheet.

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

Keep only items that are currently in stock.

## `minDiscountPercent` (type: `integer`):

Keep only items discounted by at least this much. Set to 0 to keep everything. Useful for tracking a competitor's sale.

## Actor input object example

```json
{
  "storeUrls": [
    "https://gymshark.com",
    "allbirds.com"
  ],
  "maxProductsPerStore": 1000,
  "outputMode": "variant",
  "includeDescription": false,
  "onlyAvailable": false,
  "minDiscountPercent": 0
}
```

# Actor output Schema

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

No description

## `productsCsv` (type: `string`):

No description

# 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://gymshark.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("tidyfeed/shopify-product-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": ["https://gymshark.com"] }

# Run the Actor and wait for it to finish
run = client.actor("tidyfeed/shopify-product-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": [
    "https://gymshark.com"
  ]
}' |
apify call tidyfeed/shopify-product-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,tidyfeed/shopify-product-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/OZhKBBl2Cn08VPtK3/builds/4bLLuMmI0IRa2dbnR/openapi.json
