# Shopify Scraper & Price Tracker - Products, Stock, Alerts (`akhil_rajesh/shopify-scraper`) Actor

Shopify scraper & API: store and product scraper for any store, collection or product link, with variants, prices per country, stock, images, videos. Shopify competitor price tracker & monitor with price history and restock alerts: price drops, new and removed products, to Slack, Discord or Teams.

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

## Pricing

from $0.70 / 1,000 result rows

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Shopify Scraper & Price Tracker

Scrape any Shopify store's products, prices and stock, and track what changes. Give it a store, a collection link or a product link and get every product with its variants, prices in the currency of the country you pick, compare-at (sale) prices, stock per variant, SKU, barcode, vendor, product type, tags, every image, product videos, the full HTML description, SEO title and description, subscription plans, the collections it belongs to and links. A store's `/collections` link lists its collections, and Shopify's own "you may also like" products can be added to each row. Switch on **Only changes** and schedule it: each run returns only new products, price drops and rises, products back in stock, sold out, newly on sale or removed, and can post them to **Slack, Discord or Microsoft Teams**.

### Why this scraper

- **Any Shopify store, no login, no API key.** It reads the store's own Storefront API, the one Shopify gives every storefront, so the data is what the store shows its shoppers.
- **Best sellers or newest first.** Shopify's own best-seller order, newest products, price low to high or high to low, or the store's order. Most scrapers only read `/products.json`, which has no best-seller order and no local prices.
- **Prices for a country.** Set Country to `United Kingdom`, `Canada` or `DE` and prices come in that market's currency, exactly as the store charges there. A store that doesn't sell to that country is named, and its own prices are kept.
- **Stock per variant.** Every size and colour with its price, compare-at price, SKU, barcode, weight and whether it can be bought now; `variantsInStock` counts them.
- **Change tracking built in.** New products, price drops and rises past a minimum %, back in stock, sold out, newly on sale and removed, each reported once, with every price the tracker has seen (`trackedPrices`).
- **The whole product page.** Every image with its size, hosted videos (the best MP4) and YouTube or Vimeo links, the description as the store's own HTML, SEO title and description, subscribe-and-save plans, and the product's collections. Switch off **Product details** for lean rows read 250 at a time.
- **Recommendations and collection lists.** Up to 10 of Shopify's own recommended products per row, and a store's whole list of collections from its `/collections` link.
- **Already using another Shopify scraper?** Its input works here as it is: `startUrls`, `domains`, `maxRequestsPerCrawl`, `maxProducts` and `maxRecommendationsPerProduct` are read with the same meaning, so a saved task or API call can switch by changing only the Actor's name.
- **Alerts where your team works.** Paste a Slack, Discord or Teams webhook and each change is posted once.
- **Filters that run on the store's side.** Product types, stock and price ranges are sent with the request where Shopify allows, so a filtered run of a 20,000-product store reads only what matches.

### Use cases

- **Competitor price monitoring**: watch rival stores' prices and sales every day, with alerts for drops.
- **Restock alerts**: know the moment a sold-out product or size comes back.
- **New product tracking**: see what a brand launches, newest first.
- **Market research and dropshipping**: a store's best sellers, price ranges, vendors and product types.
- **Catalogue exports**: every product and variant of a store as JSON, CSV or Excel.

### How to use it in 4 steps

1. Paste one or more stores (`allbirds.com`), collection links (`https://www.allbirds.com/collections/mens`) or product links into **Stores, collections or products**.
2. Pick **Sort by** and, if you want local prices, a **Country**.
3. Set **Max results** (rows in all) and any filters.
4. Click **Start**. For tracking, switch on **Only changes**, add a webhook if you like, and schedule the run.

### What you get

One row per product (or per variant, with **One row per variant**):

```json
{
  "type": "product",
  "store": "www.allbirds.com",
  "storeName": "Allbirds",
  "rank": 1,
  "country": "US",
  "productId": "1878193471557",
  "handle": "mens-wool-runners",
  "title": "Men's Wool Runner - Natural Grey (Light Grey Sole)",
  "vendor": "Allbirds",
  "productType": "Shoes",
  "price": 110,
  "onSale": false,
  "currency": "USD",
  "available": true,
  "variantsCount": 7,
  "variantsInStock": 3,
  "options": [{"name": "Size", "values": ["8", "9", "10", "11", "12", "13", "14"]}],
  "tags": ["allbirds::gender => mens", "allbirds::material => wool", "shoprunner"],
  "url": "https://www.allbirds.com/products/mens-wool-runners",
  "imageUrl": "https://cdn.shopify.com/s/files/1/1104/4168/files/Allbirds_WL_RN_SF_PDP_Natural_Grey_LAT.png?v=1751143404",
  "variants": [
    {"variantId": "31338073981072", "title": "8", "sku": "WR3MNCW080", "barcode": "843416126274", "price": 110,
     "currency": "USD", "available": false, "options": {"Size": "8"}, "weight": 1.872, "weightUnit": "POUNDS"}
  ],
  "createdAt": "2018-11-12T22:25:43Z",
  "publishedAt": "2026-09-25T23:06:01Z",
  "scrapedAt": "2026-11-12T23:40:00.000Z"
}
```

A real row from Allbirds' best sellers (2026-11-12), with its tags and variants cut short here. Fields a product doesn't have (no compare-at price, for one) are left out of its row.

| Field | What it is |
|---|---|
| `price`, `maxPrice` | The lowest and highest variant price, in `currency` |
| `compareAtPrice`, `discountPercent`, `onSale` | The store's "was" price and the discount, when a variant is on sale |
| `available`, `backorder` | Whether it can be bought now, and whether that's on backorder |
| `variantsCount`, `variantsInStock` | Variants in all, and those that can be bought |
| `variants` | Every variant: id, title, options, price, compare-at price, stock, SKU, barcode, weight, image |
| `vendor`, `productType`, `tags` | The store's own catalogue fields |
| `rank` | Position in the order you picked, per store or collection |
| `description` | The product description as text (first 1,000 characters) |
| `country` | The market the prices are for |
| `images` | Every product image (up to 10): `url`, `alt`, `width`, `height` |
| `videos` | Hosted videos (`type: video`, the tallest MP4) and YouTube or Vimeo links (`type: externalVideo`, with `host`), each with `previewImageUrl` |
| `descriptionHtml` | The description as the store's HTML (up to 20,000 characters) |
| `seoTitle`, `seoDescription` | The product's search-engine title and description, when the store set them |
| `sellingPlans`, `requiresSellingPlan` | Subscription plans (`group`, `name`, `recurring`), and whether the product can only be bought on one |
| `collections` | The collections the product is in (up to 10): `handle`, `title` |
| `recommendations` | With **Recommendations per product**: Shopify's recommended products, each with id, title, vendor, price, stock, link and image |

With Only changes, rows also carry `change` (and `changes` when there are several), `previousPrice`, `previousAvailable`, `changeAmount`, `changePercent`, `trackedPrices` (every price the tracker has seen, by date), `firstSeenDate` and `lastSeenDate`.

A store's `/collections` link gives one `collection` row per collection: `collectionId`, `handle`, `title`, `description`, `updatedAt`, `url` and `imageUrl`.

A store, collection or product the Actor can't read gets a free `notFound` row that says why.

### How to use it

#### Stores, collections and products

- **A store**: `allbirds.com`, `https://kith.com`, or a `.myshopify.com` address. The Actor follows the store to its own domain.
- **A collection**: `https://www.allbirds.com/collections/mens`. Only its products, in the collection's order when you pick Store order.
- **A product**: `https://www.allbirds.com/products/mens-wool-runners`, with all of its variants.
- **A store's collections**: `https://www.allbirds.com/collections`. One row per collection, to pick which ones to read next.

Several inputs share **Max results** evenly; a store with fewer products gives its unused share to the others. **Max products per store** caps how far each store is read (10,000 unless set).

#### Sort and country

**Sort by**: best selling (Shopify's own order), newest, price low to high, price high to low, title, or store order. **Country** takes a name or a two-letter code; the store's own currency is used when it's empty.

#### Filters

Keywords (title, vendor, type or tags hold every word), words to exclude, vendors, product types, tags, minimum and maximum price, in stock only, on sale only, and published within N days. Product types, stock and (without a country) prices are applied by Shopify itself; a product type the store doesn't have is named with the types it does have, before anything is read.

#### Product details and recommendations

**Product details** (on unless switched off) adds images, videos, the HTML description, SEO texts, subscription plans and collections. Shopify limits how much one request may ask for, so with details a request reads 60 products; without them, 250. **Recommendations per product** (0 to 10, off unless set) asks Shopify for each product's recommendations, one request per product, so it slows a run down: use it on the rows you need.

#### Switching from another Shopify scraper

Paste your existing input. These names are read as they are meant there: `startUrls` and `domains` (stores), `maxRequestsPerCrawl` (Max results, 0 for no limit), `maxProducts` (per store, 0 for the whole store), `maxRecommendationsPerProduct` (capped at Shopify's 10). Proxy settings aren't needed and are set aside with a note. Stock counts per variant (`grabRealInventory`, `includeInventoryDetails`) aren't available without the store's own token: Shopify answers "access denied" to everyone else, so rows say whether each variant can be bought instead.

### Tracking changes on a schedule (Shopify price tracker)

Switch on **Only changes** and schedule the Actor (daily is typical). The first run reads each store and saves it as the baseline: its rows come with `change: firstRun`. From then on, each run returns only what changed since the last one:

| Change | When |
|---|---|
| `newProduct` | A product that wasn't there last run (one that was only new to the read, such as a collection it joined, is recorded quietly) |
| `priceDrop`, `priceIncrease` | The lowest price moved by at least **Minimum price change (%)** (1% unless set) |
| `backInStock`, `soldOut` | It became available, or stopped being available |
| `onSale` | A compare-at price appeared |
| `removed` | The store no longer has it (checked by id when a store was read only in part) |

Tracking watches products: a `/collections` list isn't tracked, and recommendations are left out of tracked runs.

**Change types** limits the output to the changes you want; the others are still recorded. Each store, collection or product has its own history for each combination of settings (country, filters, sort), kept in the named store **History name** (`shopify-scraper-history` unless set). A run stopped by an abort, a timeout or your spending limit records what it delivered; the rest comes with the next run.

### Alerts in Slack, Discord or Microsoft Teams

Paste a webhook URL in **Alert webhook URL** (Slack incoming webhook, Discord webhook, Teams Workflows URL, or any HTTPS endpoint for JSON). The first run posts a short "alerts are set up" message; from the next run on, the changes picked in **Alert on** (new products, price drops and restocks unless set) are posted, at most 50 a run, most important first. Alerts switch Only changes on. The URL is treated as a secret and never written to the log.

### Run summary for pipelines

Each run saves `RUN_SUMMARY` in its key-value store: rows by type, changes, stores read (products, currency, in stock, on sale, median price), not found, not loaded, notes and the requests made.

### How much does it cost?

You pay **$1.00 per 1,000 rows** (products, variants with One row per variant, or collections), less on paid Apify plans, plus a start fee of $0.00005 per run. **Platform usage is included.** Not-found rows are free. With Only changes you pay only for the changes returned.

| Apify plan | Per 1,000 rows |
|---|---|
| Free | $1.00 |
| Starter | $0.90 |
| Scale | $0.80 |
| Business and above | $0.70 |

| Run | Rows | Cost (Free plan) |
|---|---|---|
| A store's 500 best sellers | 500 | about $0.50 |
| A 5,000-product catalogue | 5,000 | about $5.00 |
| 10 competitor stores tracked daily, about 30 changes a day | 30 a day | about $0.03 a day |

To cap spending, lower Max results or set a maximum cost per run in the run options; the Actor stops cleanly at the limit.

### Good to know

- **Speed**: one request a second per store, 60 products per request with Product details and 250 without: a 5,000-product store in about 90 seconds with details, 30 without. Stores are read one after another. Recommendations add one request per product.
- **Identified, not disguised**: requests carry this Actor's name in their User-Agent. Stores that turn away automated visitors (some put a browser check in front of a custom storefront) are named in the run summary and not worked around.
- **Headless stores**: a store whose shop runs on its own front end, with Shopify only at checkout, may not answer; it gets a `notFound` row that says so.
- **What isn't collected**: inventory counts (Shopify's public API answers "access denied" without the store's own token; rows say whether each variant can be bought), customer data and reviews.

### FAQ

**Can it track a Shopify competitor's prices?** Yes: put the store (or a collection) in, switch on Only changes and schedule it. Price drops and rises past your minimum %, restocks and new products come back once each.

**Does it work on any Shopify store?** On stores whose storefront answers Shopify's public Storefront API, which is nearly all of them. Stores with a separate front end or a bot check in front are named, not read.

**Can I get prices in another currency?** Yes, through Country: the prices are the ones the store charges shoppers in that country, in its currency.

**How do I scrape only one collection?** Paste the collection's link (`.../collections/<name>`). To see which collections a store has, paste `.../collections` first.

**Does it get product images and videos?** Yes, every image (up to 10 per product) with its size, and hosted videos as MP4 links, plus YouTube or Vimeo links.

**Is there a Shopify scraper API?** Yes: start a run through Apify's API (or the `run-sync-get-dataset-items` endpoint) with the same JSON input, and get the rows back as JSON, CSV or Excel.

**Is it legal?** It reads the product catalogue a store publishes to every shopper, through Shopify's public Storefront API, at a modest pace and without logging in. Use the data in line with each store's terms and the laws that apply to you.

### Other Actors by this developer

- [Google Trends Scraper & API - Trending Now, Pinterest Trends](https://apify.com/akhil_rajesh/google-trends-scraper): Trending Now in 125 countries, interest over time, by region and rising searches without a login or API key, plus Pinterest Trends and alerts for new trends
- [Amazon.ca Product & Trending Tracker](https://apify.com/akhil_rajesh/amazon-ca-product-trending-tracker): Amazon Best Sellers, prices, search positions and reviews on 23 marketplaces, with change tracking
- [AliExpress Price Tracker & Scraper](https://apify.com/akhil_rajesh/aliexpress-price-tracker): AliExpress search results with store prices, units sold and price history
- [Price Tracker Canada](https://apify.com/akhil_rajesh/bot-wall-price-scraper): one product's price at Best Buy, Walmart, Canada Computers and more
- [Google Ads Transparency Scraper & Monitor](https://apify.com/akhil_rajesh/google-ads-transparency-scraper): every ad a brand runs on Google, with ad copy, targeting and alerts for new and stopped ads
- [Contact Details Scraper](https://apify.com/akhil_rajesh/contact-details-scraper): emails, phone numbers, social profiles and contact pages from any list of websites

### Disclaimer

This Actor is an independent tool. It is not affiliated with, endorsed by or sponsored by Shopify Inc. or any store it reads. Use the data in line with each store's terms and the laws that apply to you.

# Changelog

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

# Actor input Schema

## `stores` (type: `array`):

Shopify stores (allbirds.com), collection links (allbirds.com/collections/mens) or product links (allbirds.com/products/mens-wool-runners). A store's /collections link (allbirds.com/collections) lists its collections, one row each. A store's own address or its .myshopify.com address both work. A link's language or country part (/en-ca/) is ignored: use Prices for country below. Up to 50.

## `maxResults` (type: `integer`):

Most rows a run returns, shared evenly between the stores and links. With Only changes: most changes a run returns (price drops first; the rest come with the next run).

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

Most products read from each store or collection. With Only changes, each store is read whole up to this number, so no change is missed. Leave empty for 10,000.

## `sortBy` (type: `string`):

The order products are read in, and so which ones a run returns first. Best selling is the store's own best-seller order.

## `country` (type: `string`):

Two-letter country code or name (CA, GB, Germany): prices and availability in that country's currency, as the store sells there. A store that doesn't sell in that country answers with its main market's prices, and the run says so. Leave empty for the store's main market.

## `oneRowPerVariant` (type: `boolean`):

A row for each variant (size, colour) with its own SKU, barcode, price and stock, instead of one row per product with its variants inside. Each variant row is a result.

## `productDetails` (type: `boolean`):

Every image (up to 10, with alt text and size), videos, the full description as HTML, the SEO title and description, subscription plans and the collections each product is in. Pages then hold 60 products instead of 250, so a big store takes a little longer. Switch off for lean rows.

## `recommendationsPerProduct` (type: `integer`):

The products the store itself recommends with each product (its "You may also like"), up to 10, inside the row. One extra request per product, so a run takes about a second longer per product; not charged extra. Not read with Only changes. Leave empty for none.

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

Only products whose title, vendor, type or tags hold one of these words or phrases. Leave empty for all.

## `excludeWords` (type: `array`):

Leave out products whose title, vendor, type or tags hold any of these (gift card, sample).

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

Only products by these vendors or brands, as the store names them. Leave empty for all.

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

Only products of these types, as the store names them (Shoes, Socks). Leave empty for all.

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

Only products with at least one of these tags. Leave empty for all.

## `minPrice` (type: `number`):

In the store's currency (the one for Prices for country). Leave empty or 0 for no minimum.

## `maxPrice` (type: `number`):

In the store's currency. Leave empty or 0 for no maximum.

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

Only products (or variants) that can be bought now.

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

Only products (or variants) with a compare-at price above the price.

## `publishedWithinDays` (type: `integer`):

Only products the store published within this many days. Leave empty or 0 for any.

## `onlyChanges` (type: `boolean`):

Track your stores: each run returns only what changed since the last run with the same stores and filters (new products, price drops and increases, back in stock, sold out, newly on sale, removed). The first run saves the products there now as the baseline and returns them as firstRun.

## `changeTypes` (type: `array`):

Which changes Only changes returns. Leave empty for all.

## `minChangePercent` (type: `number`):

Smaller price moves aren't reported, and the last reported price stays the baseline, so a slow slide still shows once it adds up. Leave empty for 1%.

## `alertWebhookUrl` (type: `string`):

Post each run's changes to Slack, Discord or Microsoft Teams (their incoming-webhook URL), or to any HTTPS endpoint that takes JSON (Zapier, Make, n8n). Switches on Only changes. The first run posts a short 'alerts are set up' message.

## `alertChangeTypes` (type: `array`):

Which changes are posted. Leave empty for new products, price drops and back in stock.

## `historyName` (type: `string`):

Keep a separate history for this tracker (a-z, 0-9 and hyphens). Leave empty to share the default one: histories are kept per store and filters anyway.

## Actor input object example

```json
{
  "stores": [
    "https://www.allbirds.com"
  ],
  "maxResults": 100,
  "sortBy": "bestSelling",
  "productDetails": true
}
```

# Actor output Schema

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

No description

## `changes` (type: `string`):

No description

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

No description

## `catalog` (type: `string`):

No description

## `details` (type: `string`):

No description

## `collectionList` (type: `string`):

No description

## `notFound` (type: `string`):

No description

## `allData` (type: `string`):

No description

## `runSummary` (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 = {
    "stores": [
        "https://www.allbirds.com"
    ],
    "maxResults": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("akhil_rajesh/shopify-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 = {
    "stores": ["https://www.allbirds.com"],
    "maxResults": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("akhil_rajesh/shopify-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 '{
  "stores": [
    "https://www.allbirds.com"
  ],
  "maxResults": 100
}' |
apify call akhil_rajesh/shopify-scraper --silent --output-dataset

```

## MCP server setup

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