Shopify Product Catalog Scraper
Pricing
from $1.00 / 1,000 products
Shopify Product Catalog Scraper
Exports the complete product catalog of any public Shopify store - every product, every variant, prices, options and images - and verifies the export against the store's own published product count so you know whether you got all of it.
Pricing
from $1.00 / 1,000 products
Rating
0.0
(0)
Developer
Nyx Ward
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
13 days ago
Last modified
Categories
Share
Read the full product catalog and every price of the Shopify stores you name — one store or two hundred — and know, per store, whether you got all of it.
You give it a list of store domains. It gives you one clean row per product, with the store on every row, and a run report saying, per store, whether the export matched the number of products the store itself says it has published.
Built for watching a set of stores
storeUrls is a list, not a field. One run covers a whole watchlist, and every
row carries its store, currency, minPrice, maxPrice and scrapedAt, so
a single CSV export pivots straight into a price sheet.
{"storeUrls": ["allbirds.com","brooklinen.com","tentree.com","chubbiesshorts.com"],"maxProductsPerStore": 0}
A store that cannot be read does not stop the run and costs you nothing per product. It comes back with a status and a reason, and the other stores finish.
Why "complete" is the point
If you are comparing today's prices to last week's, a silently short read is worse than a failure. Twelve products missing from an export look exactly like twelve products delisted. You act on a signal that never happened.
Shopify's storefront product feed makes that easy to get wrong. It has no cursor. It is paged by offset, it carries no pagination metadata, and it silently caps every page at 250 items no matter what you ask for. A catalog edited while it is being read can shift under the offset, and nothing in the response tells you it happened.
This Actor reads the store's own published product count first, deduplicates every product by id, and compares the two at the end. If the count is short it walks the catalog a second time and merges the results. Then it tells you the outcome either way:
"complete": true, "expectedProductCount": 293, "productCount": 293
No other tool in this category reports that number. Most report nothing at all. It is the difference between a price sheet you can act on and one you have to spot-check by hand.
Input
| Field | Required | What it does |
|---|---|---|
storeUrls | yes | One or more store domains. allbirds.com, https://allbirds.com/collections/mens and weareallbirds.myshopify.com all work — only the host is used |
maxProductsPerStore | no | Safety cap per store. 0 (default) means no cap |
proxyConfiguration | no | Apify proxy. If a store answers BLOCKED, re-run it with the RESIDENTIAL group |
This Actor never discovers stores on its own. It reads the ones you name, and only those.
{"storeUrls": ["allbirds.com", "deathwishcoffee.com"],"maxProductsPerStore": 0}
Output — one row per product
{"schemaVersion": 1,"store": "allbirds.com","storeUrl": "https://www.allbirds.com","currency": "USD","scrapedAt": "2026-08-30T21:00:00Z","productId": 7218356060240,"handle": "mens-strider-explore","title": "Men's Strider Explore - Natural Black","url": "https://www.allbirds.com/products/mens-strider-explore","vendor": "Allbirds","productType": "Shoes","tags": ["classic", "mens"],"publishedAt": "2025-10-01T09:35:00-07:00","createdAt": "2025-09-10T12:47:47-07:00","updatedAt": "2026-08-30T14:28:13-07:00","bodyHtml": "<p>...</p>","optionNames": ["Color", "Size"],"featuredImageUrl": "https://cdn.shopify.com/.../left.png","imageUrls": ["https://cdn.shopify.com/.../left.png"],"imageCount": 5,"variantCount": 13,"minPrice": 130.0,"maxPrice": 130.0,"available": true,"variants": [{"variantId": 41334293889104,"title": "Natural Black / 9","sku": "A11768M090","price": 130.0,"compareAtPrice": null,"available": true,"position": 3,"grams": 954,"requiresShipping": true,"taxable": true,"options": { "Color": "Natural Black", "Size": "9" },"imageUrl": "https://cdn.shopify.com/.../left.png"}]}
The four things this saves you writing yourself:
- Option names resolved. The feed keeps option names on the product and
option values on the variant, joined by nothing but array position. Here
they arrive as
{"Color": "Natural Black", "Size": "9"}. - Prices as numbers. The feed sends
"130.00"as a string, with no currency anywhere. Here they are floats, with the store's currency on every row. - Variant images resolved. The variant→image link lives in two different places and stores use one or the other, sometimes both in the same catalog. Both are read.
- Deduplicated and counted. See above.
The run report
The dataset stays one shape so a CSV export is usable. Everything about a
store rather than a product goes to the key-value record OUTPUT:
{"scrapedAt": "2026-08-30T21:00:00Z","storesRequested": 2,"storesRead": 1,"productsExported": 293,"stores": [{ "store": "allbirds.com", "status": "OK", "complete": true,"expectedProductCount": 293, "productCount": 293,"duplicatesDropped": 0, "pagesFetched": 2, "passes": 1,"message": "Exported 293 products, which matches the 293 the store itself reports published. Catalog complete." },{ "store": "example.com", "status": "NOT_SHOPIFY","message": "No Shopify product feed at this domain (HTTP 404...). Check the domain." }]}
Every way a store can end
| Status | What it means |
|---|---|
OK | The catalog was read. Check complete for whether it was whole |
NOT_SHOPIFY | No Shopify product feed at this domain |
HEADLESS_STOREFRONT | A Shopify store whose product feed is switched off — a headless or custom storefront. No tool can read it through this endpoint |
PASSWORD_PROTECTED | Behind Shopify's password gate; the catalog is not public |
BLOCKED | The store refused the request (HTTP 401/403/430). Re-run with the RESIDENTIAL proxy group |
RATE_LIMITED | Throttled, and still throttled after three attempts |
UNREACHABLE | No answer after three attempts |
ROBOTS_DISALLOWED | The store's robots.txt disallows this endpoint. Skipped, and not charged |
Limits, stated plainly
- 25,000 products per store. That ceiling is Shopify's, not ours: past it
the endpoint answers
HTTP 400 Page * Limit exceeds the 25000 limit. Larger catalogs come back withdepthLimited: trueandcomplete: false. - Public catalog fields only. Inventory quantities, cost, barcodes and the rest of the Admin API are not in this feed and are not invented here.
- Published products only — what a visitor to the storefront can see.
What it does not do
Stated up front so you do not have to find out:
- No change detection between runs. It exports the catalog as it stands.
Diffing consecutive runs is yours to do — schedule the runs and compare the
datasets, or filter on
updatedAt. - No store discovery. It reads the domains you name and only those.
- No headless storefronts. A Shopify store that has switched the feed off
comes back
HEADLESS_STOREFRONT. No tool reads those through this endpoint.
How many stores actually expose the feed
Measured 2026-09-03 on a sample of 12 domains, n = 12: 8 exposed a readable feed, 1 was a headless storefront, 3 had no Shopify feed at all.
That sample was assembled to exercise this scraper — large well-known brands,
one negative control, one at the size ceiling — not to estimate the Shopify
population, and it should not be read as a coverage rate for it. Run it on your
own list with maxProductsPerStore: 1 and the run report answers the question
for your domains, one page per store.
Pricing
You are charged per product exported. A store that cannot be read costs you nothing beyond the run itself.
Support
Open an issue on the Actor page. This Actor is maintained by Nyx Ward.