Shopify Products Scraper + Price & Stock Change Monitor
Pricing
from $0.40 / 1,000 results
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
Maintained by CommunityActor 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_STORESrecord 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 answers | Status | What happens |
|---|---|---|
| Catalog JSON | ok | Rows saved |
| Catalog JSON with zero products | empty | Success, zero rows |
| 403, 503, other 5xx, bot challenge, repeated 429 | blocked | Retried up to maxAttempts, then listed in FAILED_STORES |
| Password-protected storefront | blocked | Listed at once (retrying will not help) |
| 404, non-JSON page, JSON without products | not_shopify | Listed at once, no retries |
| DNS or connection failure | unreachable | Retried, 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"}
- 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 exampleexample-store-com) and pushes nothing. The log says so, and nothing is charged. - 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
| Field | Default | Notes |
|---|---|---|
stores | (required) | Domains, store URLs, collection URLs or product URLs |
mode | snapshot | snapshot or changes |
maxProductsPerStore | 1000 | 0 = whole catalog. Counts products that pass your filters |
includeVariants | nested | nested: variants array per product. rows: one row per variant |
onlyAvailable | false | Snapshot 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) |
includeDescription | false | Adds bodyHtml |
snapshotStoreName | shopify-snapshots | Named key-value store for changes mode |
includeStoreStatus | false | Adds one store_status row per store |
maxItems | (none) | Total row cap for the run |
maxConcurrency | 4 | Stores fetched in parallel |
maxAttempts | 3 | 1 to 5, each on a fresh proxy session |
requestTimeoutSecs | 30 | Per request |
proxyConfiguration | Apify proxy | Datacenter 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
blockedornot_shopifyand not charged; there is nothing to bypass. - Prices are in the store's own currency (from
/meta.json,nullif 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
snapshotStoreNameevery 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
maxProductsPerStorecap, products past the cap are not reported as removed. Use0for complete tracking. - Changing the
tagsorvendorfilters starts a fresh baseline for that store (no rows that run), because the old snapshot tracked a different set of products. onlyAvailableandupdatedSinceare 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.
Legal
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.