Shopify Scraper - Products, Inventory, Variants, SKU, Prices avatar

Shopify Scraper - Products, Inventory, Variants, SKU, Prices

Pricing

from $1.20 / 1,000 products

Go to Apify Store
Shopify Scraper - Products, Inventory, Variants, SKU, Prices

Shopify Scraper - Products, Inventory, Variants, SKU, Prices

Shopify products and inventory scraper that recovers shops whose own /products.json is closed: 43 of 47 stores read where the plain path reads 33, measured. Prices, SKUs, barcodes, named options and real stock counts where the shop publishes them. Shopify product scraper, products JSON.

Pricing

from $1.20 / 1,000 products

Rating

0.0

(0)

Developer

Snow Leo Data

Snow Leo Data

Maintained by Community

Actor stats

0

Bookmarked

1

Total users

0

Monthly active users

2 days ago

Last modified

Share

Shopify Inventory & Products Scraper — any store, $1.20 per 1,000 products

Reads the public product feed of any Shopify storefront and returns 42 fields per product: every variant, every price, the shop's own compare-at price, SKUs, barcodes, stock counts, collection membership and the full description. No API key, no login, no browser, no proxy.

It also does the thing every other Shopify products scraper skips. When a shop refuses /products.json on its own domain, this Actor finds the shop's Shopify origin and reads the catalogue there. On a measured sample of 47 live Shopify storefronts that recovered 10 of the 14 shops that refuse the feed, which is the difference between 43 stores readable and 33.

Shopify products, Shopify prices, Shopify variants, Shopify inventory, Shopify stock, Shopify SKU lists, Shopify collections and the whole Shopify catalog of a store come out of one run, in one table — product inventory, product prices and product variants side by side.


What problem does this actually solve?

A Shopify store publishes its catalogue at /products.json. That endpoint is the backbone of every Shopify scraper on this store, and it works — until the merchant puts a firewall in front of the domain, moves the storefront to a headless frontend, or installs an app that blocks the feed. Then the endpoint answers 403 or 404, and a scraper built on it returns nothing at all.

That is not a rare corner. Measured on 12 September 2026 across 60 well-known direct-to-consumer domains, of which 47 turned out to be Shopify storefronts:

OutcomeStores
/products.json open on the store's own domain33
Domain refuses it (403, 404)14
Of those, recovered through the shop's Shopify origin10
Still unreachable4
Catalogue reachable in total43 of 47 (91%)

A scraper that only tries the store's own domain gets 33 of 47 — 70%.

Reproduce the whole table yourself:

python3 tools/measure_coverage.py tools/stores.txt

The recovery is not a guess. The shop's Shopify origin address is published by the storefront itself, and before a single product is read the Actor asks that origin who it belongs to and compares the answer with the domain you typed. A store that does not match is never used — a name-based guess can land on a completely unrelated merchant, and serving you someone else's catalogue would be worse than returning nothing.

What does one row look like?

One row per product. Every row carries the store it came from, so several stores in one run stay easy to split apart.

GroupFields
Storestore_domain, store_origin, store_name, store_country, store_currency, catalog_source
Identityproduct_id, handle, title, url, vendor, product_type, tags
Datespublished_at, created_at, updated_at, scraped_at
Textdescription_text, description_html
Mediafeatured_image, images, image_count
Priceprice_min, price_max, compare_at_price_max, on_sale, discount_percent, currency
Stockavailable, available_variant_count, variant_count, total_inventory
Codessku_list, barcode_list
Structureoptions, variants, collections, recommended_handles
Detail modeimage_alt_texts, subscription_available
Change modechange_type, changed_fields

variants holds every size, colour and pack the shop sells, each with its own price, compare_at_price, sku, available, option values and image — and, when stock detail is on, barcode, inventory_quantity, inventory_policy, weight and weight_unit.

Which fields does the bulk product feed not carry?

This is the second measured gap, and it is the reason the Actor has a stock detail switch at all. The bulk feed and a product's own card are two different documents, and the card is richer. Measured on a live storefront on 12 September 2026, a variant in the bulk feed carries exactly these keys:

available, compare_at_price, created_at, featured_image, grams, id,
option1, option2, option3, position, price, product_id,
requires_shipping, sku, taxable, title, updated_at

A variant on the product's own card adds eight more:

barcode, inventory_quantity, inventory_management, inventory_policy,
weight, weight_unit, quantity_rule, quantity_price_breaks

So the exact stock count and the GTIN/EAN/UPC barcode — the two fields most catalogue work actually needs — are simply not in the feed everyone reads. Turn on Include stock counts and barcodes and the Actor fetches each product's card and fills barcode_list, total_inventory and the per-variant stock fields, plus image_alt_texts and subscription_available. On a 25-product sample of one live store, 24 products came back with a barcode filled in.

The same is true of the images. In the bulk feed an image has src, width, height and nothing else — the alt text a merchant wrote for accessibility and SEO lives only on the product card.

Can I get the shop's own "customers also bought" list?

Yes. Switch on Include recommended products and recommended_handles fills with the handles Shopify's own recommendation engine returns for that product. That is the shop's real merchandising, not a similarity score invented by the scraper. It costs one request per product and, unlike the leading Actor in this niche, it is not charged as an extra.

That switch costs one extra request per product, so it is off by default. Nothing else in the Actor depends on it: no filter and no view needs a field that only the card provides, which is why turning it off can never empty your dataset.

How is the Shopify catalog paged, and where does it stop?

The feed hands out at most 250 products per page, and it does so silently: ask for 1,000 and you get 250 without a word. The Actor walks pages until the store runs out, so a full catalogue of any size comes back in one run. Measured on one live store, the walk returned 1,006 products over 5 pages, and the store's own sitemap listed exactly the same 1,006 — when the feed is open it is complete, not a sample.

How do I point it at the right products?

Put whatever you have into Store URLs. All four shapes work:

What you pasteWhat you get
allbirds.comthe whole catalogue
allbirds.com/collections/saleonly that collection
allbirds.com/products/some-handlethat single product
some-shop.myshopify.comthe Shopify origin directly

A collection link is not a filter applied after collection. Shopify serves a collection at its own address, so the Actor asks the store for that collection and never downloads the rest — which is both faster and cheaper for you.

How much does a run cost?

You pay $1.20 per 1,000 products delivered to the dataset, plus the usual per-run start event. Nothing else is billed: filtered-out products, unchanged products in change mode, blocked stores and empty pages all cost nothing. Stock counts, barcodes and collection mapping are included in that price rather than charged as extras.

Every filter in the input runs before anything is written, so you are never charged for rows you asked the Actor to throw away.

How do I watch a competitor's prices without paying twice?

Turn on Only new and changed products and put the Actor on a schedule. It remembers every product it has delivered, in a named store tied to your Apify account, and on the next run it returns only what moved. changed_fields names what moved for each row:

ValueMeaning
priceprice_min or price_max moved
availabilitya variant went out of stock or came back
variantsa size or colour was added or removed
contenttitle, type, tags or description were edited

A scheduled run over a catalogue where nothing changed writes zero rows and charges nothing, and it says so in the log and in empty_reason rather than finishing silently. That matters: a successful run with an empty dataset usually means something is broken, so an empty run that is legitimate has to name itself.

The fingerprint deliberately ignores updated_at. Shopify moves that timestamp on housekeeping the merchant never sees, and a monitor built on it reports the whole catalogue as "changed" every morning.

Why does an empty dataset never happen silently?

Because the worst failure in this business is the quiet one: the run is green, the buyer is charged for the start, and no rows arrive with no explanation. Every empty run here ends with a named reason in the run report and a warning in the log:

empty_reasonWhat happened
nothing-changedchange mode, and nothing moved since the last run
filtered-outproducts were read and your filters removed all of them — the report says which filter took how many
store-not-readableno store served a feed anywhere
not-foundthe store is readable but the collection handle or product handle you asked for is not in it
source-returned-nothingthe store's catalogue is genuinely empty
charge-limit-reachedyour own spending limit for the run was already used up
no-store-giventhe input had no store in it

When a collection handle is missing, the log prints how many collections the store does publish and the first few handles, so the fix takes one look instead of one support ticket.

What do the filters do, exactly?

Every filter is applied before billing, and every one of them leans only on fields the bulk feed always carries. That is a deliberate rule, not an accident: a filter that depends on a field which only exists in some mode will quietly return zero rows on a green run.

  • Only these collections — asked from the store, not filtered afterwards.
  • Title or description contains / Exclude these words — plain word matching over title and description.
  • Vendors, Product types, Tags — a product with that field empty is kept, because missing data is not a mismatch.
  • Minimum / maximum price — on the cheapest variant, in the store's own currency. A reversed range is swapped silently.
  • In stock only — at least one variant the shop still sells.
  • On sale only — compare-at price above the selling price.
  • Changed in the last N days — on updated_at; a product with no timestamp is kept.

Max products returned limits the dataset, not the crawl. With a filter switched on the Actor keeps reading past the limit until it has enough surviving rows, so asking for 30 discounted products returns 30 discounted products rather than an empty table.

Max products per store gives each store its own quota. Without it, one large catalogue eats the whole limit and a ten-store run looks like a scraper of a single shop.

What does it cost in time?

Each page of 250 products is one request. A 1,000-product catalogue is 5 requests, a 250-product catalogue is one. Measured on the Apify cloud at 512 MB: 1,000 products in 5.7 seconds, and 200 products from two stores in 3.7 seconds.

Stock detail is the slow mode, because it is one request per product: 200 products with stock counts and barcodes took 160 seconds. Collection mapping costs one pass over the store per collection, and large shops publish hundreds — one measured store publishes 525, another 1,346. Left unbounded that is the one setting that can spend ten minutes before the first row appears, so it runs inside two limits: the 40 largest collections and a budget of 60 pages across all of them. The run report says how many were actually mapped and whether the budget ran out, and the log says so too. Name the handles you actually need in Only these collections and exactly those are mapped, with a far larger budget, because then you have told the Actor what you want.

Both extras are opt-in for that reason — and neither of them costs extra money. You pay per product, whatever is in it.

What are the honest limits?

  • Some stores cannot be read at all. 4 of the 47 measured stores refuse the feed on the domain and publish no usable Shopify origin. For those the Actor reports the HTTP code it got and returns nothing rather than inventing data.
  • Only what the storefront publishes. Unpublished and draft products, and products hidden from the online store channel, are not in the feed and cannot be recovered from it.
  • No reviews. Product reviews live in third-party apps, not in the storefront feed. If you need them, that is a different job.
  • No metafields and no per-product SEO title or description. Measured, not assumed: a metafields path on a live store returns the product itself and ignores the path, and the SEO pair lives in the product page's HTML head. An Actor that lists those fields is parsing each product page as HTML, which is a different and much more fragile job than reading JSON.
  • No sales or revenue figures. Anyone quoting a Shopify store's revenue is estimating it, and this Actor does not guess.
  • Stock counts are what the shop chooses to publish. Merchants who hide inventory return no count, and the field stays empty rather than being filled with a zero that would read as "sold out".
  • Collection handles can differ between a custom domain and the Shopify origin. When a handle is missing, the log prints the handles the store really has.
  • Prices are in the store's own currency, as published. No conversion is applied, because a converted price is a guess with a timestamp attached.

How does this compare with the other Shopify products scrapers?

Feature by feature, against what the established Actors on this store publish:

This ActorTypical Shopify product scraper
Reads a store that blocks /products.jsonyes, measured 10 of 14no
Stock counts and barcodesyes, optionalsometimes, usually charged extra
Collection membership per productyesrarely
Price and stock change monitorbuilt inusually a separate Actor
Shopify's own recommended productsyes, includedleader charges extra
Names the reason for an empty runyes, 7 reasonsno
Per-store quotayesno
Product metafields and SEO pairnoone competitor has them
Price per 1,000 products$1.20$1.00 to $18.00

Where they are ahead is listed above under honest limits: product reviews, metafields and the per-product SEO pair. All three need the product page's HTML rather than the JSON this Actor reads.

Frequently asked questions

Do I need a Shopify API key or a private app? No. Everything this Actor reads is what the storefront serves to any visitor. There is no login, no token and no app install.

Does it work on myshopify.com addresses? Yes, and you can paste one directly. If you paste a custom domain that refuses the feed, the Actor looks the Shopify origin up for you and says in the log and in catalog_source which route it used.

Will it read a store that is not on Shopify? No, and it will tell you so instead of returning an empty table with no explanation. Non-Shopify domains end the run with store-not-readable and the HTTP code the domain gave.

How do I get just the products that are on sale? Switch on On sale only. Discounts are computed from the shop's own compare-at price: discount_percent is the drop from the highest compare-at price down to the cheapest variant price.

Can I get one row per variant instead of one per product? The dataset gives one row per product with variants nested, which keeps billing honest — you pay per product, not per colour. Use the dataset API's unwind parameter on variants to flatten it into one row per variant at export time.

How big can a catalogue be? The walk goes up to 250 pages of 250 products, which is 62,500 products. The largest catalogue in the measured sample was 19,524.

Does it slow the store down? No. A full 1,000-product catalogue is five requests, the same five a price comparison site would make. The client backs off on its own when a host asks it to, and the run report shows any host that did.

What happens if the run is cut off half way? Rows already pushed are yours, and in change mode a product is marked delivered only after it has actually been written. The next run picks up exactly what did not arrive, so nothing is silently skipped and nothing is billed twice.

Why is total_inventory empty? Either stock detail is switched off, or the merchant does not publish inventory for that product. Both are normal, and the field stays empty rather than pretending the answer is zero.

Which currency are prices in? The store's own, given in store_currency and currency. A store that sells in several markets publishes one catalogue per market; point the Actor at the market address you care about.

What else is this called?

Names buyers use: Shopify scraper, Shopify products API, Shopify inventory levels, Shopify variant SKU, myshopify store data, store catalog export. Each row carries the variant SKU and the inventory number, so stock work and catalog work are the same run.

More field names: store origin canonical domain (store_origin), barcode and GTIN product export (barcode_list). price_min together with compare_at_price_max is the data people read for MAP violation monitoring; the Actor returns the prices, the policy check is yours.