Shopify Store Scraper: Products, Prices, Variants & Images avatar

Shopify Store Scraper: Products, Prices, Variants & Images

Pricing

from $0.50 / 1,000 products

Go to Apify Store
Shopify Store Scraper: Products, Prices, Variants & Images

Shopify Store Scraper: Products, Prices, Variants & Images

Scrape every product of any Shopify store by domain, or single products by URL: title, vendor, type, tags, description, price and compare-at price, availability, variants with SKU and options, images, dates and URL. Several stores per run, a cap per store, a free store row with a verdict.

Pricing

from $0.50 / 1,000 products

Rating

0.0

(0)

Developer

Hyperbach

Hyperbach

Maintained by Community

Actor stats

0

Bookmarked

1

Total users

1

Monthly active users

10 hours ago

Last modified

Features

Limited permissions

Share

Every product of a Shopify store, logged out: store domains, store URLs, *.myshopify.com addresses or product URLs in, one row per product out — the title, handle, vendor, product type, tags, the description as text and HTML, the lowest and highest price, the compare-at price, on sale, in stock, every variant with its SKU, options, availability and prices, the options, the image URLs, the created, updated and published dates, the product URL and the store's currency — several stores in one run, a cap per store. One free store row per input carries a completeness verdict: COMPLETE (the feed's own end, checked against the store's own product count), CAPPED (your limit), TRUNCATED (Shopify's 25,000-product feed limit) or PARTIAL. A password-protected store, a domain with no Shopify feed, an unknown store: free typed rows. No login, no API key, no browser, proxies handled for you.

$1.00 per 1,000 product rows on the Free plan, $0.85 on Starter. A run start is $0.0013. The default input returns about 50 rows for about $0.0513. A value that names nothing is a free row that says why.

A Shopify product page in a browser: a glass cold brew maker on sale, in two sizes and two colours. Beside it, the row this Actor returns for the product, with price, price_max and compare_at_price as numbers, on_sale, availability, the vendor, the product type and the currency.

Why this Shopify products scraper

  • Several stores in one run. Give store domains (allbirds.com), store URLs, *.myshopify.com addresses or product URLs: each store gets its own cap (maxProductsPerStore), and the run's cap sits on top (maxItems). The store's name and currency come with every row.
  • Every field the store publishes. The title, handle, vendor, product type and tags; the description as plain text and as HTML; the lowest and highest variant price, the highest compare-at price and whether any variant is on sale; in stock; every variant with its id, title, SKU, named options, availability, price, compare-at price, weight and shipping flags; the options with their values; the image URLs; the created, updated and published dates; the product URL.
  • Whole catalogues, checked. Products come 250 a call in the store's own feed, page after page, to the feed's own end, and the store's own product count says whether the walk got them all: our test read Allbirds' 692 of 692 and Death Wish Coffee's 153 of 153 (2026-10-22). The free store row says how many products were delivered from how many pages and why the walk stopped: the feed's end (COMPLETE), your cap (CAPPED), Shopify's feed limit of 25,000 products a store (TRUNCATED, with the store's count) or a refusal or the time limit (PARTIAL).
  • Single products by URL. A product URL (/products/<handle>, with or without a collection or a ?variant=) reads that product with the same fields, its availability per variant included.
  • Honest runs. A password-protected store is a free password row, a domain that serves no Shopify feed (a headless store or not a Shopify store) a free no_feed row with the hint to give its *.myshopify.com address, an unknown store or product a free not_found row (two exits agreed), a value that is not a store a free invalid row, a repeated value or the same store under two addresses a free duplicate row. Every input gets a row; a product is charged once a run.
  • Fast and light. Plain HTTP on the store's own public feed: 250 products a call, about 0.6 KB on the wire a product. A store of 692 products took 3 calls on our test.

Who it's for

  • E-commerce and pricing teams — a competitor's whole catalogue with prices, compare-at prices and availability per variant, read again on a schedule to see what changed.
  • Market and product researchers — the assortments of a list of brands in one run: vendors, product types, tags, price bands, new products by their created and published dates.
  • Data and analytics teams — typed rows keyed by store and product_id, a free row per store with the status, the store's count and the verdict, and a run summary to check a pipeline against.

Quick start

A store's first 50 products and one product by URL

{
"startUrls": [
"https://www.allbirds.com",
"https://www.allbirds.com/products/mens-tree-runners"
],
"maxProductsPerStore": 50
}

Two whole catalogues

{
"startUrls": [
"deathwishcoffee.com",
"allbirds.com"
]
}

A headless store on its Shopify address

{
"startUrls": [
"gymshark.myshopify.com"
],
"maxProductsPerStore": 500
}

Output

One row per product (and one free store row per input):

fieldmeaning
row_typeproduct (one product, charged) or store (the input's row with the walk's verdict; free).
input_indexThe 1-based position in startUrls of the input this row answers.
input_valueThe input as you gave it (the domain or URL).
input_kindWhat the input names: store, product, invalid or duplicate.
storeThe store's host as given (www.allbirds.com).
store_nameThe store's name, as the store publishes it.
currencyThe store's currency (ISO 4217: USD, GBP …); every price on the row is in it.
statusA store row: ok, password, no_feed, not_found, invalid, duplicate, failed or not_read (see the FAQ). A product row: ok.
status_detailWhy, in words, when the status is not ok.
scraped_atWhen the row was read (ISO 8601, UTC).
product_idShopify's product id.
handleThe product's handle (the last part of its URL).
titleThe product's title.
vendorThe product's vendor (the brand), as the store sets it.
product_typeThe product's type, as the store sets it (null when none).
tagsThe product's tags.
descriptionThe product's description as plain text (paragraphs on their own lines).
description_htmlThe product's description as the store's HTML.
priceThe lowest variant price, in currency.
price_maxThe highest variant price, in currency.
compare_at_priceThe highest compare-at ("was") price among the variants, in currency; null when none is set.
on_saleTrue when a variant's compare-at price is above its price.
availableTrue when at least one variant is available to buy.
variant_countHow many variants the product has.
variantsOne object per variant: variant_id, title, sku, options (option name → value), available, price, compare_at_price, grams, requires_shipping, taxable, position, image, created_at, updated_at.
optionsThe product's options with their values ([{name: Size, values: [8, 9, 10]}]).
image_countHow many images the product has.
featured_imageThe product's first image.
imagesEvery image URL of the product, in the store's order.
created_atWhen the product was created in the store (ISO 8601, the store's time zone).
updated_atWhen the product was last changed (ISO 8601, the store's time zone).
published_atWhen the product was published to the store (ISO 8601, the store's time zone).
urlThe product's page.
store_urlA store row: the store's address (or the product's, for a product URL).
products_countA store row: the store's own count of published products (null when the store did not answer).
products_deliveredA store row: the product rows delivered for this input.
pages_readA store row: how many calls of the store's feed were read.
verdictA store row: COMPLETE (the feed's own end, at the store's count), CAPPED (your cap), TRUNCATED (the feed ended short of the store's count, or Shopify's 25,000 limit) or PARTIAL (a refusal or the time limit).
verdict_detailA store row: the verdict in words — products delivered, pages read, the store's count, why the walk stopped.

Example record:

{
"row_type": "product",
"input_index": 2,
"input_value": "deathwishcoffee.com",
"input_kind": "store",
"store": "deathwishcoffee.com",
"store_name": "Death Wish Coffee",
"currency": "USD",
"status": "ok",
"status_detail": null,
"scraped_at": "2026-10-22T21:24:43Z",
"product_id": 6703976153143,
"handle": "shell-toddy-system",
"title": "Toddy Cold Brew System",
"vendor": "Toddy",
"product_type": "Storage & Brewing",
"tags": [
"At Home",
"Merch",
"Storage & Brewing"
],
"description": "Coffee as cold as your heart.\nCold brew all year round? Sign me up. The Toddy Cold Brew System is a nonelectrical coffee maker that produces a smooth, rich liquid concentrate to m …(truncated for display)",
"description_html": "<h4>Coffee as cold as your heart.</h4>\n<p>Cold brew all year round? Sign me up. The Toddy Cold Brew System is a nonelectrical coffee maker that produces a smooth, rich liquid concentrate to make the best cold brew right from your kitchen. Skip the cafe lines and B.Y.O.B—be your own barista. <br></p>",
"price": 45.0,
"price_max": 45.0,
"compare_at_price": null,
"on_sale": false,
"available": true,
"variant_count": 1,
"variants": [
{
"variant_id": 39897030885431,
"title": "Default Title",
"sku": "UOTODCBS",
"options": {
"Title": "Default Title"
},
"available": true,
"price": 45.0,
"compare_at_price": null,
"grams": 1084,
"requires_shipping": true,
"taxable": true,
"position": 1,
"image": null,
"created_at": "2022-08-31T14:34:37-04:00",
"updated_at": "2026-10-22T17:24:42-04:00"
}
],
"options": [
{
"name": "Title",
"values": [
"Default Title"
]
}
],
"image_count": 3,
"featured_image": "https://cdn.shopify.com/s/files/1/0271/7209/products/Toddy-100.jpg?v=1666098019",
"images": [
"https://cdn.shopify.com/s/files/1/0271/7209/products/Toddy-100.jpg?v=1666098019",
"https://cdn.shopify.com/s/files/1/0271/7209/products/Toddy-101.jpg?v=1666098019",
"https://cdn.shopify.com/s/files/1/0271/7209/products/Toddy-104.jpg?v=1666098025"
],
"created_at": "2022-08-31T14:34:37-04:00",
"updated_at": "2026-10-22T17:24:42-04:00",
"published_at": "2024-08-06T09:05:03-04:00",
"url": "https://deathwishcoffee.com/products/shell-toddy-system",
"store_url": null,
"products_count": null,
"products_delivered": null,
"pages_read": null,
"verdict": null,
"verdict_detail": null
}

Pricing

Pay for the products a run delivers, plus one run start; no platform usage is billed to you. The product price falls with your Apify plan.

eventunitFreeStarterScaleBusiness
Product (product)per 1,000$1.00$0.85$0.70$0.50
Run start (start)per run$0.0013$0.0013$0.0013$0.0013

A product is $1.00 per 1,000 on the Free plan ($0.001 a product). Each run that delivers a charged product also pays the run start, $0.0013 once; a run that delivers no charged product pays nothing at all. So a store of 700 products costs $0.7013 on the Free plan and $0.5963 on Starter; several stores in one run share one start. The free store row of every input, a password-protected store (password), a domain with no Shopify feed (no_feed), an unknown store or product (not_found), a value that is not a store (invalid), a repeated value (duplicate), a store left at the cap or the time limit (not_read) and a refusal the run could not pass (failed) are never charged. A product is charged once a run. A run that stops early (a timeout, an abort) is charged for the products it delivered. maxItems counts the charged rows; before the first request the log names the price of each row at your plan and the most the run can cost. Enterprise plans have their own rates: the Actor's Pricing tab shows the price for your plan.

Usage patterns

  • Stores and products — startUrls takes a store's domain (allbirds.com), a store URL (https://www.allbirds.com/ — any page of the store reads the whole store), a store's own Shopify address (yourstore.myshopify.com) or a product URL (https://www.allbirds.com/products/mens-tree-runners). A headless store (its site is not Shopify's storefront) serves no feed on its own domain: give its *.myshopify.com address. A collection URL is not read as a collection: give the store's URL.
  • What a row is — row_type = product: one product with the store's record; variants holds one object per variant. row_type = store: the free row of one input value — products_count (the store's own count of published products), products_delivered, pages_read, the verdict with its verdict_detail, and the status with its reason.
  • How far a store goes — maxProductsPerStore = 0 (the default) reads every product the store's feed lists — up to 25,000, Shopify's own limit for the feed. maxProductsPerStore = 100 is the first 100 in the feed's order. maxItems caps the charged rows of the whole run. The store row's verdict says where the walk stopped and why.

Input configuration

fieldtypedefaultwhat it does
startUrlsarrayWhat to read: a store's domain (allbirds.com), a store URL (https://www.allbirds.com/), a store's own Shopify address (yourstore.myshopify.com) for every product of the store, or a product URL (https://www.allbirds.com/products/mens-tree-runners) for one product. Up to 1,000 values a run. A headless store whose own domain serves no Shopify product feed reads on its *.myshopify.com address. A value that is not a store gets a free invalid row, a value given twice a free duplicate row.
maxProductsPerStoreinteger0Stop each store after this many product rows (0 = every product the store's feed lists; Shopify's feed lists at most 25,000 products a store).
maxItemsinteger0Stop the run after this many product rows across every store (0 = no limit). Store rows are free and not counted; a store left unread at the cap gets a free not_read row.
feedbackstringA note to the people who maintain this Actor: what you needed and did not get. For example a missing field, a wrong or empty result, or the Actor you switch from or to, and why. The run does not read it: the rows and the charges stay the same. A person reads every note.

Errors

A run does not fail because of your input. When an input cannot be used — a date that is not a date, an id in the wrong form, two settings that contradict each other — the run ends Succeeded, charges nothing, and says what to change:

  • the status message starts with INPUT REJECTED;
  • the dataset holds one row, {"error": true, "code": "…", "message": "…"}, and no results;
  • the key-value store record ERROR holds the same object.

From code, check error on the first row before reading results.

A store whose products are not public is not an error: a password-protected store gets one free store row with status password, and the run goes on with the others. A run that ends Failed is a fault on our side, never your input; it charges nothing, and we are alerted.

FAQ

Which status is which?

A store row's status is ok (products served), password (the store is password-protected: no product is public), no_feed (the domain serves no Shopify product feed: not a Shopify store, or a headless one — give its *.myshopify.com address), not_found (no such store, or no such product; two exits agreed), invalid (a value that is not a store or product URL, or a collection URL), duplicate (the same value, or the same store under another address: read once), failed (a refusal or an error; free) or not_read (left at maxItems, the charge limit or the run's time limit). A product row's status is ok. When Shopify answers "too many requests" (HTTP 429) on every try of a call, the run waits the 60 seconds Shopify asks for and tries fresh connections again, up to three times while the run's time limit allows; the failed row says how many waits it took.

Does it give the exact stock count?

No. The store's public feed says whether each variant is available, not how many are in stock; the row carries available per variant and per product.

Why are some prices without a compare-at price?

The compare-at price is the store's own "was" price; most products have none, and a store that writes 0 means none. on_sale is true when a variant's compare-at price is above its price.

Are the prices in my currency?

The prices are the store's own catalogue prices in the store's currency (currency on every row). On our test the feed gave the same prices to visitors from the US, Germany and the UK.

Does it need a Shopify account or API key?

No. Everything is read from the store's public product feed, logged out.

Integration

JavaScript

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('hyperbach/shopify-products-scraper').call({"startUrls": ["https://www.allbirds.com", "https://www.allbirds.com/products/mens-tree-runners"], "maxProductsPerStore": 50});
const { items } = await client.dataset(run.defaultDatasetId).listItems();

Python

from apify_client import ApifyClient
client = ApifyClient('YOUR_TOKEN')
run = client.actor('hyperbach/shopify-products-scraper').call(run_input={'startUrls': ['https://www.allbirds.com', 'https://www.allbirds.com/products/mens-tree-runners'], 'maxProductsPerStore': 50})
items = client.dataset(run['defaultDatasetId']).list_items().items

CLI

$apify call hyperbach/shopify-products-scraper --input '{"startUrls": ["https://www.allbirds.com", "https://www.allbirds.com/products/mens-tree-runners"], "maxProductsPerStore": 50}'

REST

curl -X POST "https://api.apify.com/v2/acts/hyperbach~shopify-products-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
-H 'Content-Type: application/json' -d '{"startUrls": ["https://www.allbirds.com", "https://www.allbirds.com/products/mens-tree-runners"], "maxProductsPerStore": 50}'

AI agents and MCP

Apify's MCP server offers this Actor as a tool to Claude, Cursor, VS Code and any other MCP client. Connect https://mcp.apify.com?tools=hyperbach/shopify-products-scraper and ask, for example: "List the first 50 products of the Shopify store allbirds.com with prices and variants". The agent fills the input from the table above and reads the rows from the run's dataset. The input names are the ones in the table above. An agent or a person can add feedback to any run and say what was needed and not delivered: a missing field, a wrong or empty result, the Actor switched from or to, and why. The run returns the same rows at the same price, and a person reads every note.

n8n, Make and Zapier

The Apify node in n8n, the Apify app in Make and the Apify integration in Zapier run this Actor on a schedule or from a trigger and read its dataset. A webhook on the run's end (Integrations on the Actor page) sends the rows on to your own endpoint. Nothing to install on our side.

Support

Disagree with a charge? Write to apify@hyperbach.com with the run ID. We will ask Apify to compensate you, and the charge comes back to your Apify account as platform credit.

Did it do the job? A short review on the Store, at any rating, helps the next buyer choose.

See also

  • Amazon Product Details Scraper — Amazon products by ASIN, URL or UPC / EAN / ISBN on 21 Amazon stores: the buy-box price, every best-seller rank, the rating and the review summary.
  • AliExpress Product Details Scraper — AliExpress products by URL or item id: every SKU with its own price and stock, in the country and currency you choose.
  • Alibaba Products Scraper — the products of an Alibaba.com search: price range, minimum order and the supplier with its country and verification.
  • Walmart Products Scraper — Walmart products by search, category or URL: the price with the was-price, the seller and the rating.
  • Coles Scraper — Coles Australia products and specials, with the price, the was-price and the unit price.
  • Woolworths Scraper — Woolworths Australia products and specials, with the barcode, the unit price and nutrition.

This page is generated from the Actor's schemas and a live sample — it cannot describe a field the Actor does not have.

⌂ 换网址