Shopify Product Price & Catalog Scraper
Pricing
from $2.50 / 1,000 results
Shopify Product Price & Catalog Scraper
Product and variant data from any Shopify store via the public /products.json endpoint. HTTP only, no browser, no proxy. Returns one row per variant (SKU) with its own price, compare-at price and availability — not one row per product.
Pricing
from $2.50 / 1,000 results
Rating
0.0
(0)
Developer
ABDULWAHAB NASER RASHED ALQARAWI
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
2 days ago
Last modified
Categories
Share
Shopify Catalog & Price Scraper
Product and price data from any Shopify store, straight from the store's own public /products.json endpoint.
No browser. No proxy. No API key. Just clean rows.
What you get
One row per variant (SKU) — because a size-8 shoe and a size-11 shoe have different prices and different stock, and price monitoring that collapses them is useless.
| Field | Example |
|---|---|
store | allbirds.com |
title | Men's Strider Explore |
vendor | Allbirds |
variantTitle | 8 |
sku | A11768M080 |
price | 130.0 |
compareAtPrice | 165.0 |
onSale | true |
discountPct | 21.21 |
available | false |
productUrl | https://allbirds.com/products/... |
Plus productId, handle, productType, tags, publishedAt, updatedAt, imageUrl, optionNames, variantCount, variantId, grams, currency, scrapedAt — and, when onlyChanges is on, changeType, previousPrice, previousCompareAtPrice, previousAvailable (always present, null otherwise).
28 fields. Every one always present, and always its declared type — missing or malformed data comes back as null, never dropped and never passed through in the wrong type (a numeric SKU arrives as a string, a NaN price as null). Pipe it straight into a database without defensive checks.
Use it for
- Competitor price monitoring — schedule it daily, diff the
pricecolumn - Sale and discount tracking — filter
onSale = true, sort bydiscountPct - Stock-out alerts — watch
availableflip tofalse - Catalog research — brands, product types, tags across a whole market
- Feeding an LLM or agent — the output is already flat, typed and clean
Input
{"storeDomains": ["allbirds.com", "kith.com", "deathwishcoffee.com"],"maxProductsPerStore": 250,"rowsPerVariant": true,"respectRobots": true,"requestDelayMs": 400}
| Option | Default | What it does |
|---|---|---|
storeDomains | — | Any Shopify store, with or without https:// |
maxProductsPerStore | 60 | Stop after N products per store |
rowsPerVariant | true | Off = one row per product instead of per SKU |
respectRobots | true | Skips any store whose robots.txt disallows the endpoint |
requestDelayMs | 400 | Politeness delay. Lower it and stores start rate-limiting you |
onlyChanges | false | Return only variants that are new or whose price / compare-at price / stock changed since the last run |
emitRemoved | false | With onlyChanges: also return a REMOVED row for each known variant no longer in the store (see below) |
stateStoreName | shopify-catalog-state | Named key-value store that holds the snapshot for onlyChanges |
Only what changed (onlyChanges)
For daily price or stock monitoring you usually do not want the whole catalog again — you want what moved. Turn on onlyChanges and schedule the Actor:
- The first run returns every variant with
changeType: "NEW"and saves a snapshot per store. - Later runs return only variants that are
NEW, orUPDATEDbecauseprice,compareAtPriceoravailablechanged — withpreviousPrice,previousCompareAtPriceandpreviousAvailablealongside. Unchanged variants are not returned, so they are not charged.
The snapshot is kept in a named key-value store in your account (stateStoreName), one record per store domain. It is saved only when a store was read to the end (catalog exhausted or maxProductsPerStore reached) — a store cut off by your spending limit or a failed request keeps its previous snapshot, so nothing is marked as seen that you did not receive.
Keep maxProductsPerStore and rowsPerVariant the same between runs: products beyond the limit are not compared, and switching rowsPerVariant makes every row NEW once. Products removed from a store are not reported unless emitRemoved is on.
Removed products (emitRemoved, off by default)
With onlyChanges and emitRemoved on, a variant (or, with rowsPerVariant off, a product) that was in the store and is gone comes back as a row with changeType: "REMOVED": its ids, handle, title, variant title and SKU as last seen, and its last previousPrice / previousCompareAtPrice / previousAvailable. A removal row is a delivered row and is charged like any other.
A removal is reported only when both the recorded baseline and this run read the whole catalog: every page up to a short last page, no failed request, no unreadable or repeated product (a store that ignores page= never counts as fully read), not stopped by maxProductsPerStore, the 200-page guard or your spending limit — and with the same store and rowsPerVariant setting. So the first run with emitRemoved on, and the first run after changing rowsPerVariant, only record a baseline. A removed variant is forgotten only once its row was delivered; if the spending limit stops the run first, the rest are reported next run. The run report lists removedRows, and under removalNotes why a store was not checked.
Measured locally, 14 Sep 2026, allbirds.com + deathwishcoffee.com, 5 products each: first run 73 rows, all NEW; second run minutes later 0 rows.
Speed and cost
Measured locally, 14 Sep 2026, four stores, 5 products each:
108 rows · 12 requests · 7.7 seconds · 1 of 4 stores blocked (shop.tesla.com)
That is three requests per store (robots.txt, meta.json, one page of products). The endpoint returns up to 250 products per request and each product carries its variants, so one request can yield thousands of rows. Paging stops at the first short page, and a product id already delivered in the run is never delivered again — even from a store that ignores the page number.
Honest notes
Currency comes from the store, not a guess. /products.json carries no currency code, so the Actor makes one extra request per store to its public /meta.json and takes the 3-letter code from there (e.g. USD). If that request fails or returns anything that is not a 3-letter code, currency is null rather than guessed.
Not every store answers. Some sit behind a WAF and return 403, some rate-limit with 429. The Actor records both in its run report instead of failing silently — check blocked and rateLimited, and reachRate for the share of stores that responded.
One malformed product does not fail the run. A product the Actor cannot read is skipped and counted in the run report as skippedProducts; a store whose endpoint answers something other than a product list is listed under blocked.
Every run publishes a report to the key-value store under RUN_REPORT: requests, rows, megabytes, reach rate, skipped products, and the estimated cost per 1,000 rows. The run's status message always summarises it, e.g. 108 row(s) from 3 of 4 store(s); blocked: shop.tesla.com.
Public data only. The Actor reads robots.txt before it reads anything else, and skips stores that disallow the endpoint. Reviewing a target store's own terms of use is still your call.
Questions or a store that misbehaves?
Open an issue on the Actor page. Include the store domain — most problems are a single store's WAF, and they are usually quick to diagnose.