Shopify Scraper — Stores & Products avatar

Shopify Scraper — Stores & Products

Pricing

from $1.40 / 1,000 products

Go to Apify Store
Shopify Scraper — Stores & Products

Shopify Scraper — Stores & Products

Shopify store scraper and Shopify products scraper in one run: every product and variant with GTIN barcodes, plus store-level launch velocity, price positioning, discounting, brand model and the apps the store runs.

Pricing

from $1.40 / 1,000 products

Rating

0.0

(0)

Developer

Spool

Spool

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

6 days ago

Last modified

Share

Point it at a Shopify store. Get every product and variant with GTIN barcodes, and a read on the business itself — how fast it ships, where it prices, how hard it discounts, and which apps it pays for.

store deathwishcoffee.com 0.03 new/day median $40 dormant · 8 apps · Dawn theme
product Coconut Caramel Coffee / 1 bag 810063344313 $13.99 subscribe & save 10%
product Valhalla Java / 5 lb bag 810063342159 $89.99 in stock, tracked

Most Shopify scrapers give you rows of products. This gives you the catalog and what the catalog says about the merchant, from the same read.


Two records, one run

Set output and you get either or both:

The store record — one row per store. Launch velocity measured properly, price bands in USD, discount depth, stock pressure, brand model, catalog scale, and the apps and theme the storefront loads.

The product records — one row per variant (or per product), each with the GTIN barcode, price, stock, subscription plan and collection membership.

Use store alone for a fast, cheap scan of a hundred competitors. Use products for a pure catalog extract. Use both when you want the business and its catalog together.


The barcode

/products.json — the endpoint nearly every Shopify scraper reads — does not contain the barcode.

GTIN / UPC / EAN is what connects a Shopify product to the rest of commerce: to Amazon, to Google Shopping, to a distributor's price list, to your own ERP. Without it you have product titles and manual matching.

It lives one layer down, at /products/{handle}.js, which costs one request per product. Measured across eight stores it came back on 97–100% of variants for six of them. The other two are the merchant's doing, not the endpoint's: Rothy's is 81% — its newest products are fully barcoded, its oldest half are not — and Death Wish Coffee is 26%:

rothys.com 81% organicbasics.com 97%
allbirds.com 98% gymshark.com 100%
finisterre.com 100% kith.com 100%
tentree.com 100% deathwishcoffee.com 26%

The same endpoint carries four more things /products.json lacks: subscription plans with the real subscriber price, wholesale volume breaks, order minimums, and inventory_management — which decides whether "in stock" means anything, because when a merchant does not track inventory available is a default rather than a fact. inventoryTracked tells you which kind you are looking at.


Velocity that is actually right

The store record reports how fast the store ships new product. Getting that right took three attempts, because the obvious methods are all wrong.

published_at looks like a launch date and is not — it is rewritten in bulk whenever a store replatforms. Allbirds spans 7.6 years by created_at and 83 days by published_at. Deriving a rate from it is wrong in both directions at once:

from published_at measured properly
allbirds.com 3.54/day 0.00/day overstated without bound
gymshark.com 12.39/day 0.00/day overstated without bound
finisterre.com 0.28/day 1.86/day 6.6x too low
rothys.com 0.22/day 0.81/day 3.7x too low

So both rates are reported: perDay counts products genuinely created in the last 90 days, publishPerDay counts what was published. The gap is the finding — Gymshark has created nothing since May while publishing sixteen a day, which is re-merchandising, not dying, and reads as republishing.

And it says when it does not know. On a store too large to read in full, velocityConfidence comes back low and the number is a lower bound rather than a wrong answer. It fires only when it should: Organic Basics reads 1.76/day from 250 products against 1.77/day from its full 1,816.


Which apps the store pays for

The catalog says what a store sells. The storefront says how it operates.

deathwishcoffee.com 8 apps Klaviyo · Postscript · Rebuy · Smile.io · Afterpay Dawn
rothys.com 2 apps Intelligems · Yotpo DNA

Over 50 apps recognised across email/SMS, reviews, support, subscriptions, CRO, loyalty, search, analytics, post-purchase, payments and affiliate. Anything third-party that cannot be named is still reported in otherThirdPartyHosts, so a store running something unusual shows up rather than looking simpler than it is. themeIsStock flags the free Shopify themes — a store still on stock Dawn has not invested in design.


What the merchant says is selling

/collections.json is the store's own merchandising, and it is public. A product in the merchant's best-sellers collection is a demand signal the store is publishing itself.

Reading all of it is wasteful — Rothy's has 196 collections, Allbirds 1,000, mostly colour and size groupings. So collections are ranked, not matched: best sellers first, then new arrivals, trending, sale, each capped so one kind cannot crowd out another, with internal plumbing (

Discount Eligible Products Excluding Markdown
, test and hidden collections) dropped. On Allbirds that turns 1,000 collections into 23 useful reads.


Prices you can compare

products.json carries prices as bare numbers with no currency in it. A UK store showing 50 and a US store showing 50 are £50 and $50. Currency is read from the store profile and every price reported twice, in store currency and USD. When no rate is available the USD fields are null, never a guess.

A trap worth naming: /products.json gives decimal strings while /products/{handle}.js gives integer cents. Mixing them divides enriched prices by a hundred. Each source is normalised on the way in.


Quick start

{ "storeUrls": ["rothys.com", "deathwishcoffee.com"] }

Domains, full URLs and product links all work. www. is stripped, and www.x.com and x.com merge so you are never billed twice for one store.


Recipes

Scan 100 competitors fast — one row each, no per-product cost

{ "storeUrls": ["..."], "output": "store" }

Build a Google Merchant feed

{ "storeUrls": ["rothys.com"], "output": "products", "requireBarcode": true }

feedReady says whether a row has everything Merchant Center needs — id, title, link, image, brand, price, availability and GTIN.

Every subscription product and what subscribing saves

{ "storeUrls": ["deathwishcoffee.com"], "requireSubscription": true }

Everything discounted under $50 across rivals

{ "storeUrls": ["a.com", "b.com"], "onlyOnSale": true, "maxPrice": 50 }

Every option

OptionDefaultWhat it does
storeUrls—Required. Domains, URLs or product links
outputbothboth, products, or store for a fast one-row scan
enrichtrueBarcodes, subscriptions, wholesale. One request per product
rowsPervariantvariant for feeds and price monitoring, product for analysis
detectAppstrueApps and theme on the store record. One request per store
maxProductsPerStore1000Read depth — the main lever on time and cost
collectionsModekeykey reads signal collections only, all, or none
requireBarcodefalseKeep only rows with a GTIN
onlyInStock / onlyOnSalefalseStock and discount filters
requireSubscriptionfalseSubscription products only
minPrice / maxPrice—Compared in USD, so consistent across stores
vendors / productTypes / tags—Filter by brand, type or tag
includeDescriptionfalseAdds full descriptions. Large
maxConcurrency5Parallel requests. Lower it if a store returns 429

What it covers, honestly

About five stores in six work. Of 18 real stores tested, 15 served the catalog. The rest return not_a_shopify_store. A miss is always reported.

Barcode coverage is the merchant's choice. Six of eight stores returned 97–100%. Rothy's returned 81% — its older products were never given one — and Death Wish Coffee 26%, because they have not entered them. A null barcode means the merchant left it empty; if the store throttled the run instead, the log says so and RUN_SUMMARY counts the rows affected.

catalogSize is exact only when catalogComplete is true. Otherwise it is null rather than a number that would be wrong.

App detection reads what the storefront loads. A retailer that self-hosts its scripts shows fewer apps than it runs — Gymshark detects only one — so a low count on a big brand means "not visible", not "not installed".

Currency is what the store serves you. Shopify switches storefront by geography.

Direct requests first; Apify Proxy only for what a store refuses. Shopify throttles the platform's shared addresses by address, not by rate — Allbirds enriched 30% of its catalog directly from the platform and 96% once the refused products were retried through Apify Proxy, while the same requests pass 100% from an ordinary home connection. So every product is tried directly, and only the ones the store turned down go through the proxy, one fresh session each. It is included in the price; the log says how many it recovered, and anything still missing is reported as missing rather than passed off as "no barcode".

Prices and stock are a snapshot, true at the moment of the run.


Everything read is a public endpoint Shopify serves by design, requiring no authentication and permitted by robots.txt: /products.json, /products/{handle}.js, /collections.json and /meta.json.

What this Actor deliberately does not do: /cart/add.js reveals a variant's exact remaining stock if you request an absurd quantity, and tools advertising "real inventory counts" appear to use it. Shopify's default robots.txt disallows /cart on every store checked, and /search with it. Neither is touched here. Exact stock counts are not worth ignoring a merchant's robots.txt for.

Everything returned is catalog data. No customer data, no personal information.


When a store can't be read

errorMeaning
not_a_shopify_storeNo public catalog at /products.json
catalog_blockedThe store returned 403 or 429
catalog_emptyCatalog exists but has no products
all_products_filtered_outRead fine, but your filters matched nothing
invalid_inputCouldn't read a domain from that value

A breakdown is saved to the key-value store as RUN_SUMMARY.


FAQ

How do I scrape a Shopify store? Pass the domain. It reads the public catalog every Shopify store serves and returns the products plus a summary of the store itself.

Is this a Shopify store scraper or a Shopify products scraper? Both, from one read. As a Shopify store scraper it returns one summary row per store — launch velocity, pricing, discounting, brand model and installed apps. As a Shopify products scraper it returns every product and variant with its GTIN barcode, price, stock and collections. Set output to get either on its own.

How do I get GTIN or UPC codes from Shopify? Leave enrich on. Barcodes come from /products/{handle}.js, which /products.json does not include.

How is this different from other Shopify scrapers? Most read one endpoint and return titles, prices and images. This adds barcodes, subscription plans with real subscriber prices, wholesale breaks, whether stock is actually tracked, the merchant's own best-seller lists, the apps the store runs, and a correct launch-velocity measurement.

Can I scrape every Shopify store at once? No. You supply the store list. It reads the stores you name, thoroughly.

Will it get me blocked? Plain HTTPS requests at a concurrency you control. Lower maxConcurrency if a store returns 429.


How to use it

  1. Open the Actor and paste store domains into Shopify stores.
  2. Choose What to return — both, products for a catalog extract, or store for a fast one-row-per-store scan.
  3. Click Start. Two stores with 800 products take about 45 seconds.
  4. Open the Stores view for the business read, or Merchant feed / Pricing / Subscriptions for the products. Filter on recordType when exporting.
  5. Schedule it to track competitors over time, or call it through the API and integrations.

How much does it cost?

Pay per event. Two things are charged: one store summary per store, and one charge per product — a product with 20 variants is 20 rows and one charge.

Planstore summaryproduct, enrichedproduct, catalog only
Free$0.02$0.002$0.0005
Bronze$0.018$0.0018$0.00045
Silver$0.016$0.0016$0.0004
Gold$0.014$0.0014$0.00035

Actor start is $0.002. Stores that could not be read cost nothing. If a store throttles the per-product requests, the products that could not be enriched are billed at the catalog-only price, not the enriched one — you pay for barcodes only when you get them.

Worked examples

  • output: "store" on 100 competitors → 100 × $0.02 = $2.00, no per-product cost at all
  • 3 stores, 2,000 products, both records → 3 × $0.02 + 2,000 × $0.002 = $4.06
  • Rothy's + Death Wish at the defaults (812 products, 2 summaries) → $1.66
  • The free plan's $5 credit covers about 2,400 enriched products

More Shopify tools from spool

All three read the same public endpoints, respect robots.txt, and report a miss rather than guessing.


Support

Open an issue on the Issues tab and you will get a reply, usually the same day. Requests for extra fields are welcome.