Shopify Store Scraper: Products, Prices & Stock
Pricing
from $1.00 / 1,000 product scrapeds
Shopify Store Scraper: Products, Prices & Stock
Scrape any Shopify store's full catalog from public products.json: prices, compare-at, variants, SKUs, stock and inventory. Track new, removed and price changes between runs. WooCommerce too. $1 per 1,000 products.
Pricing
from $1.00 / 1,000 product scrapeds
Rating
0.0
(0)
Developer
Majd Hijazin
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
4 days ago
Last modified
Categories
Share
Shopify Store Scraper — products, prices, variants & stock
Shopify Store Scraper exports the full product catalog of any Shopify store to JSON, CSV or Excel. It reads the public products.json endpoint that every Shopify store exposes, so it needs no login, browser or proxy. You get prices, compare-at prices, every variant and SKU, stock availability and (where the store exposes it) inventory quantity.
Turn on change tracking and schedule the scraper. Each run then reports which products are new, removed, price changed or availability changed since the last run. That makes it a cheap Shopify price and stock monitor for competitor research, MAP monitoring, dropshipping and AI agents.
- 🛍️ Any Shopify store: paste a domain like
allbirds.com, a collection URL or product URLs. - 💲 Prices, discounts, variants and SKUs:
priceMin,priceMax,compareAtPrice,onSaleanddiscountPercent, plus avariants[]array with SKU, options, price, availability, barcode and weight. - 📦 Stock and inventory:
available, available-variant counts, and per-variantinventory_quantitywhen the store exposes it. - 🔁 Change tracking: new, removed, price_changed and availability_changed, including per-variant price and stock deltas.
- 🏪 Optional store profile: theme, currency, country, contact page, emails, social links, detected apps (Klaviyo, Judge.me, Recharge, Gorgias…) and the product count.
- 🧩 WooCommerce too: if a URL is not Shopify, the scraper tries the public WooCommerce Store API (
/wp-json/wc/store/v1/products). - 💵 $1 per 1,000 products ($0.001 each). No start fee, and platform usage is included.
What data can you extract from a Shopify store?
| Field | Example | Notes |
|---|---|---|
store, storeUrl | kith.com | Canonical host after redirects |
productId, handle, title, url | 8286105305216, bb-139855 | The ID is a string, so no 64-bit precision loss |
vendor, productType, tags[] | BAUBLEBAR, Toys & Collectibles | |
priceMin, priceMax, currency | 35, 35, USD | Currency comes from /meta.json or /cart.js |
compareAtPrice, onSale, discountPercent | 50, true, 50 | The highest compare-at price across variants |
available, variantCount, availableVariantCount | true, 7, 5 | |
totalInventory, variants[].inventoryQuantity | 172 | Needs includeInventory, or product URLs. Null when the store hides stock |
variants[] | id, sku, title, option1-3, price, compareAtPrice, available, barcode, grams | Can be turned off with includeVariants: false |
options[], images[], featuredImage | Size: S/M/L | |
createdAt, updatedAt, publishedAt | ISO 8601 | From Shopify |
changeType, changes[], variantChanges[], previousPriceMin, previousAvailable | price_changed | Only when trackChanges is on |
description | plain text | Opt-in with includeDescription |
scrapedAt | ISO 8601 |
The dataset uses one flat record per product. Store profiles (recordType: "store") and error records (recordType: "error") are in the same dataset. Use the Products, Changes since last run, Store profiles and Errors views to see each type.
How to scrape a Shopify store
- Paste one or more store URLs into Store URLs, e.g.
https://www.allbirds.com. - Optionally restrict the scrape with Collection URLs (
/collections/mens) or Product URLs (/products/tree-runner). - Set Max products per store, then click Start.
- Download the results as JSON, CSV, Excel or HTML, or read them through the API.
To build a Shopify price monitor, enable Track changes since the last run and Output only changed products. Then create a schedule (for example daily). The first run records a baseline. Later runs output and bill only the products that changed.
Input example
{"storeUrls": ["https://www.allbirds.com", "https://kith.com"],"collectionUrls": ["https://www.brooklinen.com/collections/sheets"],"productUrls": ["https://www.allbirds.com/products/mens-tree-runners"],"maxProductsPerStore": 500,"includeVariants": true,"includeInventory": false,"trackChanges": true,"onlyChanges": false,"analyzeStore": true}
| Option | Default | What it does |
|---|---|---|
storeUrls | — | Store home pages. The whole catalog is scraped via /products.json?limit=250&page=N |
collectionUrls | — | Uses /collections/<handle>/products.json |
productUrls | — | Uses /products/<handle>.js (includes inventory when it is exposed) |
maxProductsPerStore | 1000 | Hard cap per store, from 1 to 100,000 |
includeVariants | true | Adds the variants[] array |
includeInventory | false | Makes one extra .js request per product to read inventory_quantity. Slower, same price |
includeDescription | false | Adds the plain-text description |
trackChanges / onlyChanges | false | Diffs against the previous run, which is stored in the named key-value store stateStoreName |
analyzeStore | false | Adds one store profile per store (+$0.005) |
includeWooCommerce | true | Falls back to the WooCommerce Store API |
requestDelayMs | 500 | Polite delay between requests to the same store |
maxConcurrency | 3 | Number of different stores scraped in parallel |
Output example
{"recordType": "product","platform": "shopify","store": "kith.com","productId": "8286105305216","handle": "bb-139855","title": "BAUBLEBAR Kids DIY Bag Charm Craft Kit - Multi","vendor": "BAUBLEBAR","productType": "Toys & Collectibles","tags": ["accessories"],"priceMin": 35,"priceMax": 35,"compareAtPrice": null,"onSale": false,"currency": "USD","available": true,"variantCount": 1,"availableVariantCount": 1,"totalInventory": null,"sku": "10866430","variants": [{ "id": "45137916461184", "sku": "10866430", "title": "OS", "price": 35, "compareAtPrice": null, "available": true, "inventoryQuantity": null, "grams": 1361 }],"images": ["https://cdn.shopify.com/s/files/1/0094/2252/files/139855_G_01_SQ.jpg"],"url": "https://kith.com/products/bb-139855","createdAt": "2026-01-27T18:20:26-05:00","updatedAt": "2026-10-20T14:19:33-04:00","publishedAt": "2026-10-20T10:57:22-04:00","source": "products.json","changeType": "new","scrapedAt": "2026-10-20T18:19:32.058Z"}
Store profile (analyzeStore: true):
{"recordType": "store","store": "www.allbirds.com","storeName": "Allbirds","currency": "USD","country": "US","myshopifyDomain": "weareallbirds.myshopify.com","theme": { "name": "[DNAM Theme July 2026]", "schemaName": "allbirds-theme", "schemaVersion": "1.231.12", "themeStoreId": null },"contactPage": null,"emails": ["help@allbirds.com"],"socialLinks": { "instagram": "https://www.instagram.com/allbirds", "tiktok": "https://www.tiktok.com/@weareallbirds" },"detectedApps": ["Attentive", "Google Tag Manager", "Shop Pay", "Elevar"],"productCount": 692,"collectionCount": 1346}
A per-store run summary (status, counts, change totals, billing and HTTP stats) is saved as the RUN_SUMMARY record in the default key-value store.
Pricing
This Actor uses pay-per-event pricing, and Apify platform usage is included.
| Event | Price | When it is charged |
|---|---|---|
product-scraped | $0.001 ($1 per 1,000) | For each product record delivered |
store-analyzed | $0.005 | Once per store, only with analyzeStore: true and only when a profile was built |
You are not charged for error records, removed records, or (with onlyChanges) unchanged products. Examples: 1,000 products cost $1.00. A daily monitor of a 2,000-product store where about 50 products change per day costs about $0.05 per day after the first run.
The run respects your maximum charge per run (maxTotalChargeUsd). The scraper stops delivering once the budget is used up, and it never bills a product it did not deliver. If the budget cannot cover a single product, the run stops immediately with BUDGET_TOO_LOW.
API
Run the scraper and fetch the products in one call with the Apify API:
curl -X POST "https://api.apify.com/v2/acts/impressionable_lupine~shopify-store-scraper/run-sync-get-dataset-items?token=$APIFY_TOKEN" \-H "Content-Type: application/json" \-d '{"storeUrls":["https://www.allbirds.com"],"maxProductsPerStore":100}'
JavaScript (apify-client):
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: process.env.APIFY_TOKEN });const run = await client.actor('impressionable_lupine/shopify-store-scraper').call({storeUrls: ['https://kith.com'],maxProductsPerStore: 500,trackChanges: true,});const { items } = await client.dataset(run.defaultDatasetId).listItems({ view: 'overview' });console.log(items.filter(i => i.changeType === 'price_changed'));
Python (apify-client):
from apify_client import ApifyClientclient = ApifyClient("<APIFY_TOKEN>")run = client.actor("impressionable_lupine/shopify-store-scraper").call(run_input={"storeUrls": ["https://www.brooklinen.com"], "maxProductsPerStore": 200})for item in client.dataset(run["defaultDatasetId"]).iterate_items():print(item["title"], item["priceMin"], item["currency"], item["available"])
MCP (Claude, Cursor and other AI agents)
AI agents can call this scraper as a tool through the Apify MCP server. Add it to your MCP client:
{"mcpServers": {"apify": {"url": "https://mcp.apify.com/?actors=impressionable_lupine/shopify-store-scraper","headers": { "Authorization": "Bearer <APIFY_TOKEN>" }}}}
Then ask, for example: "Get the 50 newest products from allbirds.com with prices and stock", or "Which kith.com products changed price since the last run?". The flat output and the changeType field let an agent answer without extra parsing.
Integrations
Send the results to Google Sheets, Airtable, Slack, Zapier, Make, n8n or a webhook with Apify integrations. For example, post every price_changed product to Slack after each scheduled run.
Limitations
- Public catalog only. The scraper reads the same data as a store's visitors. It cannot see unpublished products, B2B or wholesale price lists, customer-specific pricing or cost of goods.
- Inventory quantity is optional per store. Many stores hide
inventory_quantity(thentotalInventoryisnull), and some cap or round it. Availability (available) is always present. - Prices are in the store's default currency and market. Shopify Markets prices for other countries are not fetched.
- Some stores block public catalog endpoints. Headless storefronts (Hydrogen, Next.js) may block
products.jsonon their public domain. The scraper then automatically tries the www/apex twin host and the store's*.myshopify.comdomain. If all of these are blocked, you get aPRODUCTS_JSON_DISABLEDerror record. Sites behind a JavaScript bot challenge returnBOT_CHALLENGE. Password-protected stores returnSTORE_PASSWORD_PROTECTED. No proxies or CAPTCHA solving are used. - WooCommerce support uses the public Store API. It gives prices, stock status, images and categories, but no variant-level prices and no created/updated dates. Collection and product URL scoping is Shopify-only.
- Removed-product detection needs a complete crawl. If a run is capped by
maxProductsPerStoreor by your budget, no product is reported as removed. - Very large catalogs are paginated 250 products at a time with a polite delay. A 10,000-product store takes about 40 requests, which is roughly 20–30 seconds.
FAQ
Is it legal to scrape Shopify stores? The scraper only reads public, unauthenticated catalog data that the store itself publishes as JSON, at a polite rate. You are responsible for how you use the data. Do not republish copyrighted descriptions or images without permission, and check the store's terms and your local law.
Do I need a Shopify API key or the store owner's permission to access products.json?
No. products.json is a public endpoint on every Shopify storefront. The Shopify Admin API (with inventory levels, orders and costs) is only available to the store owner and is not used.
How do I know whether a site is a Shopify store?
Just paste it. The scraper detects Shopify automatically and returns a NOT_SHOPIFY error record (free) for other sites. With includeWooCommerce on, it also scrapes WooCommerce stores.
How do I monitor competitor prices on Shopify?
Enable trackChanges and onlyChanges, then schedule the Actor. Each run outputs only new, removed, price-changed and availability-changed products, with the previous price and stock.
Can I get inventory or stock levels?
Yes, where the store exposes them. Enable includeInventory (or pass product URLs) to read inventory_quantity per variant from /products/<handle>.js. available and availableVariantCount are always included.
How many products can I scrape?
Up to 100,000 per store per run. Use maxProductsPerStore to keep test runs small and cheap.
Why do I see records with recordType: "error"?
Each input URL that could not be scraped (not Shopify, password page, missing collection or product, rate limit) produces one free error record. This way you always know why a store returned no products. The run itself still succeeds.
Does it use proxies or a browser?
No. It makes plain HTTPS JSON requests, one at a time per store, and backs off on HTTP 429 using Retry-After. That keeps it fast, cheap and reliable.