# Shopify Store Product Scraper (`galdrcode_dev/shopify-store-product-scraper`) Actor

- **URL**: https://apify.com/galdrcode\_dev/shopify-store-product-scraper.md
- **Developed by:** [Galdrcode](https://apify.com/galdrcode_dev) (community)
- **Categories:**
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.14 / 1,000 variants

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 — One Row Per Variant

Scrape the full product catalog of **any number of Shopify stores at once** — one clean row per variant, with prices, discounts and stock status.

Paste in a list of domains. Get back a flat table you can open in Excel, Google Sheets, or load straight into a database. No API keys, no store login, no configuration.

***

### Real sample output

Five actual rows from a live run against `allbirds.com`, `gymshark.com` and `kith.com`:

| store\_domain | product\_title | variant\_title | sku | price | compare\_at\_price | discount\_pct | on\_sale | available |
|---|---|---|---|---|---|---|---|---|
| allbirds.com | Trino® Tubers - Navy Night | S (W5-7) | ST1UNNT101 | 4.00 | 16.00 | 75.0 | true | false |
| allbirds.com | Trino® Tubers - Navy Night | M (W8-10 / M8) | ST1UNNT102 | 4.00 | 16.00 | 75.0 | true | false |
| gymshark.com | Gymshark Everyday Holdall Small - Black | One Size | I3A1V-BB2J-OS | 44.00 | | | false | true |
| kith.com | Kith Baby & '47 for the New York Yankees Acrylic Beanie | OS | 2000376136 | 210.00 | | | false | true |
| allbirds.com | Free Returns Coverage | $0.80 | x-redo | 0.80 | 0.80 | | false | true |

Note the last row: a compare-at price equal to the price is **not** counted as a discount. `on_sale` stays `false` and `discount_pct` stays empty, so your "what's discounted" filter doesn't fill up with noise.

Every row also carries `vendor`, `product_type`, `tags[]`, `options{}`, `image_url`, `grams`, `product_url`, `published_at` and `updated_at`.

***

### Why this one

Most Shopify scrapers return **one row per product**, with variants buried in a nested array. That is unusable in a spreadsheet — you cannot sort by price, filter by SKU, or pivot on size when every product is a single cell containing twenty variants.

This Actor returns **one row per SKU**. In the run above, 75 products became **523 rows** — the rows you actually wanted.

| | |
|---|---|
| **Bulk multi-domain** | Pass 1 store or 500. Built for competitor sets and market scans, not one-off lookups. |
| **Variant-level flat output** | One row per SKU. Spreadsheet-ready, no post-processing, no JSON flattening step. |
| **Detection + graceful skip** | Non-Shopify, password-protected and bot-protected domains are reported in a run summary with a reason — never silently dropped, and **never charged**. |
| **Discount + inventory signals** | `discount_pct` and `on_sale` are computed for you, `available` is surfaced per variant. The analysis you were going to do in Excel is already done. |

***

### Input

| Field | Type | Default | What it does |
|---|---|---|---|
| `storeUrls` | array | *required* | Store domains. `example.com`, `https://example.com`, or any page URL on the store — anything after the host is ignored. |
| `maxProductsPerStore` | integer | `0` | Cap per store. `0` = unlimited. Counts **products**, not variants. |
| `includeOutOfStock` | boolean | `true` | When off, out-of-stock variants are dropped and not charged. |
| `onlyOnSale` | boolean | `false` | When on, returns only discounted variants. Everything else is dropped and not charged. |
| `includeImages` | boolean | `true` | Adds `image_url` — the variant's own image, falling back to the product's first. |
| `maxConcurrency` | integer | `5` | Requests in flight across all stores. Each store paginates one page at a time. |
| `proxyConfiguration` | object | off | Usually unnecessary — the product feed has no anti-bot. Turn on only for stores behind a checkpoint. |

The example input is prefilled with three real stores, so the Actor produces output on the first click without you typing anything.

***

### Output

One row per variant in the dataset. Alongside it, a **run summary** is written to the key-value store under `RUN_SUMMARY`:

```json
{
  "stores_requested": 4,
  "stores_scraped": 2,
  "stores_skipped": 2,
  "total_variants": 31,
  "scraped": [
    { "store_domain": "allbirds.com", "currency": "USD", "products": 3, "variants": 28 }
  ],
  "skipped": [
    { "store_domain": "example.com", "reason": "not_shopify" },
    { "store_domain": "bombas.com",  "reason": "bot_protected" }
  ]
}
```

Skip reasons: `not_shopify`, `password_protected`, `bot_protected`, `empty_catalog`, `robots_disallowed`, `request_failed`.

***

### How it works, and its limits

The Actor reads Shopify's **public** `products.json` feed — the same data the storefront serves to anyone. No login is bypassed and no customer data is touched. `robots.txt` is respected and each store is paginated sequentially to stay polite.

Known limits, stated up front:

- **25,000 products per store.** Shopify's public feed refuses to page beyond this. Larger catalogs are truncated.
- **No `barcode`.** It is not in the public feed — it exists only in Shopify's authenticated Admin API. No scraper reading `products.json` can return it.
- **`currency` comes from `/meta.json`.** If a store blocks that endpoint, currency is `null` and everything else still works.
- **No collection membership.** Shopify's feed does not carry it, and deriving it costs one request per collection — real stores have 250+, so it is neither cheap nor complete. Left out rather than shipped half-working.
- **Password-protected stores return nothing** by design. They are reported, not charged.

***

### Pricing

Pay-per-event. You are charged **per variant row** actually returned to you.

Because this Actor returns a row per SKU rather than per product, compare the per-catalog total rather than the headline unit price — a store with 300 products and 5 sizes each is 1,500 rows here versus 300 elsewhere, for the same underlying data at finer granularity.

Filtered-out variants (`includeOutOfStock: false`, `onlyOnSale: true`) and every skipped domain cost nothing.

***

### Other Actors

- [Dubai Property Data Enricher](https://apify.com/galdrcode_dev) — property listing enrichment for the Dubai market.

Found a store that should work but doesn't? Open an issue on the Actor — the skip reason in `RUN_SUMMARY` tells me exactly what to look at.

# Actor input Schema

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

One or more Shopify store domains. Accepts bare domains (example.com), full URLs (https://example.com), or any page URL on the store — everything after the host is ignored. Non-Shopify and password-protected domains are reported in the run summary and are never charged.

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

Stop after this many products per store. 0 = unlimited. Note this counts products, not variants — one product usually yields several variant rows. Shopify's public feed caps out at 25,000 products per store regardless of this setting.

## `includeOutOfStock` (type: `boolean`):

When off, variants with `available: false` are dropped and never charged.

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

When on, only variants with a compare-at price higher than the current price are returned. Everything else is dropped and never charged.

## `includeImages` (type: `boolean`):

Adds `image_url` to every row — the variant's own image when it has one, otherwise the product's first image.

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

Across all stores. Each store is paginated one page at a time, so this is effectively how many stores are crawled in parallel. Keep it modest to stay polite.

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

Optional. Shopify's public product feed has no anti-bot protection, so proxies are not needed for most stores. Turn this on only if a store sits behind Cloudflare or a similar checkpoint.

## Actor input object example

```json
{
  "storeUrls": [
    "allbirds.com",
    "gymshark.com",
    "kith.com"
  ],
  "maxProductsPerStore": 0,
  "includeOutOfStock": true,
  "onlyOnSale": false,
  "includeImages": true,
  "maxConcurrency": 5,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `variants` (type: `string`):

Flat, spreadsheet-ready dataset - one row per SKU.

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

Stores requested, scraped and skipped (with a reason for each skip).

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

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

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

```

## MCP server setup

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