What it does
Most Shopify stores expose their published catalog as JSON at /products.json. This actor reads that feed for one or more store URLs and returns one row per product: title, handle, vendor, product type, tags, created, published and updated dates, price range, compare-at price and discount percentage, stock availability, every variant (SKU, price, compare-at, options, weight) and every image URL.
For competitor price monitoring, pass updatedSince on a daily schedule and only products updated since that date become rows, so unchanged products cost nothing. collections narrows a run to specific collection handles. In store mode you get one row per shop instead: name, country, currency, ships-to countries, product and collection counts, price statistics, top vendors, product types and tags, the biggest collections, the theme and the apps detected on the homepage.
It is polite by design: the store's robots.txt and Crawl-delay are obeyed, requests go one at a time per store at about 1.3 per second, and any block stops that store with a free row. Products cost $0.30 per 1,000 and each analyzed store $0.005; blocked, headless and non-Shopify sites are free.
How it works
- Each URL in
storeUrls(orstoreUrl) is reduced to its host, so any page of a store works, including myshopify.com domains. - The actor reads the store's robots.txt first, then pages through products.json at 250 products per page, up to
maxProductsPerStore. - Filters are applied as it goes:
collectionsswitches to collection feeds andupdatedSincekeeps only recently updated products, which are the only ones charged. - In store or both mode it also reads meta.json and the homepage HTML to compute price stats and detect the theme and apps from open fingerprints.
- A 401, 403, 429 or bot challenge stops that store at once with no retries, proxies or bypass, and the resulting row is free.
Input example
A typical run input (JSON). Every field is also available as a form in the Apify console.
{
"storeUrls": [
"allbirds.com",
"https://www.tentree.com"
],
"mode": "products",
"collections": [],
"maxProductsPerStore": 1000,
"updatedSince": "",
"includeVariants": true,
"includeBodyHtml": false
}All input fields
| Field | Type | Required | Default | What it does |
|---|---|---|---|---|
storeUrls | array | no | Store domains or URLs, e.g. allbirds.com or https://www.allbirds.com/collections/mens. Any page of the store works because the store is taken from the host. myshopify.com domains work too. Duplicates are removed. | |
storeUrl | string | no | One store domain or URL. Handy for Clay, Make/Zapier and AI agents. Scraped together with storeUrls. | |
mode | string | no | "products" | products: one row per product ($0.30 per 1,000). store: one row per shop with meta.json details, product count, price stats, top vendors/types/tags, top collections, theme and detected apps ($0.005 per store). both… |
collections | array | no | [] | Optional collection handles (the part after /collections/ in the URL, e.g. mens-shoes). When set, products come from each collection instead of the whole catalogue, and each row says which collection it came from.… |
maxProductsPerStore | integer | no | 1000 | Stop each store after this many product rows (products/both mode), or after scanning this many products for the stats (store mode). products.json returns 250 products per page. |
updatedSince | string | no | "" | Optional date (YYYY-MM-DD) or ISO timestamp. Only products whose updated_at is on or after it become rows. Products skipped by this filter are never charged. Use it for daily price and stock monitoring. |
includeVariants | boolean | no | true | Add every variant (SKU, price, compare-at price, availability, options, weight) to each product row. Same price. |
includeBodyHtml | boolean | no | false | Add the product description (body_html) to each row. Off by default because it makes rows much bigger. Same price. |
Output example
Product rows carry the store, pricing, discount and stock fields up front, with variants and images attached to each row:
| store | title | vendor | productType | price | compareAtPriceMin | discountPct | available | variantCount |
|---|---|---|---|---|---|---|---|---|
| www.allbirds.com | Women's Allbirds Flip Flop - Dusty Pink | Allbirds | Shoes | 25 | 50 | 50 | true | 7 |
| www.allbirds.com | Men's Canvas Runner NZ - Deep Navy Stripes (Blizzard Sole) | Allbirds | Shoes | 100 | true | 13 | ||
| www.tentree.com | Sasquatch Peak Hat | tentree | Accessories | 31.5 | 45 | 30 | true | 1 |
| www.deathwishcoffee.com | White Chocolate Pistachio Ground Bundle | Death Wish Coffee | Bundle | 48.99 | 53.99 | 9.3 | true | 1 |
Pricing
Pay per event: you are charged only for the results you receive, with no subscription. Set a maximum total charge on any run and it stops cleanly at the cap.
| Charged for | When | Price | Per 1,000 | Worked example |
|---|---|---|---|---|
| Product result | Charged per product row delivered (with variants and images) | $0.0003 | $0.30 | A 1,000-product store = $0.30; with updatedSince, unchanged products are skipped and cost nothing |
| Store analyzed | Charged per store row in store/both mode (meta, price stats, top vendors/types, collections, theme, apps) | $0.005 | $5 | 100 competitor stores = $0.50; blocked, headless, robots-disallowed and non-Shopify sites are free rows |
| Actor Start (platform start fee) | Charged when the Actor starts running. Number of events charged depends on Actor memory (one event per GB, minimum one event). | $0.00005 | — | One run at 256 MB = $0.0000125 |
Prices are read from the live Apify Store listing when this page is built. The listing is always authoritative.
Try Shopify Store Scraper on Apify. Pay only for the results you get.
Run on Apify →Use cases
Competitor price and stock monitoring
Schedule a daily run for a list of competitor stores with updatedSince set to the previous day. You get only the products whose prices, stock or details changed, and pay only for those rows.
Catalog export and migration prep
Pull a whole store's published catalog with variants, images and optionally descriptions as JSON or CSV. It is a quick way to audit a catalog or hand product data to another system.
Product research and dropshipping
Compare vendors, product types, price ranges and discount depth across several stores in one dataset. Compare-at prices show how often and how deeply a store discounts.
Store profiling for sales and partnerships
Use store mode to get one row per shop with its theme, detected apps, currency, ships-to countries and catalog size. Agencies and app vendors can qualify a list of Shopify stores at $0.005 each.
How it compares
| ActorForge Shopify Scraper | Downloading products.json yourself | Store owner's admin CSV export | |
|---|---|---|---|
| Works on stores you do not own | Yes | Yes | No, owner only |
| Pagination, robots.txt and polite pacing handled | Yes | You handle it | Not needed |
| Clean rows with discount % and stock flags | Yes | No, raw JSON | Partial |
| Only products updated since a date | Yes, unchanged products free | You filter it | No |
| Theme and app detection | Yes, store mode | No | No |
| Inventory quantities and sales figures | No, not public | No | Yes |
| Headless (Hydrogen/custom) storefronts | No, returns a free not-reachable row | No | Yes |
If you own the store, Shopify's admin export includes inventory quantities and data this public feed never shows. For one small store, opening /products.json in a browser may be all you need; the actor helps once you have many stores, schedules or large catalogs.
Limits and honest notes
- Only stores that serve Shopify's public /products.json work. Headless storefronts (Hydrogen/Oxygen or a custom frontend, e.g. fashionnova.com, hydrogen.shop) do not serve it and come back as free not-reachable rows.
- Each store's own robots.txt is read first and obeyed, including any Crawl-delay. The actor never requests /search, /cart, /checkout, /policies/, /recommendations/products or sort_by collection URLs. A store that disallows products.json gives a free robots-disallowed row.
- Polite by design: one request at a time per store at about 1.3 requests/second, at most 3 stores in parallel, honest user agent (ActorForge-ShopifyScraper/1.0). A 401/403/429 or bot challenge stops that store at once: no retries, proxies or bypass. Some big brands (e.g. gymshark.com with a 403, bombas.com with a Vercel challenge) block this, so their rows are free.
- products.json returns 250 products per page and lists published products only. maxProductsPerStore caps each store (default 1,000, up to 100,000). In both mode the store stats cover the products scanned, and catalogComplete says whether that was the whole catalogue.
- Prices are the store's base-currency prices from products.json (meta.json gives the currency). Market-specific or B2B prices, inventory quantities and sales figures are not public there.
- Apps and theme come from the homepage HTML (Shopify.theme and the open webappanalyzer fingerprints). Apps that load only after JavaScript runs or only on product pages may be missed.
- Catalogue data only: no customer, reviewer or contact-person data is read or returned.
- The run's spend limit is respected: products are charged one page at a time before delivery, and the run stops cleanly when the limit is reached.
FAQ
How do I scrape all products from a Shopify store?
Add the store's domain to storeUrls with mode set to products. The actor pages through the public products.json feed at 250 products per page until it reaches maxProductsPerStore (default 1,000, up to 100,000).
Why does a Shopify store return no products?
Headless storefronts built on Hydrogen/Oxygen or a custom frontend do not serve /products.json, and some stores block automated requests with a 403 or a bot challenge. Those stores come back as free rows explaining the reason.
Can I get inventory quantities or sales numbers?
No. products.json shows whether each variant is available but not stock quantities, and sales figures are not public. Prices are the store's base-currency prices, not market-specific or B2B prices.
How do I scrape only one collection?
Put the collection handle, the part after /collections/ in the URL such as mens-shoes, in collections. Each row then says which collection it came from. The handles apply to every store in the run.
Which Shopify apps can it detect?
Store mode checks the homepage HTML against open webappanalyzer fingerprints (7,600+ of them), covering apps such as Klaviyo, Rebuy, Yotpo and Afterpay, and reads the theme name, base theme and version. Apps that load only after JavaScript runs or only on product pages may be missed.
Use it from code
Run the actor through the Apify API with the input above.
cURL
curl -X POST "https://api.apify.com/v2/acts/actor-forge~shopify-store-scraper/run-sync-get-dataset-items" \
-H "Authorization: Bearer $APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"storeUrls": ["allbirds.com", "https://www.tentree.com"], "mode": "products", "collections": [], "maxProductsPerStore": 1000, "updatedSince": "", "includeVariants": true, "includeBodyHtml": false}'JavaScript (Node.js)
// Node.js 18+ (built-in fetch). Set APIFY_TOKEN in your environment.
const input = {
"storeUrls": [
"allbirds.com",
"https://www.tentree.com"
],
"mode": "products",
"collections": [],
"maxProductsPerStore": 1000,
"updatedSince": "",
"includeVariants": true,
"includeBodyHtml": false
};
const res = await fetch(
"https://api.apify.com/v2/acts/actor-forge~shopify-store-scraper/run-sync-get-dataset-items",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.APIFY_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify(input),
}
);
const items = await res.json();
console.log(items.length, items[0]);Python
# pip install apify-client (set APIFY_TOKEN in your environment)
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run_input = {
"storeUrls": [
"allbirds.com",
"https://www.tentree.com"
],
"mode": "products",
"collections": [],
"maxProductsPerStore": 1000,
"updatedSince": "",
"includeVariants": True,
"includeBodyHtml": False
}
run = client.actor("actor-forge/shopify-store-scraper").call(run_input=run_input)
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
print(item)The run-sync-get-dataset-items endpoint waits for the run (up to 5 minutes) and returns the dataset rows directly. For longer runs, start the run asynchronously and read the dataset when it finishes, as the Python example does. Replace APIFY_TOKEN with your own token from the Apify console.
Step-by-step guides
How to Export Shopify Store Product Data to CSV or JSON
Give the ActorForge Shopify Store Scraper a store URL, let it page through the public products.json feed, then download the dataset as CSV or JSON…
Related actors
App Store & Google Play Scraper — Apps, Charts, Reviews
Scrape Google Play and App Store app details (rating, histogram, installs, price, version, developer, screenshots, similar apps), Apple top charts…
$0.80 per 1,000 · app resultGoogle Ads Transparency Scraper — Competitor Ads | $0.60/1K
Scrape competitor ads from the Google Ads Transparency Center by domain, advertiser name or ID: format, first/last shown, regions, impressions, ad…
$0.60 per 1,000 · ad resultSEO Audit Crawler — On-Page SEO Checker for Whole Sites
Crawl a whole website and audit every page for technical and on-page SEO: titles, meta descriptions, H1s, canonicals, noindex, duplicates, broken…
$6 per 1,000 · page auditedTry Shopify Store Scraper on Apify. Pay only for the results you get.
Run on Apify →