# Shopify Store Scraper - All Products & Live Prices (`gganbukim/shopify-product-scraper`) Actor

Scrape the full product catalogue of any Shopify store: titles, variants, SKUs, live prices, compare-at discounts, stock status, images, tags, product types and collections. Bulk multi-store, no API key, no login. Duplicate products from paginated boards are removed so you are never billed twice.

- **URL**: https://apify.com/gganbukim/shopify-product-scraper.md
- **Developed by:** [DONGMIN KIM](https://apify.com/gganbukim) (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

from $0.50 / 1,000 product delivereds

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/platform/actors/running/actors-in-store#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 Store Scraper — All Products, Variants, Prices & Stock from Any Store

Point it at any Shopify store and get the **entire product catalogue**: every product, every variant, SKUs, live prices, compare-at discounts, stock status, images, tags, product types and optionally the store's collections.

Multiple stores per run. No API key, no store permission, no login.

A live run pulled **600 products in under a second** from one store.

### Why this is reliable

Every Shopify storefront publishes `/products.json` — it is part of the platform, not an oversight, and it has been stable for years. There is no HTML to parse here and no markup churn to chase, which is why this actor does not break the way theme-scraping tools do.

Two things it handles that a naive reader does not:

- **Duplicate products.** Shopify paginates by offset over a catalogue that shifts between requests, so the same product genuinely comes back on two pages — measured at **100 repeats in 600 rows** on a live store. Those are dropped, so you are never billed twice for one product.
- **Bot walls.** A minority of stores sit behind a WAF that throttles datacenter IPs. The actor starts on cheap datacenter proxies and escalates to residential only for the stores that actually need it.

### Input

```jsonc
{
  "storeUrls": ["gymshark.com", "https://allbirds.com/collections/mens"],
  "maxProductsPerStore": 1000,
  "onSaleOnly": true,
  "inStockOnly": false,
  "minPrice": 0,
  "productTypeContains": ["shoes"],
  "includeVariants": true,
  "includeCollections": false
}
```

Bare domains or any URL on the store both work — it normalises to the origin.

#### Every option

The same wording you see in the Apify console, with the JSON key for API and MCP callers.

| Option | What it does | Default |
|---|---|---|
| **Shopify stores** — `storeUrls` *(required)* | Bare domains or any URL on the store — gymshark.com, https://allbirds.com/collections/mens, shop.example.co.uk. Multiple stores per run. | — |
| **Max products per store** — `maxProductsPerStore` | Products arrive 250 per request, so this is the main cost control. Leave high for a full catalogue. | `1000` |
| **On sale only** — `onSaleOnly` | Keep only products whose compare-at price is above the live price. Filtered products are not billed. | `false` |
| **In stock only** — `inStockOnly` | Keep only products with at least one available variant. | `false` |
| **Minimum price** — `minPrice` | In the store's own currency. 0 disables. | `0` |
| **Maximum price** — `maxPrice` | In the store's own currency. 0 disables. | `0` |
| **Product type or tag contains** — `productTypeContains` | Keep only products whose product type or tags contain one of these. Case-insensitive. | — |
| **Include variant detail** — `includeVariants` | Attach the full variant array (SKU, price, availability, options) to each product. Turn off for a slimmer dataset. | `true` |
| **Also scrape collections** — `includeCollections` | Add one row per collection with its title, handle and product count — a map of how the store organises its catalogue. | `false` |
| **Skip non-Shopify domains** — `skipNonShopify` | Check each domain is Shopify before scraping. Costs one cheap request per store and avoids wasted work. | `true` |
| **Concurrency** — `concurrency` | Stores processed in parallel. | `3` |
| **Proxy** — `proxyConfiguration` | Leave the default. A minority of stores sit behind a WAF that throttles datacenter IPs; the actor starts cheap and escalates to residential only for the stores that need it. | `{"useApifyProxy":true}` |

### Output

One row per product. With **Include collections** on, each store also writes one row per
collection, marked `type: "collection"`.

```json
{
  "storeDomain": "allbirds.com",
  "productId": 6806723428554,
  "title": "Men's Strider - Medium Grey",
  "handle": "mens-strider",
  "url": "https://allbirds.com/products/mens-strider",
  "vendor": "Allbirds",
  "productType": "Shoes",
  "tags": "sale,mens,wool",
  "createdAt": "2025-07-24T09:12:00-04:00",
  "publishedAt": "2025-08-01T10:00:00-04:00",
  "updatedAt": "2026-08-09T22:41:13-04:00",
  "minPrice": 91,
  "maxPrice": 95,
  "compareAtPrice": 130,
  "onSale": true,
  "discountPercent": 30,
  "inStock": true,
  "variantCount": 13,
  "availableVariantCount": 2,
  "imageUrl": "https://cdn.shopify.com/…",
  "imageCount": 7,
  "options": [{ "name": "Size", "values": ["9", "10", "11"] }],
  "description": "Soft & light. Wool upper.",
  "variants": [
    { "variantId": 1, "title": "9", "sku": "AB-9", "price": 91, "compareAtPrice": 130, "available": false, "option1": "9" }
  ]
}
```

#### Every field

You are billed per row delivered, so here is everything a row can contain. A field is
absent when the store did not publish it.

**Product rows**

| Field | What it is |
|---|---|
| `storeDomain` | Which store the row came from, so a multi-store run reads without a join. |
| `productId` | Shopify's numeric product id. |
| `title` | Product title. |
| `handle` | The URL slug. It is the stable key across renames, and what you join on against a store's own exports. |
| `url` | Canonical product URL. |
| `vendor` | Brand as the store records it. |
| `productType` | The store's own product type. |
| `tags` | The store's tag string. |
| `createdAt` | When the product was created in the store's admin — earlier than `publishedAt`, and the better signal for how long something has existed. |
| `publishedAt` | When it went live on the storefront. |
| `updatedAt` | Last edit of any kind, so a diff between runs tells you something changed. |
| `description` | Body copy with HTML stripped. |
| `options` | The option axes, each with `name` and `values` — e.g. Size and Colour. |
| `imageUrl` | First image. |
| `imageCount` | How many images the product has, without pulling them all. |
| `variantCount` | Number of variants. |
| `availableVariantCount` | How many of those are in stock — the difference is the size curve breaking. |
| `inStock` | `true` when any variant is available. |
| `minPrice` / `maxPrice` | Live price range across variants. |
| `compareAtPrice` | Highest compare-at price found, which is how Shopify represents the pre-markdown price. |
| `onSale` | `true` when a compare-at price sits above the live price. |
| `discountPercent` | That markdown as a whole-number percent. |
| `variants` | Per-variant rows: `variantId`, `title`, `sku`, `price`, `compareAtPrice`, `available`, `requiresShipping`, `grams`, `option1`–`option3`. Dropped when **Include variants** is off. |

`onSale` and `discountPercent` are derived from the compare-at price, so you get the
markdown without doing the arithmetic.

**Collection rows** (`type: "collection"`)

| Field | What it is |
|---|---|
| `type` | Always `"collection"` on these rows; product rows have no `type`. |
| `storeDomain` | Which store the collection belongs to. |
| `collectionId` | Shopify's numeric collection id. |
| `title` | Collection title. |
| `handle` | Its URL slug. |
| `url` | Canonical collection URL. |
| `productsCount` | How many products the store reports in it. |
| `updatedAt` | Last time the collection changed. |
| `description` | Collection body copy with HTML stripped. |

### Who this is for

- **Price analysts** — schedule it and diff the price ladder over time, variant by variant.
- **Buyers and merchandisers** — what a competitor sells, in what types, at what price, and what is selling out.
- **Sourcing and dropshipping teams** — whole catalogues, filtered by price band, without asking anyone for access.
- **Promotion trackers** — `onSaleOnly` shows exactly what a store has marked down and by how much.

### Common uses

- **Competitor price monitoring** — schedule it and diff prices over time.
- **Assortment analysis** — what a competitor sells, in what types, at what price ladder.
- **Discount tracking** — `onSaleOnly: true` shows exactly what a store has marked down and by how much.
- **Stock intelligence** — `availableVariantCount` reveals what is selling out.
- **Dropshipping and sourcing** — pull catalogues and filter by price band.
- **Feeding a comparison site or a price-tracking product.**

### Pricing

Pay per product delivered. Products removed by your filters, duplicate products, non-Shopify domains, and failed stores **cost nothing**.

Starting a run costs $0.00002 — the platform's $0.00001 minimum, charged once per GB of memory, and these Actors run on 2 GB. That is two cents per thousand runs, and it is the only charge not tied to a row you received.

### Other Actors in this family

Same engines, same billing, no account or API key on any of them.

**YouTube & video**

- [Download YouTube Subtitles in Bulk — SRT, VTT & Text](https://apify.com/gganbukim/youtube-transcript-scraper) — Bulk subtitles from videos, channels or playlists — text, SRT, VTT or RAG chunks.
- [Export YouTube Comments to CSV — Replies and Likes](https://apify.com/gganbukim/youtube-comments-scraper) — Every comment and reply thread, with likes, authors and creator flags.
- [List Every Video on a YouTube Channel — Export to CSV](https://apify.com/gganbukim/youtube-channel-scraper) — A channel's whole back catalogue plus a subscriber and RSS summary row.
- [Find YouTube Sponsors — Brand Deals, Codes & Links](https://apify.com/gganbukim/youtube-sponsorship-finder) — Which brands pay which creators, with the campaign link, the code and the timestamp.
- [YouTube Search API — Bulk Results, No Quota](https://apify.com/gganbukim/youtube-search-scraper) — Many search terms at once, every result as a row, filtered before you are billed.
- [Track Deleted YouTube Videos & Title Changes](https://apify.com/gganbukim/youtube-channel-monitor) — What a channel quietly changed: deleted videos, rewritten titles, view velocity.
- [YouTube Creator Email Finder & Sponsor Lookup](https://apify.com/gganbukim/youtube-creator-leads) — A channel list into leads: the published email, audience bands, and who already sponsors them.
- [Export a YouTube Playlist to CSV — Every Video](https://apify.com/gganbukim/youtube-playlist-scraper) — Any playlist as a table, with each video position in it.

**Search demand**

- [AnswerThePublic Alternative — Autocomplete Keyword API](https://apify.com/gganbukim/long-tail-keyword-scraper) — One seed into hundreds of real keywords from Google, YouTube and Amazon autocomplete.
- [Google Trends API — Today's Trending Searches, No Key](https://apify.com/gganbukim/google-trends-scraper) — Today's trending searches by country, with traffic bands and the news behind them.

**E-commerce**

- [New Shopify Product Alerts — Competitor Drop Tracker](https://apify.com/gganbukim/shopify-new-arrivals-monitor) — Only what a store launched since the last run. Scanning is free.

**Hiring**

- [Greenhouse, Lever & Ashby Job Scraper — No API Key](https://apify.com/gganbukim/ats-job-scraper) — Paste a company domain, get its open roles from Greenhouse, Ashby, Lever or SmartRecruiters.
- [Ghost Job Detector — Track Reposts, Closures & Edits](https://apify.com/gganbukim/job-lifecycle-monitor) — What changed on a careers page: opened, closed, quietly reposted, or a ghost job.

### FAQ

**Will I get blocked or rate-limited?** This reads `/products.json`, the endpoint Shopify itself serves on every shop for exactly this purpose. It is not defended, which is why the Actor runs on datacenter IPs and why the price can be what it is. Nothing here signs in to a store.

**Does this need Shopify API access?** No. It reads the public storefront endpoint that every Shopify store serves.

**Does it work on every Shopify store?** Almost all. A small number disable the endpoint (reported as a clear error) or sit behind a WAF (handled by proxy escalation).

**Is the domain Shopify?** The actor checks before scraping and skips non-Shopify domains rather than wasting your budget on them.

**Can I get inventory quantities?** No — Shopify exposes availability as a boolean publicly, not exact counts. `availableVariantCount` is the closest public signal.

**Can I run it on a schedule?** Yes, via Apify Schedules, webhooks, or the API. Also available over MCP for AI agents.

**Is it legal to scrape Shopify stores?** This reads the public `/products.json` endpoint
that Shopify itself serves on every storefront for exactly this purpose — no login,
nothing bypassed, no rate limit worked around. Prices, stock and titles are facts about
products on public sale. Each store's own terms are a separate contract question. Not
legal advice.

**How much does 1,000 products cost?** $0.50, plus $0.00002 for the run. Products your
filters remove, duplicate rows from paginated boards, and stores that fail are never
billed.

**Can I export the results to Excel or Google Sheets?** Yes. Every run's dataset
downloads as CSV, Excel, JSON, XML or RSS from the Storage tab, or straight from the API
if you want a live link a spreadsheet can pull.

**Can I connect it to Zapier, Make or n8n?** Yes — Apify publishes integrations for all
three, plus webhooks that fire when a run finishes. A common setup is a schedule here and
a webhook into your own database or Slack.

**Do I need to write code?** No. Fill the form in the console and press Start. If you do
want code, the Apify client libraries for Python and JavaScript call this the same way,
and it is available over MCP so an AI agent can call it directly.

# Actor input Schema

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

Bare domains or any URL on the store — gymshark.com, https://allbirds.com/collections/mens, shop.example.co.uk. Multiple stores per run.

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

Products arrive 250 per request, so this is the main cost control. Leave high for a full catalogue.

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

Keep only products whose compare-at price is above the live price. Filtered products are not billed.

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

Keep only products with at least one available variant.

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

In the store's own currency. 0 disables.

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

In the store's own currency. 0 disables.

## `productTypeContains` (type: `array`):

Keep only products whose product type or tags contain one of these. Case-insensitive.

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

Attach the full variant array (SKU, price, availability, options) to each product. Turn off for a slimmer dataset.

## `includeCollections` (type: `boolean`):

Add one row per collection with its title, handle and product count — a map of how the store organises its catalogue.

## `skipNonShopify` (type: `boolean`):

Check each domain is Shopify before scraping. Costs one cheap request per store and avoids wasted work.

## `concurrency` (type: `integer`):

Stores processed in parallel.

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

Leave the default. A minority of stores sit behind a WAF that throttles datacenter IPs; the actor starts cheap and escalates to residential only for the stores that need it.

## Actor input object example

```json
{
  "storeUrls": [
    "gymshark.com"
  ],
  "maxProductsPerStore": 1000,
  "onSaleOnly": false,
  "inStockOnly": false,
  "minPrice": 0,
  "maxPrice": 0,
  "includeVariants": true,
  "includeCollections": false,
  "skipNonShopify": true,
  "concurrency": 3,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

One row per product, with prices, variants and sale state; collection rows too when that option is on.

## `runSummary` (type: `string`):

Counts for this run: rows delivered, rows filtered, items that failed, the proxy tier used, and whether the run stopped at its charge limit.

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

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

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

```

## MCP server setup

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