Shopify Product Scraper & Shopify Price Monitor avatar

Shopify Product Scraper & Shopify Price Monitor

Pricing

from $0.80 / 1,000 products

Go to Apify Store
Shopify Product Scraper & Shopify Price Monitor

Shopify Product Scraper & Shopify Price Monitor

Every product of any Shopify store from its public products.json: variants, SKUs, prices, stock, images. Shopify price monitor with price-drop and back-in-stock alerts. $1/1k products, no start fee.

Pricing

from $0.80 / 1,000 products

Rating

0.0

(0)

Developer

Yukai Lin

Yukai Lin

Maintained by Community

Actor stats

0

Bookmarked

1

Total users

1

Monthly active users

6 days ago

Last modified

Share

What does Shopify Product Scraper & Price Monitor do?

It returns every product of any Shopify store from the store's own public product feed (/products.json), with all variants, SKUs, prices, compare-at prices, stock status and images. Paste store URLs or domains, collection URLs or product URLs; get one clean row per product (or per variant). It can also tell you whether a site is a Shopify store and watch stores for price and stock changes.

  • 🛍️ The store's own JSON, not page scraping: /products.json, /collections/<handle>/products.json, /collections.json and /meta.json are public endpoints most Shopify storefronts serve (a few turn them off, see below). No login, no browser, no bot-check bypassing
  • 🔗 Any input: https://www.allbirds.com, kith.com, https://kith.com/collections/mens-footwear, https://store.com/products/handle, name.myshopify.com, market paths like /en-gb
  • 🧾 Clean product rows: title, handle, url, vendor, productType, tags, price (lowest variant), priceMax, compareAtPrice, onSale, discountPercent, currency, available, variantsAvailable/variantsCount, skus, options, images, description (plain text), publishedAt, createdAt, updatedAt, and variants (SKU, price, compare-at, availability, options, weight)
  • 📊 One row per product or per variant (size/colour rows for spreadsheets). You pay once per product either way
  • 💱 Currency included: products.json has no currency field; the Actor reads it from the store page (the currency the store serves, which follows Shopify Markets) and from meta.json (the store's default currency)
  • 📉 Price and stock monitoring: only new and changed products since the last run, with changes (price_drop, price_increase, back_in_stock, out_of_stock, compare_at_price, variant_added, variant_removed), previous values per variant, and free rows for removed products
  • 🔎 "Is it a Shopify store?": one row per site with store name, myshopify domain, currency, country, product and collection counts; sites that are not Shopify stores are free
  • 🎯 Filters before charging: keywords, excluded words, vendors, product types, tags, price range, in stock only, on sale only, new since a date, chosen collections
  • 🤖 Polite by design: robots.txt is checked before reading the catalog (a disallowed products.json is skipped), one request per second per store by default, rate limits respected
  • 💵 $1 per 1,000 products (all variants included), $0.30 per 1,000 collections, $1 per 1,000 Shopify stores detected. No start fee

Shopify price monitor: daily price and stock alerts

Watch competitor stores for price drops, price increases, discounts and stock changes in three steps:

  1. Paste the stores, turn on Only new and changed products (onlyChanges) and give the monitor a name (monitorName).
  2. Run it once: this first run returns the products and saves them as the baseline.
  3. Create a daily schedule in Apify Console (Schedules). Each later run returns only new and changed products with their changes and previous values, so you pay only for what changed. Add a Slack, email or webhook integration to get the changes as alerts.
{
"stores": ["https://www.allbirds.com"],
"onlyChanges": true,
"monitorName": "allbirds-daily",
"onlyOnSale": false
}

Price monitor or one-off scrape? For a full product export (a catalog, SKUs, a competitor's whole range), leave Only new and changed products off. For daily or weekly competitor price tracking, turn it on: unchanged products are free, removed products get a free row, and the monitoring guard stops a store from being charged when most of its catalog changes at once.

Where the data comes from, and the limits

DataStore endpointLimit
Products and variants/products.json?limit=250&page=N or /collections/<handle>/products.json250 products per request; Shopify serves at most 100 pages (25,000 products) per catalog or collection
One product/products/<handle>.json—
Collections/collections.json250 per request; first Max collections per store (default 1,000)
Store facts/meta.json and the home pageName, myshopify domain, default currency, country, product and collection counts

What this means in practice:

  • Some stores turn the feed off. Headless storefronts (Hydrogen, e.g. fashionnova.com in our tests) and a few stores that block data-center networks answer 404 or 403. They get a free error row; store detection still recognises them as Shopify.
  • Prices are in the currency the store serves to the requester. Stores using Shopify Markets convert prices by visitor country. Runs on Apify come from the US, so such stores usually answer in USD; currency says which currency the numbers are in, and storeCurrency shows the store's default currency when it differs.
  • Market paths (/en-gb) are optional. Many stores serve their data only at the root and answer 404 under a market path; then the Actor reads the store's main catalog instead (in the currency the store shows this network) and says so in SUMMARY.perStore[].note.
  • Inventory quantities are not public. products.json says whether each variant is available, not how many units are left.
  • Order: products come in the store's own order. Max products per store stops early on big catalogs; the run says so in SUMMARY.perStore[].note. When a catalog is cut by Shopify's own 25,000-product limit, the note says so, catalogCapReached is true and the log identifies the affected input number.

Who is it for?

  • E-commerce brands and agencies: track competitors' catalogs, prices, discounts and new arrivals
  • Price monitoring: daily alerts for price drops, sales and back-in-stock variants
  • Dropshippers and resellers: export full catalogs with SKUs, variants and images to spreadsheets
  • Market research and lead lists: check which sites run on Shopify and how large their catalogs are
  • AI agents and analysts: structured product data for comparisons and summaries

How much does it cost?

Price per 1,000 events by Apify plan:

EventFreeStarterScaleBusiness and higher
Product (with all its variants)$1.00$1.00$0.90$0.80
Collection (collections mode)$0.30$0.30$0.27$0.24
Shopify store detected (detection mode)$1.00$1.00$0.90$0.80

No start fee. Not charged: products removed by your filters, unchanged products in monitoring mode, removed-product rows, suspicious change sets in monitoring mode (see the monitoring guard below), sites that are not Shopify stores (including sites that only load files from Shopify's CDN, and *.myshopify.com addresses that do not exist), password-protected stores in products and collections mode, stores without a public catalog, catalogs disallowed by robots.txt, sites that cannot be reached, and invalid input lines (including private and local network addresses). Detection mode charges any real Shopify store, also a password-protected or headless one (its row says passwordProtected and productsJsonAvailable).

For comparison (checked September 2026, Apify Store, Free plan prices): trovevault/shopify-products-scraper charges $0.001 per product plus $0.001 per run; autofacts/shopify $0.0018 per product and $0.0008 per collection; webdatalabs/shopify-product-scraper $0.01 per product; clearpath/shop-by-shopify-product-scraper $0.00249 per product; thirdwatch/shopify-store-scraper $0.002 per product; makework36/shopify-products-scraper $0.001 per product; ghostgrid/shopify-store-scraper $0.0008 per product plus $0.0012 per change and $0.004 per run. A few are cheaper per row (sleek_waveform/universal-shopify-scraper $0.0005) without monitoring or store detection.

Control your cost

  • What is charged: each product returned (once, whatever the number of variants or variant rows), each collection in collections mode, each Shopify store in detection mode.
  • Max products per store (default 1,000) and Max products for the whole run (default 10,000: at most $8 on the Free plan) cap the charge. In collections mode Max collections per store (default 1,000) does the same: some stores list thousands of collections (Kith has more than 3,000), each billed.
  • Filters are applied before charging, so "on sale, under $50, in stock" costs only for those products.
  • Only new and changed products makes daily monitoring cheap: after the first run you pay only for products that changed.
  • At the start, the run logs its worst case (e.g. Plan: 2100 products × $0.001 = at most $2.10) and warns when that is more than your maximum charge per run.
  • When the maximum charge per run is reached, the run stops, keeps everything found so far, and says so. The SUMMARY record then has status: "LIMIT_REACHED" and notProcessed (up to 100 stores not processed).

Worked example (real run, 30 September 2026): allbirds.com, gymshark.com, a Kith collection, colourpop.com, one Tentree product URL, fashionnova.com (headless, no feed) and example.com (not Shopify), 300 products per store: 1,201 products from 5 stores in 12.6 seconds, 2 free error rows. Cost on the Free plan: 1,201 × $0.001 = $1.20. The same stores with Only new and changed products on the next day cost only for the products whose price or stock changed.

How to use it

  1. Enter your stores, collections or products, one per line.
  2. Choose What to return: products, "Is it a Shopify store?", or collections.
  3. Optional: One row per variant, Max products per store, filters, and Only new and changed products (then schedule the Actor daily).
  4. Click Start. The Products, Price and stock changes, Store detection and Collections tables are in the Output tab; export as CSV, Excel or JSON, or call the Actor from the API.

Input example

{
"stores": ["https://www.allbirds.com", "gymshark.com", "https://kith.com/collections/mens-footwear"],
"maxProductsPerStore": 300,
"onlyOnSale": true
}

Daily price and stock monitor:

{ "stores": ["https://www.tentree.com", "https://colourpop.com"], "onlyChanges": true, "monitorName": "competitors" }

Which of these sites are Shopify stores?

{ "stores": ["allbirds.com", "gymshark.com", "kith.com", "example.com", "wikipedia.org"], "mode": "detect" }

Output example: product

{
"type": "product",
"input": "https://www.allbirds.com",
"store": "https://www.allbirds.com",
"storeName": "Allbirds",
"productId": 7340901859408,
"title": "Women's Allbirds Flip Flop - Dusty Pink",
"handle": "womens-allbirds-flip-flop-dusty-pink",
"url": "https://www.allbirds.com/products/womens-allbirds-flip-flop-dusty-pink",
"vendor": "Allbirds",
"productType": "Shoes",
"price": 25,
"priceMax": 25,
"compareAtPrice": 50,
"onSale": true,
"discountPercent": 50,
"currency": "USD",
"currencySource": "store page",
"available": true,
"variantsCount": 7,
"variantsAvailable": 1,
"skus": ["A12513W050", "A12513W060", "A12513W070", "A12513W080", "A12513W090", "A12513W100", "A12513W110"],
"options": [{ "name": "Size", "values": ["5", "6", "7", "8", "9", "10", "11"] }],
"imageUrl": "https://cdn.shopify.com/s/files/1/1104/4168/files/A12513_26Q2_Allbirds-Flip-Flop-Dusty-Pink_PDP_LEFT.png?v=1774646345",
"imagesCount": 5,
"description": "Sun on your feet. Comfort underneath. Light, easy, and made for warm weather, these flip flops bring everyday comfort to sunny days, beach walks, and everything in between. Just slip them on and go.",
"publishedAt": "2026-09-25T23:58:13.000Z",
"updatedAt": "2026-09-30T09:33:49.000Z",
"variants": [
{ "variantId": 42146889039952, "title": "5", "sku": "A12513W050", "price": 25, "compareAtPrice": 50, "available": false, "option1": "5", "grams": 455 }
],
"charged": true
}

Output example: price change (monitoring)

{
"type": "product",
"title": "Women's Allbirds Flip Flop - Dusty Pink",
"price": 20,
"changeType": "changed",
"changes": ["price_drop", "back_in_stock"],
"previousPrice": 25,
"previousAvailable": true,
"variantChanges": [
{ "variantId": 42146889039952, "title": "5", "change": "price,available", "price": 20, "previousPrice": 25, "available": true, "previousAvailable": false }
],
"charged": true
}

(Shape from the unit tests; in the real test runs, a second run of the Tentree monitor two minutes later returned 0 changed products and cost nothing.)

Output example: store detection

{ "type": "store", "store": "https://gymshark.com", "isShopify": true, "detectedBy": ["meta.json", "powered-by header", "Shopify theme script", "products.json"], "storeName": "Gymshark US", "myshopifyDomain": "gymsharkusa.myshopify.com", "currency": "USD", "country": "US", "productsCount": 10088, "collectionsCount": 526, "productsJsonAvailable": true, "robotsAllowsProductsJson": true, "charged": true }

Errors (not charged)

{ "type": "store", "input": null, "inputType": "string", "inputIndex": 0, "store": null, "success": false, "errorType": "not_found", "error": "no Shopify store answers or requested resource was not found (not charged)", "charged": false, "isShopify": false }

inputIndex is zero-based and points to the original item in Stores (0 is the first item). If one item contains several addresses, inputPartIndex is the zero-based position of the address within that item. If the input also has a storesText string, it follows the stores array at index stores.length. Use these positions to find the original input in your run; free error rows do not repeat the pasted URL or raw HTTP details.

errorType is invalid_input (a line that is not a URL or domain, or a private/local network address), not_shopify (the site is not a Shopify store), password_protected (the store is behind its storefront password: HTTP 401 or a redirect to /password), not_found (no public catalog, collection or product, or the domain does not exist), robots_disallowed (robots.txt disallows the endpoint, so it is skipped), blocked (the store kept refusing requests), network/timeout.

The SUMMARY record (key-value store) has status (SUCCESS, PARTIAL_RESULTS, FAILED, NO_RESULTS, LIMIT_REACHED), totals and perStore (Shopify signals found, currency, products read, matched, returned, unchanged, removed, changePercent, and a note such as "stopped at Max products per store"). In monitoring mode it also has storesWithSuspiciousChanges and suspiciousStores (input position, change percentage, whether the baseline was updated, and whether output was free). It does not include raw reasons.

Integrations

  • Schedules: run daily with Only new and changed products for price-drop and back-in-stock alerts.
  • API and webhooks: start runs and read the dataset from any language; a webhook tells you when a run finishes.
  • Exports: CSV, Excel, JSON, XML and HTML from the dataset (use One row per: Variant for flat spreadsheets).
  • Ready-made views in the Output tab: Products (overview), Prices and discounts (lowest and highest price, compare-at price, discount %, stock), Variants (SKU list) (with One row per: Variant), Price and stock changes (monitoring), Store detection and Collections.
  • Apify integrations: send results to Google Sheets, Slack, Zapier, Make, n8n and others from the Integrations tab.

Use with AI agents (MCP)

Connect Apify's MCP server (https://mcp.apify.com?tools=tidytools/shopify-store-products-scraper) to Claude, Cursor or any MCP client, then ask e.g. "Which Allbirds shoes are on sale right now, and by how much?"

{ "stores": ["https://www.allbirds.com"], "onlyOnSale": true, "productTypes": ["Shoes"] }

Failed items are not charged and carry an errorType.

Tips and limitations

  • Big catalogs: Shopify serves at most 25,000 products per catalog or collection through products.json; read big stores collection by collection with Only these collections.
  • Collection URLs read only that collection; a store URL reads the whole catalog. In collections mode a collection URL (or Only these collections) returns just those collections; a store URL returns the store's collection list.
  • Price filters use the currency shown in each row. A store that converts prices by visitor country (Shopify Markets) may show another currency from Apify's network than from yours; the run note says when that applies.
  • Unreachable sites (no answer from either network) are given up after the first request failed on every route, with a free timeout/network row; this can still take a few minutes per site.
  • Removed products are reported only when the whole catalog was read and no filter was set, so "not read this time" is never reported as "removed".
  • Only new and changed products remembers each product's variant prices and availability per monitor name. Use a different monitor name for a different store list.
  • Monitoring guard: when a store moves (another myshopify.com shop or another domain than in the baseline) or more than Monitoring guard: suspicious change above (%) of its products (default 50%, stores with at least 10 products) change in one run, every row is still output with suspiciousChange: true, suspiciousReason and charged: false, and nothing is charged for that store in that run. The previous baseline is kept, so the next run compares with the old data again; once you have checked the store, run once with Accept suspicious changes as the new baseline on. A domain change with no product change is not flagged (it costs nothing anyway) and is remembered.
  • Pause between requests (default 1 second per store) keeps runs polite; lower it only for your own stores.

Terms and responsible use

  • The Actor reads only public storefront endpoints that Shopify serves to every visitor, with no login and no bot-check bypassing. It checks each store's robots.txt first and skips endpoints it disallows; Shopify's default robots.txt (September 2026) does not disallow products.json.
  • It returns product data only: no customer data, no order data, no personal contact details (store contact e-mails and phone numbers are not output).
  • Product texts and images belong to the stores. Use the data for price monitoring, research and analysis; do not republish images or descriptions as your own. Respect each store's terms of service.

FAQ

Does the Actor collect usage data? The Actor sends one anonymous usage counter per run (input type, item count range, outcome) to improve the tool; no URLs, content or account data.

Why does a store return nothing? It has turned off products.json (common for headless Hydrogen storefronts), blocks data-center networks, or disallows the endpoint in robots.txt. You get a free error row that says which.

Does it give stock quantities? No. Shopify's public feed says whether each variant is available, not how many units are left.

Why is a store's currency not what I expected? Stores using Shopify Markets show prices in the visitor's currency. currency tells you which currency the numbers are in; storeCurrency is the store's default.

Can it detect Shopify stores in a list of domains? Yes: set What to return to "Is it a Shopify store?". Only Shopify stores are charged. A site counts as a store when it has meta.json, Shopify headers or theme scripts, or a working products.json; a page that merely loads a file from cdn.shopify.com (blogs, marketing sites) does not.

What if the list is empty, a filter is wrong or a collection name is misspelled? An empty list, a date that is not a date (sinceDate) or a Min price above the Max price stops the run before anything is read: you get one free invalid_input row that says what to fix, and the run ends normally. A single bad line in a longer list gets its own free row and the other stores still run. A collection handle that is empty or does not exist (Shopify answers with an empty list) gets a free not_found row for that collection while the other collections are returned.

Can I run it on a schedule? Yes. Turn on Only new and changed products, set a monitor name, and create a daily schedule in Apify Console.

Found this useful? A short review on the Store helps other users find it.

More tools by TidyTools that work well with this one: