Shopify Product Price & Catalog Scraper avatar

Shopify Product Price & Catalog Scraper

Pricing

from $2.50 / 1,000 results

Go to Apify Store
Shopify Product Price & Catalog Scraper

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

ABDULWAHAB NASER RASHED ALQARAWI

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 days ago

Last modified

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.

FieldExample
storeallbirds.com
titleMen's Strider Explore
vendorAllbirds
variantTitle8
skuA11768M080
price130.0
compareAtPrice165.0
onSaletrue
discountPct21.21
availablefalse
productUrlhttps://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 price column
  • Sale and discount tracking — filter onSale = true, sort by discountPct
  • Stock-out alerts — watch available flip to false
  • 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
}
OptionDefaultWhat it does
storeDomains—Any Shopify store, with or without https://
maxProductsPerStore60Stop after N products per store
rowsPerVarianttrueOff = one row per product instead of per SKU
respectRobotstrueSkips any store whose robots.txt disallows the endpoint
requestDelayMs400Politeness delay. Lower it and stores start rate-limiting you
onlyChangesfalseReturn only variants that are new or whose price / compare-at price / stock changed since the last run
emitRemovedfalseWith onlyChanges: also return a REMOVED row for each known variant no longer in the store (see below)
stateStoreNameshopify-catalog-stateNamed 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, or UPDATED because price, compareAtPrice or available changed — with previousPrice, previousCompareAtPrice and previousAvailable alongside. 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.