Shopify Products Scraper + Price & Stock Change Monitor avatar

Shopify Products Scraper + Price & Stock Change Monitor

Pricing

from $0.40 / 1,000 results

Go to Apify Store
Shopify Products Scraper + Price & Stock Change Monitor

Shopify Products Scraper + Price & Stock Change Monitor

Full product catalogs from any Shopify store via its public products.json, plus a changes mode that reports new, removed, price-dropped and back-in-stock items since the last run. Blocked or non-Shopify stores are reported, never charged.

Pricing

from $0.40 / 1,000 results

Rating

0.0

(0)

Developer

signal clear

signal clear

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

10 hours ago

Last modified

Categories

Share

Scrape the full product catalog of any Shopify store: titles, vendors, tags, images, prices, compare-at (was) prices, every variant with SKU and stock status. Or switch to changes mode and get only what moved since your last run: new products, removed products, price drops, price increases, back in stock and out of stock. Built for Black Friday and Cyber Monday price and stock monitoring.

  • Changes since the last run. Schedule it hourly or daily and receive only the changes, not the whole catalog again.
  • Blocked and non-Shopify stores are reported, never charged. Each store is collected completely before anything is saved. A store that fails is listed in the FAILED_STORES record with the reason, and none of its rows reach your dataset.
  • Domains, collections or single products. Paste example-store.com, a /collections/... URL or a /products/... URL.
  • Light and fast. It reads the catalog JSON that every Shopify storefront publishes (250 products per request), so it needs no browser and no residential proxy.

How it works

Shopify storefronts publish their public catalog at https://<store>/products.json, 250 products per page. The Actor pages through it (or through /collections/<handle>/products.json, or /products/<handle>.json for one product), reads the store's currency from /meta.json, and normalizes every product.

Each store is fetched with bounded retries. Every attempt uses a fresh proxy session and resumes at the page where the previous attempt stopped. A 429 rate limit is honored (Retry-After, capped at 30 seconds) before the next attempt. If www. is refused before any page loaded, the next attempt tries the bare domain (or the reverse).

Store answersStatusWhat happens
Catalog JSONokRows saved
Catalog JSON with zero productsemptySuccess, zero rows
403, 503, other 5xx, bot challenge, repeated 429blockedRetried up to maxAttempts, then listed in FAILED_STORES
Password-protected storefrontblockedListed at once (retrying will not help)
404, non-JSON page, JSON without productsnot_shopifyListed at once, no retries
DNS or connection failureunreachableRetried, then listed

The run fails (exit code 1) only when every store failed. Otherwise it succeeds with a summary such as 2400 products from 3 stores saved, 1 stores failed (not charged; see FAILED_STORES).

Mode 1: snapshot (default)

One row per product (or per variant), the whole catalog or up to maxProductsPerStore.

{
"stores": ["example-store.com", "https://another-store.com/collections/sale"],
"maxProductsPerStore": 0,
"includeVariants": "nested",
"onlyAvailable": true
}

Mode 2: changes

{
"stores": ["example-store.com"],
"mode": "changes",
"maxProductsPerStore": 0,
"snapshotStoreName": "shopify-snapshots"
}
  1. First run for a store: the Actor reads the catalog, saves it as a snapshot in the named key-value store shopify-snapshots (key = the sanitized domain, for example example-store-com) and pushes nothing. The log says so, and nothing is charged.
  2. Every later run: it reads the catalog again, compares it with the saved snapshot, pushes one row per change, then overwrites the snapshot.

Change types: new, removed, price_drop, price_increase, back_in_stock, out_of_stock. Price and stock are compared per variant, so a size going out of stock is reported even when other sizes are still available. A whole new or removed product gives one row with variantId: null; a new or removed variant of an existing product gives a row with its variantId.

Schedule the same input on the Apify scheduler and connect a webhook or integration to get alerts only when something changed.

Input

FieldDefaultNotes
stores(required)Domains, store URLs, collection URLs or product URLs
modesnapshotsnapshot or changes
maxProductsPerStore10000 = whole catalog. Counts products that pass your filters
includeVariantsnestednested: variants array per product. rows: one row per variant
onlyAvailablefalseSnapshot mode only
updatedSince(none)ISO date such as 2026-11-01; uses the product's updated_at. Snapshot mode only
tags(none)Keep products with any of these tags (case-insensitive)
vendor(none)Keep products from these vendors (case-insensitive)
includeDescriptionfalseAdds bodyHtml
snapshotStoreNameshopify-snapshotsNamed key-value store for changes mode
includeStoreStatusfalseAdds one store_status row per store
maxItems(none)Total row cap for the run
maxConcurrency4Stores fetched in parallel
maxAttempts31 to 5, each on a fresh proxy session
requestTimeoutSecs30Per request
proxyConfigurationApify proxyDatacenter is enough; groups and country are configurable

Output

Snapshot row (includeVariants: "nested", shortened):

{
"store": "example-store.com",
"productId": 7001234567890,
"handle": "classic-runner-navy",
"title": "Classic Runner - Navy",
"vendor": "Example Store",
"productType": "Shoes",
"tags": ["mens", "running", "sale"],
"createdAt": "2026-03-27T14:03:50-07:00",
"updatedAt": "2026-10-28T11:26:43-07:00",
"publishedAt": "2026-09-25T16:58:13-07:00",
"url": "https://example-store.com/products/classic-runner-navy",
"imageUrl": "https://cdn.shopify.com/s/files/1/0000/0000/files/runner-navy.jpg",
"images": ["https://cdn.shopify.com/s/files/1/0000/0000/files/runner-navy.jpg"],
"priceMin": 79.0,
"priceMax": 89.0,
"compareAtPriceMin": 110.0,
"compareAtPriceMax": 110.0,
"currency": "USD",
"available": true,
"variantCount": 2,
"variants": [
{"id": 41000000000001, "title": "9", "sku": "CR-NAV-9", "price": 79.0, "compareAtPrice": 110.0,
"available": true, "option1": "9", "option2": null, "option3": null, "grams": 455, "requiresShipping": true},
{"id": 41000000000002, "title": "10", "sku": "CR-NAV-10", "price": 89.0, "compareAtPrice": 110.0,
"available": false, "option1": "10", "option2": null, "option3": null, "grams": 470, "requiresShipping": true}
],
"scrapedAt": "2026-11-27T08:00:00+00:00"
}

With includeVariants: "rows" each variant becomes its own row with the product fields repeated, plus variantId, variantTitle, sku, price, compareAtPrice, available (the variant's), productAvailable, option1..option3, grams, requiresShipping.

Change row:

{
"type": "price_drop",
"store": "example-store.com",
"productId": 7001234567890,
"variantId": 41000000000001,
"handle": "classic-runner-navy",
"title": "Classic Runner - Navy",
"variantTitle": "9",
"url": "https://example-store.com/products/classic-runner-navy",
"priceBefore": 110.0,
"priceAfter": 79.0,
"availableBefore": true,
"availableAfter": true,
"currency": "USD",
"detectedAt": "2026-11-27T08:00:00+00:00"
}

Store status row (only with includeStoreStatus: true):

{"type": "store_status", "store": "example-store.com", "input": "example-store.com", "status": "ok",
"productCount": 1064, "pages": 5, "error": null, "scrapedAt": "2026-11-27T08:00:00+00:00"}

Failures are always written to the FAILED_STORES record of the run's default key-value store as {store, input, status, error, attempts}.

The dataset has three views: Products, Changes and Store status.

Limits

  • Stores can turn the catalog JSON off or put it behind bot protection. Those stores are reported as blocked or not_shopify and not charged; there is nothing to bypass.
  • Prices are in the store's own currency (from /meta.json, null if the store does not publish it). Stores with several markets show the default market's prices.
  • Changes mode needs the named key-value store to persist between runs; use the same snapshotStoreName every time. Snapshots are kept per store (per collection or product for those inputs), so comparisons are always within one store.
  • Removals are only reported when both runs read the whole catalog. With a maxProductsPerStore cap, products past the cap are not reported as removed. Use 0 for complete tracking.
  • Changing the tags or vendor filters starts a fresh baseline for that store (no rows that run), because the old snapshot tracked a different set of products.
  • onlyAvailable and updatedSince are ignored in changes mode: stock and dates are what it tracks.
  • Only published products visible on the storefront are listed. Inventory quantities are not part of the public catalog.

Pricing

$0.40 per 1,000 results. A result is a dataset row: a product (or variant row) in snapshot mode, a change row in changes mode. Baseline runs push nothing and are not charged. Failed stores push nothing and are not charged. Optional store_status rows are dataset rows too, so they count as results when you turn them on.

This Actor reads the public catalog JSON that Shopify storefronts serve by design to any visitor. It collects product data only: no personal data, no customer data, no login. You are responsible for using the data in line with the store's terms and the laws that apply to you.