# Shopify Scraper — Products API, Prices & Stock | $0.90/1k (`glasswing/shopify-products-scraper`) Actor

Scrape any Shopify store by domain: products, variants, prices, compare-at prices, stock, SKUs, images, vendor, type, tags, dates and store currency. Many stores per run, collections, product URLs, filters, product or variant rows. No API key.

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

## Pricing

from $0.90 / 1,000 product results

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

### What does Shopify Products Scraper do?

Shopify Products Scraper reads the **product catalog of any Shopify store** and gives it back as clean JSON, CSV or Excel: titles, every variant with its price, compare-at (was) price, stock status and SKU, images, vendor, product type, tags, created / updated / published dates and the store's currency. Enter store domains such as `colourpop.com` or `allbirds.com` (one or thousands), and get their products in seconds. No Shopify account, no API key, no app install and no browser.

It works as a **Shopify products API** for stores you do not own. Every Shopify store publishes its catalog as public JSON (`/products.json`), and this Actor reads that feed page by page, handles pagination, redirects, headless storefronts and currency, and turns it into one tidy row per product (or per variant).

Typical use cases:

- **Competitor price monitoring**: track prices, discounts (compare-at prices) and stock of rival Shopify stores every day with a schedule.
- **Product research and dropshipping**: pull full catalogs of trending DTC brands, their best sellers and new arrivals, with images and descriptions.
- **Catalog and market analysis**: compare assortments, vendors, product types and price ranges across hundreds of stores.
- **Feeds for AI agents and apps**: a structured Shopify product scraper an agent can call with just a domain.

It reads only data the stores publish for every visitor. It does not log in and does not collect customer data (Shopify's public feeds contain none).

### Why use Shopify Products Scraper?

- **The whole catalog, fast.** 250 products per request; 1,445 products from 30 stores took 7 seconds on Apify in our test.
- **Works where other Shopify scrapers stop.** Headless stores (a custom front end on `www`) and stores that redirect to regional domains are read through the store's own Shopify address, so they still return data.
- **Correct currency.** The price currency comes from the store's shop record and every request pins it, so prices never switch to a visitor's local currency, and the currency is never guessed.
- **Filters that save money.** In stock only, price range, product type, vendor, tags, title keywords and "updated since" are applied before rows are written; you pay only for rows you keep.
- **Honest results.** Every row has a `status`. A site that is not a Shopify store, an empty collection or a deleted product gives one free `not_found` row that says why; you are billed only for `ok` rows.
- **Cheap.** $0.90 per 1,000 products, Apify platform usage included.

### What data can Shopify Products Scraper extract?

One row per product (default) with these fields:

| Field | Type | Description |
|---|---|---|
| `title` | string | Product title |
| `storeDomain` | string | Store domain as you entered it, e.g. `allbirds.com` |
| `storeName` | string | Shop name from the store's public shop record |
| `productId` | integer | Shopify product ID |
| `handle` | string | Product handle (URL slug) |
| `url` | string | Product page on the store |
| `vendor` | string | Vendor / brand |
| `productType` | string | Product type as the store names it |
| `tags` | array | Product tags |
| `minPrice`, `maxPrice` | number | Lowest and highest variant price |
| `compareAtMinPrice`, `compareAtMaxPrice` | number | Compare-at ("was") prices, when the store sets them |
| `currency` | string | ISO currency of the prices, e.g. `USD`, `AUD`; empty if the store does not publish it |
| `available` | boolean | At least one variant can be bought |
| `onSale` | boolean | A variant's compare-at price is above its price |
| `variantCount` | integer | Number of variants |
| `variants` | array | `id`, `title`, `sku`, `price`, `compareAtPrice`, `available`, `option1`-`option3`, `grams` per variant |
| `images`, `imageCount` | array, integer | Image URLs (up to 50) and the total count |
| `bodyText` | string | Description as plain text (HTML removed) |
| `options` | array | Option names and values, e.g. Size: S, M, L |
| `createdAt`, `updatedAt`, `publishedAt` | string | Dates with the store's time-zone offset |
| `collection` | string | Collection handle, when the row came from a collection |
| `source` | string | `products_feed`, `collection_feed` or `product_page` |
| `status`, `error`, `scrapedAt` | string | Row status, reason for non-`ok` rows, time of extraction |

With **One row per = Variant**, each size/colour is its own row with `variantId`, `variantTitle`, `sku`, `price`, `compareAtPrice`, `available`, `option1`-`option3`, `grams` and `image`, plus the product columns. Rows from **Product URLs** also carry per-variant `inventoryQuantity` and `barcode` when the store publishes them.

#### Result status (tri-state output)

| `status` | Meaning | Billed? |
|---|---|---|
| `ok` | A product (or variant) row. | Yes |
| `not_found` | Not a Shopify store, feed turned off, password-protected store, empty or unknown collection, deleted product, or nothing matched your filters. `error` says which. | No |
| `error` | The store could not be read after several retries (network error, rate limit). `error` says why. | No |

### How to scrape Shopify stores

1. Open the Actor in Apify Console and click **Try for free**.
2. Enter one or more store domains in **Store URLs or domains** (`colourpop.com`, `https://www.allbirds.com`), collection URLs, or product URLs.
3. Optionally pick **Collection handles**, **One row per** (product or variant) and **Filters**.
4. Set **Maximum results**: the default 20 is a quick test; raise it to 10,000 or more to read whole catalogs.
5. Click **Start**. The default run finishes in about 5-10 seconds. Download the results as JSON, CSV, Excel or HTML.

To automate it, call the Actor from the **API** tab (Node.js, Python, curl) or add a **Schedule** for daily price tracking.

### How much does it cost to scrape Shopify?

This Actor uses **pay-per-event** pricing. Apify platform usage is included in these prices.

| Event | Price |
|---|---|
| Actor start | $0.005 per run |
| Result (`status: ok` row) | $0.0009 per product (or per variant row) |

Examples: the default run (20 products) costs $0.023. 1,000 products cost $0.905, 10,000 products $9.005. Rows with `status` `not_found` or `error` are never billed, and products your filters drop are never written or billed. You can cap spending with **Maximum results**, **Maximum results per store** and the run's **Max total charge** option.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `startUrls` | array of strings | `colourpop.com`, `allbirds.com` | Store domains or URLs, collection URLs or product URLs |
| `productUrls` | array of strings | - | Product pages to read individually |
| `collections` | array of strings | - | Collection handles to read in every store |
| `outputMode` | `product` or `variant` | `product` | One row per product or per variant |
| `maxItems` | integer | `20` | Stop after this many rows in total |
| `maxItemsPerStore` | integer | - | Stop reading a store after this many rows |
| `inStockOnly` | boolean | `false` | Only products (variants) that can be bought |
| `minPrice`, `maxPrice` | integer | - | Price range, in the store's currency |
| `productTypes`, `vendors`, `tags`, `keywords` | array of strings | - | Keep matching products only |
| `updatedSince` | string | - | `YYYY-MM-DD` or relative such as `7 days` |
| `proxyConfiguration` | object | off | Optional Apify Proxy (datacenter is enough) |

Example: the in-stock hoodies under $80 of two stores, one row per variant:

```json
{
    "startUrls": ["gymshark.com", "https://www.allbirds.com"],
    "keywords": ["hoodie"],
    "inStockOnly": true,
    "maxPrice": 80,
    "outputMode": "variant",
    "maxItems": 500
}
```

### Output

Real rows from a run on Apify (arrays shortened):

```json
[
    {
        "url": "https://allbirds.com/products/womens-allbirds-flip-flop-dusty-pink",
        "status": "ok",
        "scrapedAt": "2026-09-27T22:24:39.779Z",
        "storeDomain": "allbirds.com",
        "storeName": "Allbirds",
        "productId": 7340901859408,
        "handle": "womens-allbirds-flip-flop-dusty-pink",
        "title": "Women's Allbirds Flip Flop - Dusty Pink",
        "vendor": "Allbirds",
        "productType": "Shoes",
        "tags": ["allbirds::complete => true", "allbirds::edition => limited", "allbirds::gender => womens"],
        "createdAt": "2026-03-27T14:03:50-07:00",
        "updatedAt": "2026-09-27T15:24:39-07:00",
        "publishedAt": "2026-09-25T16:58:13-07:00",
        "currency": "USD",
        "minPrice": 25,
        "maxPrice": 25,
        "compareAtMinPrice": 50,
        "compareAtMaxPrice": 50,
        "available": true,
        "onSale": true,
        "variantCount": 7,
        "variants": [
            { "id": 42146889039952, "title": "5", "sku": "A12513W050", "price": 25, "compareAtPrice": 50, "available": false, "option1": "5", "option2": null, "option3": null, "grams": 455 }
        ],
        "images": ["https://cdn.shopify.com/s/files/1/1104/4168/files/A12513_26Q2_Allbirds-Flip-Flop-Dusty-Pink_PDP_LEFT.png?v=1774646345"],
        "imageCount": 5,
        "bodyText": "Sun on your feet. Comfort underneath. Light, easy, and made for warm weather, these flip flops bring everyday comfort to sunny days...",
        "options": [{ "name": "Size", "values": ["5", "6", "7", "8", "9", "10", "11"] }],
        "collection": null,
        "source": "products_feed"
    },
    {
        "url": "https://princesspolly.com/",
        "status": "not_found",
        "error": "princesspolly.com is not a Shopify store: us.princesspolly.com/meta.json answered HTTP 404 (every Shopify store answers it). If the shop runs on another domain, enter that domain or its .myshopify.com address.",
        "scrapedAt": "2026-09-27T22:24:38.641Z",
        "storeDomain": "princesspolly.com"
    }
]
```

### Tips

- Put many stores in one run: you pay the start fee once, and stores are read in parallel.
- Use **Maximum results per store** so one huge catalog cannot use up the run.
- For daily price tracking, schedule the run and use **Updated since** `1 day`: the feed is newest-first, so the scan stops early.
- If a store answers with rate-limit errors, run it again later or enable Apify Proxy (datacenter).

### Limitations

- Only public Shopify storefronts. Password-protected stores and stores that turned their public product feed off return a `not_found` row.
- Stock counts (`inventoryQuantity`) are published by some stores only, and only on product-URL rows; feed rows carry `available` per variant, not quantities.
- Prices are in the store's own currency (its main market). Regional price lists of other markets are not collected.
- `productType`, `vendor` and `tags` are whatever the store enters; they differ between stores.
- For product URLs on stores with a headless front end, `available` comes from the published stock data and can be empty; `availabilityError` then says why. Scanning the store or a collection always gives `available`.

### FAQ

#### Do I need a Shopify account, app or API key?

No. The Actor reads the public JSON that every Shopify store serves to every visitor. It works for any store, not only your own.

#### How do I know if a site is a Shopify store?

Just enter it. A Shopify store returns products; any other site returns one free `not_found` row explaining that it is not a Shopify store (or that its feed is off). You can use this to check lists of domains.

#### Can I get the products of one collection only?

Yes. Enter the collection URL (`https://colourpop.com/collections/best-sellers`) or put the handle `best-sellers` in **Collection handles**.

#### How do I track the price of specific products?

Put their URLs in **Product URLs** and schedule the run. Use **One row per = Variant** to get one row per size or colour with its own price and stock.

#### Can I use this Actor from an AI agent or MCP client?

Yes. It runs with no input at all (it then reads two example stores), accepts plain domains, and every row explains itself with `status` and `error`.

#### Why did I get fewer rows than `maxItems`?

The stores have fewer matching products, a per-store limit stopped them, or your run hit its **Max total charge**. `not_found` rows say when a filter matched nothing.

### Related Actors

- [App Store Scraper](https://apify.com/glasswing/app-store-scraper) - use it if the merchant also sells or promotes an iOS app.
- [Google Play Scraper](https://apify.com/glasswing/google-play-scraper) - use it if the merchant also sells or promotes an Android app.
- [Google Maps Scraper — Business Leads with Emails](https://apify.com/glasswing/google-maps-leads-2-49-1k) - use it to find local retailers as leads alongside online store data.
- [Pinterest Scraper](https://apify.com/glasswing/pinterest-scraper) - use it to see how the same products are being pinned and discovered visually.

### Legal and data-protection notice

This Actor extracts only product and store information that Shopify stores publish publicly for every visitor; it does not extract customer data, e-mail addresses, phone numbers or other personal data, and it does not log in or get around access controls. Personal data is protected by the GDPR in the European Union and by other regulations around the world; do not use the output to process personal data without a legitimate reason. You are responsible for complying with each store's terms of service and applicable law when using the extracted data.

This Actor is an independent tool and is not affiliated with, endorsed by or sponsored by Shopify Inc. or any store it reads. Shopify is a trademark of Shopify Inc. All trademarks belong to their respective owners.

# Changelog

This Actor's version history is a separate document: https://apify.com/glasswing/shopify-products-scraper/changelog.md

# Actor input Schema

## `startUrls` (type: `array`):

Shopify stores to scan, one per line: a bare domain (`colourpop.com`), a store URL (`https://www.allbirds.com`), a collection URL (`https://colourpop.com/collections/best-sellers`) or a product URL. A domain or store URL reads the whole public catalog. A site that is not a Shopify store gets one free `not_found` row explaining why.

## `productUrls` (type: `array`):

Optional. Individual product pages to read (`https://store.com/products/<handle>`), for example to track the price and stock of specific items. These rows also carry per-variant stock counts and barcodes when the store publishes them. When you give only product URLs, the example stores above are skipped.

## `collections` (type: `array`):

Optional. Read only these collections of every store in Store URLs, e.g. `best-sellers`, `sale`, `new-arrivals` (the part after `/collections/` in the store's URL). A collection that does not exist gives one free `not_found` row.

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

`product` (default): one row per product with its variants in a `variants` array and min/max prices. `variant`: one row per variant (size, colour...) with its own price, compare-at price, SKU and availability - handy for price and stock tracking in a spreadsheet.

## `maxItems` (type: `integer`):

Stop after this many rows in total. Each row with status `ok` is one billable result. A large store has thousands of products; raise this to read whole catalogs.

## `maxItemsPerStore` (type: `integer`):

Optional. Stop reading a store after this many rows, so one huge store cannot use up the whole run. Product URLs you list are always returned. Leave empty for no per-store limit.

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

Keep only products with at least one available variant (in variant mode: only available variants).

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

Keep products with at least one variant priced at or above this amount, in the store's own currency (in variant mode: variants at or above it). Whole units, e.g. 20.

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

Keep products with at least one variant priced at or below this amount, in the store's own currency (in variant mode: variants at or below it). Whole units, e.g. 100.

## `productTypes` (type: `array`):

Keep only these product types, as the store names them (case-insensitive exact match), e.g. `Shoes`, `Lipstick`.

## `vendors` (type: `array`):

Keep only products from these vendors (case-insensitive exact match), e.g. `Nike`, `Apple`. Useful for multi-brand stores.

## `tags` (type: `array`):

Keep products that carry at least one of these tags (case-insensitive exact match), e.g. `sale`, `new-arrival`.

## `keywords` (type: `array`):

Keep products whose title contains at least one of these words or phrases (case-insensitive), e.g. `hoodie`.

## `updatedSince` (type: `string`):

Keep products the store changed on or after this date: `YYYY-MM-DD`, or relative such as `7 days` or `24 hours`. The store's own product feed is newest-first, so the scan stops early once it reaches older products.

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

Optional. The Actor reads Shopify's public JSON directly and works from Apify's datacenter IPs. Enable Apify Proxy (datacenter group is the default) only if a store rate-limits you; residential proxies are not needed.

## Actor input object example

```json
{
  "startUrls": [
    "https://colourpop.com",
    "https://allbirds.com"
  ],
  "outputMode": "product",
  "maxItems": 20,
  "inStockOnly": false,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `results` (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 = {
    "startUrls": [
        "https://colourpop.com",
        "https://allbirds.com"
    ],
    "maxItems": 20,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("glasswing/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 = {
    "startUrls": [
        "https://colourpop.com",
        "https://allbirds.com",
    ],
    "maxItems": 20,
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("glasswing/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 '{
  "startUrls": [
    "https://colourpop.com",
    "https://allbirds.com"
  ],
  "maxItems": 20,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call glasswing/shopify-products-scraper --silent --output-dataset

```

## MCP server setup

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