Shopify Product Monitor & Scraper - Price, Stock & Sales avatar

Shopify Product Monitor & Scraper - Price, Stock & Sales

Pricing

$2.00 / 1,000 products

Go to Apify Store
Shopify Product Monitor & Scraper - Price, Stock & Sales

Shopify Product Monitor & Scraper - Price, Stock & Sales

Scrape Shopify products or monitor stores for product additions/removals, price changes, sales and stock changes. Stateful scheduled runs emit only change events plus free store summaries. Public product feeds only.

Pricing

$2.00 / 1,000 products

Rating

0.0

(0)

Developer

Luke Hunter

Luke Hunter

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

11 hours ago

Last modified

Share

Shopify Product Monitor & Scraper — Price, Stock & Sales

For e-commerce competitor monitoring, dropshippers sourcing products, and market researchers who need clean Shopify catalogue data or compact change alerts. In normal mode it returns every product (or variant). Turn on onlyChangesSinceLastRun for persistent monitoring: the first run saves a baseline, later runs emit only price, sale, stock and assortment changes. No login, no API key, no browser.

Pay-per-result: $0.002 per delivered product — 1,000 products = $2.

Quick start (1 minute)

  1. Open the Input tab and list the store(s) you want, e.g. https://www.allbirds.com.
  2. Click Start. Export the resulting dataset to CSV/Excel/JSON, or pull it through the API shown below.
{
"storeUrls": ["https://www.allbirds.com"],
"maxItems": 20
}

That prefilled example finishes in well under a minute and costs $0.04.

How it works

Every Shopify store publishes a public, unauthenticated JSON feed of its catalogue at /products.json (and, per collection, at /collections/<handle>/products.json). This Actor:

  1. Checks the store's robots.txt for the exact path it is about to read, and skips the store (reporting why, in the run's status message) if that path is disallowed.
  2. Pages through products.json?limit=250&page=N, stopping on the first empty page — the real end of the catalogue.
  3. Normalises every product (or, in variant mode, every variant) into one flat dataset row.
  4. Reads store domains politely: about one request per second per store, backing off and retrying briefly on rate limiting, and reporting — never disguising — a block, a password wall, or a store that isn't running Shopify.

Monitor changes on a schedule + webhook

Use monitor mode when you care about what changed, not another full catalogue dump. State is stored in a named Apify key-value store across runs. The first run for each store/collection/output-mode watch is a baseline: it emits no billable changes and one free store_summary row. Later runs emit only:

  • product_added
  • product_removed — only after this run reached the real end of pagination; never inferred from a partial/capped fetch
  • price_changed — includes previous/current price and percentage change
  • sale_started / sale_ended — based on Shopify compare-at pricing
  • back_in_stock / out_of_stock

Example scheduled input:

{
"storeUrls": ["https://www.allbirds.com"],
"outputMode": "variant",
"maxProductsPerStore": 2000,
"maxItems": 200,
"onlyChangesSinceLastRun": true,
"stateStoreName": "allbirds-competitor-watch"
}

A concrete recipe:

  1. Run the input above once to establish the baseline.
  2. In Apify Console → Schedules, create a schedule for this Actor every 6 hours and reuse the same input and stateStoreName.
  3. In Integrations → Webhooks, add an Actor run succeeded webhook that POSTs to your endpoint (or an n8n/Make/Zapier webhook URL).
  4. In the webhook handler, read the completed run's default dataset. Ignore store_summary if you only want alerts; every other row is a billable change event ready to route to Slack, email, a database or a pricing workflow.

Example change row:

{
"eventType": "price_changed",
"storeDomain": "www.allbirds.com",
"productId": "7340901859408",
"variantId": "42146889039952",
"title": "Women's Allbirds Flip Flop - Dusty Pink",
"sku": "A12513W050",
"previousPrice": 50,
"currentPrice": 25,
"priceChange": -25,
"priceChangePct": -50,
"observedAt": "2026-09-28T08:00:00.000Z"
}

Use cases

  • Competitor price monitoring: run it on a schedule against competitor stores and compare priceMin/priceMax/onSale over time.
  • Dropshippers: pull a supplier or competitor's full catalogue with prices and stock in one export.
  • Market research: compare vendors, product types, tag usage and pricing across many Shopify stores at once.

Input

{
"storeUrls": ["https://www.allbirds.com"],
"collectionHandle": "",
"outputMode": "product",
"onlyInStock": false,
"maxProductsPerStore": 250,
"maxItems": 100
}
FieldTypeDefaultDescription
storeUrlsstring[]required1–100 Shopify store domains or URLs. A bare domain, http(s)://, www. and any trailing path are all accepted; only the domain is used.
collectionHandlestring(none)Limit every store to one collection (e.g. mens-shoes, or a full collection URL). Leave empty for the whole catalogue.
outputMode"product" | "variant""product"One row per product (with a nested variants[]) or one row per variant.
onlyInStockbooleanfalseDeliver only rows with at least one available variant (product mode) or an available variant (variant mode). Filtered-out rows are never charged.
maxProductsPerStoreinteger2501–20,000. Cap on distinct products read per store.
maxItemsinteger1001–20,000. Normal mode: product/variant rows. Monitor mode: billable change-event rows. Free summary rows do not count.
onlyChangesSinceLastRunbooleanfalseOpt-in monitor mode. First run saves a baseline; later runs output only change events. Off by default, so normal runs are unchanged.
stateStoreNamestringshopify-store-products-scraper-stateNamed cross-run state store used only in monitor mode. Reuse the same name for the same watch; change it to start a fresh baseline.

Output fields

CategoryFields
Storestore, storeDomain, collectionHandle
IdentityproductId, handle, title, url
Cataloguevendor, productType, tags
TimestampscreatedAt, updatedAt, publishedAt, scrapedAt
PricepriceMin, priceMax, compareAtPriceMax, onSale, currency
Stockavailable, variantCount, variants[] (product mode)
Variant (variant mode)variantId, variantTitle, sku, price, compareAtPrice, available, option1, option2, option3
Media & copyimageUrls (first 5), descriptionText (HTML stripped, truncated to 2,000 characters)

Missing values are returned as null. Nothing is invented.

Output example (product mode)

{
"store": "https://www.allbirds.com",
"storeDomain": "www.allbirds.com",
"productId": "7340901859408",
"handle": "womens-allbirds-flip-flop-dusty-pink",
"title": "Women's Allbirds Flip Flop - Dusty Pink",
"vendor": "Allbirds",
"productType": "Shoes",
"tags": ["shoprunner", "..."],
"url": "https://www.allbirds.com/products/womens-allbirds-flip-flop-dusty-pink",
"priceMin": 25,
"priceMax": 25,
"compareAtPriceMax": 50,
"onSale": true,
"currency": null,
"available": true,
"variantCount": 7,
"variants": [
{ "id": "42146889039952", "title": "5", "sku": "A12513W050", "price": 25, "compareAtPrice": 50, "available": false, "option1": "5", "option2": null, "option3": null }
],
"imageUrls": ["https://cdn.shopify.com/s/files/1/1104/4168/files/A12513_..._LEFT.png"],
"descriptionText": "Sun on your feet. Comfort underneath. Light, easy, and made for warm weather...",
"collectionHandle": null,
"scrapedAt": "2026-09-27T00:00:00.000Z"
}

In outputMode: "variant", the same fields above (minus the nested variants[]) are repeated on one row per variant, with variantId/variantTitle/sku/price/compareAtPrice/option1-3 added and available meaning this variant's stock status.

Use it as an API

from apify_client import ApifyClient
client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("lukehunter/shopify-store-products-scraper").call(
run_input={
"storeUrls": ["https://www.allbirds.com", "https://www.gymshark.com"],
"maxItems": 500,
}
)
for product in client.dataset(run["defaultDatasetId"]).iterate_items():
print(product["title"], product["priceMin"], product["available"])

Pricing and cost control

Pay per delivered row (product, or variant in variant mode).

$0.002 per product — 1,000 products = $2.

Rows deliveredCost
20$0.04
1,000$2.00
10,000$20.00

There is no charge for starting a run, and no charge for a store that is skipped, blocked, or produces nothing. In monitor mode, only change-event rows use the existing product charge; each per-store store_summary row is pushed free. maxItems gives you a hard upper bound on billable rows.

Limitations

  • Only public product data the store exposes on products.json. No customer data, order data, checkout data, or anything requiring login — this Actor never collects that, by design.
  • Some stores disable their public products.json feed, put it behind a store password, or block automated requests entirely; when that happens the run reports it honestly per store instead of failing the whole run or crashing.
  • currency is always null. Shopify's public products.json endpoint does not publish a currency anywhere in its response, and this Actor never guesses one from a store's country or price formatting.
  • Prices reflect the storefront's default price list at scrape time; quantity breaks, B2B price lists and localized/multi-currency pricing are not reflected.
  • Draft, unpublished and password-protected products never appear in the public feed and so cannot be collected.
  • This Actor honours every store's robots.txt and never bypasses a password wall, CAPTCHA or block — a blocked store is reported, not evaded.
  • This is an independent tool, not affiliated with, endorsed by, or connected to Shopify Inc. or any store scraped. You are responsible for using the collected data in accordance with the relevant store's and Shopify's terms and applicable law.

FAQ

Does this work on any Shopify store?

Any store running the standard Shopify storefront with its public products.json feed enabled and not password-protected. A minority of stores disable this feed or block automated requests; those are reported per store, not treated as a run failure.

Can I scrape just one collection?

Yes — set collectionHandle to the collection's handle (e.g. mens-shoes) or paste its full URL.

Does it collect customer or order data?

No, never. Only the public product catalogue.

Why is currency always null?

Shopify's public products.json response never states a currency. Guessing one from a country-code TLD or price format would sometimes be wrong, so this Actor reports null rather than a guess you can't verify.

Other data tools from the same developer, built to the same standard: official or public sources, hard cost caps, and honest documentation of limits.