Shopify Store Scraper
Pricing
from $0.60 / 1,000 results
Shopify Store Scraper
Export every product and variant from any Shopify-powered store by domain, read straight from the store's own JSON API rather than scraped HTML.
Pricing
from $0.60 / 1,000 results
Rating
0.0
(0)
Developer
The Netaji
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
13 days ago
Last modified
Categories
Share
The Actor exports every product and variant from any Shopify-powered store by domain. It reads the store's own /collections.json and /collections/<handle>/products.json endpoints directly, so no HTML is parsed and no product page is rendered.
This scrapes product catalogs from stores built on Shopify. It does not read the Shopify App Store (apps.shopify.com); an app listing and a store's product catalog are different documents, and this Actor only reads the latter.
Accepted input
| Field | Type | Default | Description |
|---|---|---|---|
storeUrl | string | — | Required. A domain or URL of a Shopify-powered store. |
maxItems | integer | 0 | Maximum product variants saved. 0 removes the limit. |
proxyConfig | object | Residential Apify Proxy | Proxy used for every request. Residential by default — most storefronts block or rate-limit datacenter IPs outright. |
includeRawData | boolean | false | Adds handle, every product image, option definitions, and per-variant grams/taxable/requiresShipping/publishedAt. Already present in the same response, so this costs nothing extra to turn on. |
{"storeUrl": "allbirds.com","maxItems": 500}
Response fields
One record per product variant.
| Field | Contents |
|---|---|
name | Product title, with the variant title appended when it isn't the default one |
price | Variant price, exactly as the store's JSON returns it |
category | Collection the product was found in, title-cased |
brand | Product vendor |
identifier | Variant id |
sku | Variant SKU, when the store sets one |
stock | 0 when the variant is out of stock; null when it's available |
image_url | First product image |
url | The product's page inside the collection it was found in |
description | Product description with HTML tags stripped |
old_price | Pre-sale price, when the store sets a real compare-at price |
barcode | Variant barcode, when the store's product JSON exposes it |
product_type | The store's product type for this product |
tags | The store's tags for this product |
handle | The product's URL slug. Only present when includeRawData is on |
images | Every product image, not just the first. Only present when includeRawData is on |
options | The product's option definitions (e.g. Size, Color) and their values. Only present when includeRawData is on |
grams | Variant weight in grams. Only present when includeRawData is on |
taxable | Whether the variant is taxable. Only present when includeRawData is on |
requires_shipping | Whether the variant requires shipping. Only present when includeRawData is on |
published_at | When the product was published to the store. Only present when includeRawData is on |
{"name": "Wool Runner - Natural Black / Dark Grey","price": "98.00","category": "Mens Shoes","brand": "Allbirds","sku": "","stock": null,"url": "https://www.allbirds.com/collections/mens-shoes/products/mens-wool-runner-natural-black"}
How collection is scoped
The store's collections are paged first, then each collection that reports at least one product is paged for its products. A product's variants are yielded once each; a variant that appears in more than one collection is written only the first time it's seen, not once per collection.
A collection's advertised product count can be higher than what actually comes back — the endpoint only exposes products published to the store's online-store sales channel, and a nonzero count is read as "this collection has something to fetch," not as a promise of that many results.
Behaviour on partial results and limits
maxItems stops the run once at least that many variants have been saved, not the instant the limit is reached — a request already in flight when the limit is hit is still allowed to finish, so the final count can land a little past maxItems rather than cutting off mid-request.
Stores behind extra bot protection commonly block datacenter IPs outright and rate-limit everything else, which is why Residential Apify Proxy is on by default. Password-protected storefronts still don't work regardless of proxy: the Actor reads the storefront's public JSON API directly, and a store that requires a login has no public surface for it to read.
Frequently asked questions
Does this scrape the Shopify App Store? No. apps.shopify.com lists apps for merchants to install; this Actor reads a store's product catalog through its storefront JSON API. The two are unrelated documents on unrelated domains.
Why did I get fewer results than the store's collections suggest?
A collection's products_count includes products not published to the online store, which the JSON endpoints this Actor reads don't expose. The count is a ceiling, not a guarantee.
Are the same product variants returned more than once if they sit in several collections? No. Each variant is saved once, on the first collection it's found in, even if the store lists it under several.
Does maxItems cut the run off at exactly that number?
Not to the item. The run stops once the count reaches or slightly passes maxItems, because a request already in flight when the limit is hit is allowed to complete rather than being discarded mid-request.
Will this work on a password-protected store? No. The Actor reads public JSON endpoints with no browser session and no login; a store that requires one has nothing public for it to read.
Why does this need a proxy, and why residential specifically? Most Shopify storefronts block or rate-limit plain JSON API requests, and many block known datacenter IP ranges outright regardless of request pattern. Residential Apify Proxy is enabled by default for that reason; switching it to datacenter or turning it off is likely to get blocked on stores with any real bot protection.
The store's own products.json has more fields than this Actor returns — where did they go?
Nowhere; they're not fetched selectively, they're just not included in the default row shape. The store's JSON response carries the full product object — every image, all option definitions, per-variant shipping/tax flags, timestamps, and more — and this Actor reads that same response but writes out a fixed, commonly-needed set of fields per variant. Turn on includeRawData to get the rest; it's already part of the same response, so nothing extra is fetched or charged for it.