# Shopify Product Scraper (`worktech/shopify-product-scraper`) Actor

Scrape any Shopify store without an API key: products, prices, variants, images, stock and discounts. Pay per result - price tracking from $1.50 per 1,000 products. Export JSON, CSV or Excel.

- **URL**: https://apify.com/worktech/shopify-product-scraper.md
- **Developed by:** [Per Schondell](https://apify.com/worktech) (community)
- **Categories:** E-commerce
- **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.

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

Extract **all products from any Shopify store** — prices, variants, images, stock status, and discounts — with no Shopify API key, no login, and no coding required. Enter a store URL, press Run, and export the full catalog to **JSON, CSV, or Excel**.

Unlike bundled scrapers that bill you for everything on every run, this actor is **pay-per-result with modular tiers**: a daily price check on 1,000 products costs **$1.50**, not $4–8.

### What can you do with it?

- **Track Shopify competitor prices daily** — current price, compare-at price, and discount status for every product
- **Detect winning products** — the built-in `soldOutRatio` shows which products are selling out (a strong demand signal)
- **Export a full Shopify catalog** — variants, images, tags, descriptions, vendors, SKUs, and barcodes for product research or dropshipping
- **Import products to your own store** — clean JSON/CSV output maps directly to catalog import tools
- **Monitor stock and availability** — per-variant sold-out counts across the whole store
- **Feed AI agents and pipelines** — structured output via API, webhooks, or the Apify MCP server

### Pricing — pay only for what you need

| Mode | Price | Returns | Best for |
|------|------|---------|----------|
| **Price**   | **$1.50 / 1K products** | current price, compare-at price, availability, sold-out variant ratio | Daily price tracking, repricing, discount detection |
| **Catalog** | **$4 / 1K products** | + variants, images, tags, description, vendor, SKU, barcode | Product research, dropshipping, catalog import |
| **Full**    | *(roadmap)* | + review-app aggregation (Judge.me / Yotpo / Loox / Okendo / Stamped), sales estimate | Competitor intelligence, full audits |

**Concrete examples:**

| Scenario | Mode | Run cost |
|----------|------|----------|
| Daily price check on 1,000 products | Price | $1.50 / day |
| Full catalog export of 5,000 products (one-time) | Catalog | $20 |

Compare with bundled-billing actors that charge $4–$7 per 1K products regardless of which fields you actually use.

### How to scrape a Shopify store (step by step)

1. Click **Try for free** — you'll need a free Apify account (no credit card for the trial).
2. Paste one or more Shopify store URLs into **Shopify store URLs** (e.g. `https://allbirds.com`).
3. Pick a **mode**: `price` for price tracking, `catalog` for full product data.
4. Click **Start**. The run finishes in seconds for most stores.
5. Open the **Dataset** tab and export as JSON, CSV, Excel, HTML, or XML — or fetch it via API.

To run it on a schedule (e.g. daily price tracking), click **Actions → Schedule** on the actor page.

#### Input

```json
{
  "mode": "price",
  "storeUrls": ["https://allbirds.com", "https://gymshark.com"],
  "maxProductsPerStore": 500,
  "delayMs": 1500
}
```

#### Sample output (Price mode)

```json
{
  "storeUrl": "https://allbirds.com",
  "productId": 6616124981328,
  "handle": "trino-cozy-crew-heathered-onyx",
  "title": "Trino® Cozy Crew - Heathered Onyx",
  "vendor": "Allbirds",
  "productType": "Socks",
  "minPriceCents": 2400,
  "maxPriceCents": 2400,
  "compareAtMinCents": null,
  "compareAtMaxCents": null,
  "hasDiscount": false,
  "variantCount": 4,
  "soldOutCount": 3,
  "soldOutRatio": 0.75,
  "scrapedAt": "2026-05-27T15:53:23.620Z"
}
```

A `soldOutRatio` near 1.0 with the product still listed is one of the strongest demand signals in Shopify data — buyers cleared most variants and the store hasn't restocked yet.

### Use it from code or AI agents

Run the actor and read results with the [Apify API](https://docs.apify.com/api/v2) or client libraries:

```bash
curl -X POST "https://api.apify.com/v2/acts/worktech~shopify-product-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"mode":"price","storeUrls":["https://allbirds.com"]}'
```

It also works with **n8n, Make, Zapier**, webhooks, and the **Apify MCP server**, so AI agents can discover and call it as a tool.

### Why this scraper?

- **100% parse success rate** on every reachable Shopify store in our 20-store benchmark (report ships in the repo; reproduce with `npm run test:real`).
- **Correct SKU merging** — one row per product, not one row per variant. (Some popular alternatives get this wrong.)
- **No silent failures** — HTTP 430 security rejections, 429 rate limits, malformed JSON, and blocked endpoints surface as named errors with per-store context. A failed *store* never poisons the rest of the run: the remaining stores are still scraped. (A **billing** failure is the one exception — see "What the run reports" below.)
- **Partial stores are still counted** — if a store delivers 250 products and is then blocked on page 2, those 250 products are in your dataset and are reported as 250. The run never tells you 0 for rows you actually received.
- **Retry with backoff** — 429/5xx retry automatically honouring `Retry-After`; 430/4xx fail fast (they won't fix themselves).
- **Cents-based price math** — integer minor units, no float drift on `19.99 + 0.01`.

### What the run reports

Every run writes a **`SUMMARY`** record to the run's key-value store:

| Field | Meaning |
|---|---|
| `mode` | `price` or `catalog` |
| `storeCount` | stores you asked for |
| `successCount` | stores scraped without error |
| `failureCount` | stores that failed (see `failures`) |
| `totalProductsScraped` | rows **delivered** to your dataset |
| `rowsBilled` | rows a **charge was recorded for** |
| `failures` | `[{ storeUrl, error }]`, one per failed store |
| `storesNotAttempted` | stores the run never reached because it aborted |
| `billingFailure` | `null` normally; a message when billing broke |

`successCount + failureCount + storesNotAttempted.length` always equals `storeCount`.

#### Billing failures abort the run (read this before you re-run)

`totalProductsScraped` and `rowsBilled` are reported separately because they can
differ. If Apify cannot record a pay-per-event charge — for example the actor's
monetization is misconfigured — then rows have already been delivered to your
dataset that could not be billed.

When that happens the actor **stops immediately and fails the run**. It does not
continue to the remaining stores, and `storesNotAttempted` names the ones it
skipped. This is deliberate: the same fault would repeat on every remaining
store, so carrying on would quietly deliver more and more unbilled data while
reporting a green run. The failure message states how many rows were delivered
and how many were billed, so you can see your exposure.

**Re-running after a billing failure** re-delivers *and* re-bills the rows that
were already delivered — they are not de-duplicated across runs. Fix the
pay-per-event configuration first, and if you only need the stores that were
skipped, re-run with just the URLs listed in `storesNotAttempted`.

A *scrape* failure (430/429/malformed) never does any of this: it is contained
to its own store and the run continues and finishes normally.

#### Comparison vs alternatives (Apify Store)

| Feature | Shopify Product Scraper | autofacts/shopify | webdatalabs/shopify-product-scraper |
|---------|:---:|:---:|:---:|
| Modular pay-per-result billing (Price / Catalog) | ✅ | ❌ | ❌ |
| Proper SKU merging on product pages | ✅ | ❌ (documented limitation) | ✅ |
| Explicit 430 / 429 / parse error logging | ✅ | partial | partial |
| Retry-with-backoff + Retry-After honored | ✅ | partial | partial |
| Cents-based price math (no float drift) | ✅ | ✅ | ❓ |
| Published parse success rate | **100% on 20/20 reachable stores** | not published | not published |

### FAQ

**Do I need a Shopify API key or store access?**
No. The scraper reads the store's public product listing — the same data any visitor's browser sees. You only need the store URL.

**Can I export Shopify products to CSV or Excel?**
Yes. Every run produces a dataset you can download as CSV, Excel, JSON, HTML, or XML from the Apify Console, or fetch via API.

**Is it legal to scrape a Shopify store?**
The actor only collects publicly available product data (no personal data, no login-protected content). Whether scraping is permissible for your use case depends on your jurisdiction and the store's terms — when in doubt, consult a lawyer.

**How do I track Shopify prices every day?**
Set the mode to `price`, add your competitor stores, and create a Schedule on the actor page. Each daily run costs $1.50 per 1,000 products tracked.

**Does it work on any Shopify store?**
Any store on Shopify with a public catalog. If a site has moved off Shopify, the actor reports a clear `NOT_SHOPIFY` error instead of returning empty data.

**What about stores with more than 5,000 products?**
Currently the actor reads the public products endpoint, which returns up to ~5,000 products per store. Sitemap-based pagination for larger catalogs is next on the roadmap.

**How is this different from the Shopify API?**
The official Admin API requires the store owner's credentials. This scraper needs nothing — it works on *any* store, including competitors' stores.

### Roadmap

- Sitemap-based pagination for catalogs >5K products; Web Bot Auth signed requests
- **Full mode** — review-app aggregation (Judge.me / Yotpo / Loox / Okendo / Stamped) + algorithmic sales estimate
- Diff / webhook mode for price-change alerts on a watched set
- CHF / EUR rounding, VAT-inclusive prices, German review platforms

### Limitations

- Public products endpoint only for now; stores with >5,000 products lose tail items until sitemap pagination ships.
- "Sales estimate" (Full mode, roadmap) will be algorithmic — directional, not transaction data.

### Status

**v0.2** — Price and Catalog modes in production with pay-per-event billing. 68 unit tests + 33-store integration suite passing, plus a 19-check in-image pre-push gate (`gate.mjs`).

# Actor input Schema

## `mode` (type: `string`):

Price = current/compare-at price + sold-out ratio. Catalog = full product data. Full = adds review-app aggregation.

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

One or more Shopify storefront URLs (e.g. https://allbirds.com).

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

Cap on products per store. 0 = unlimited.

## `delayMs` (type: `integer`):

Politeness delay between paginated requests to the same store.

## Actor input object example

```json
{
  "mode": "price",
  "storeUrls": [
    "https://allbirds.com"
  ],
  "maxProductsPerStore": 0,
  "delayMs": 1500
}
```

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

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

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

```

## MCP server setup

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