You ran a Shopify scraper, it said "Succeeded", and the dataset is empty. Here's how to find out why in two minutes with nothing but a browser, and how to make your own code tell an empty store from a failed read.
Step 1: open the feed yourself
Go to https://STORE-DOMAIN/products.json?limit=250 in a browser.
| What you see | What it means | What to do |
|---|---|---|
JSON with "products": [ … ] | The feed works. The problem is your tool or your input | Check the input: use the bare domain, not a collection or product URL |
{"products": []} | It's a Shopify store, but nothing is published to the online store | Nothing to scrape. That's a real zero |
A 404, but the HTML contains cdn.shopify.com or myshopify.com | It's a Shopify store with the public feed closed (headless storefront, or the feed is disabled or redirected) | There's no public catalogue to read. Treat it as "unavailable", not "empty" |
| Password page | The store is password-protected (pre-launch or wholesale) | It's not public, so stop |
| 403, 429 or 430 | You're being rate-limited or blocked | Slow down to about one request a second per store, back off on 429, and don't hammer many stores in parallel from one IP |
| A plain 404 with no Shopify markers in the HTML | Not a Shopify store | Use a different approach |
How to check whether a site is a Shopify store: look for cdn.shopify.com, Shopify.theme or myshopify.com in the page source, or open /meta.json. A Shopify store answers it with its name, currency and published product count.
Step 2: the pagination traps that lose products silently
- 250 per page is the maximum. Ask for
limit=500and you get 250. - Don't stop at the first short page. Some stores return a short page in the middle of the catalogue. Keep requesting
page=N+1until you get"products": []. - The 25,000-product ceiling. The feed stops at page 100. Page 101 returns an error. For bigger stores, use the public product sitemap and collection feeds (
/collections/{handle}/products.json), and dedupe by productid. - Prices are in the store's base currency, and the feed doesn't say which. Read
currencyfrom/meta.json. - No stock counts in the bulk feed. It only gives
available: true/false. Some stores publish a quantity in each product's own/products/{handle}.json, and many don't.
Step 3: make zeros explain themselves
Whatever you build, return one row per store with a status and a reason, even when there are no products. "0 rows, no reason" is the most common complaint about Shopify scrapers, and it's cheap to fix.
The ready-made version (disclosure: we built this)
Our Apify Actor, Shopify Products Scraper & AI Shopping Readiness Audit, runs Step 1 for every domain you give it. Every store it can't read gets a free status row with a plain-English note:
not_shopify-
shopify_products_unavailable(password-protected, headless or blocked) blocked_by_robotsblockedno_productsduplicateinvalid_inputerror
That means pasting a list of domains also works as a free "is it Shopify?" check.
For stores that work:
- Every product. It keeps paging past short pages until the feed is really empty. Above 25,000 products it reads the rest from the sitemap, collection feeds and product pages. Each audit row says whether the whole catalogue was read (
catalogueComplete) and the store's known total (totalProductsKnown). - Every image URL and every variant (SKU, price, compare-at price, options, availability), plus a one-row-per-variant view. Stock counts appear only where the store publishes them, and are never estimated.
- An optional 0–100 readiness audit of product data (GTINs, descriptions, alt text, SKUs and more).
-
maxProductsPerStoreis a hard cap. robots.txt (includingCrawl-delay) is respected, with one request per second per store.
from apify_client import ApifyClient client = ApifyClient("<YOUR_APIFY_TOKEN>") run = client.actor("yourevenings/shopify-catalogue-audit").call( run_input={"stores": ["allbirds.com", "example.com"], "maxProductsPerStore": 100}) for row in client.dataset(run["defaultDatasetId"]).iterate_items(): if row["recordType"] == "store": # stores it couldn't read print(row.get("store"), row["status"], row.get("note")) It costs $0.0008 per product row and $0.02 per store audit, plus a $0.00005 start fee per run (base prices; higher Apify plans pay less). Status rows are free.
It reads only public storefront data and collects no customer data.
What's the strangest "0 products" cause you've hit? Tell us in the comments.
