Shopify Store Product Scraper avatar

Shopify Store Product Scraper

Pricing

$4.00 / 1,000 product returneds

Go to Apify Store
Shopify Store Product Scraper

Shopify Store Product Scraper

Shopify Store Product Scraper reads any storefront's own products.json and returns one row per product: title, vendor, type, tags, price range, compare-at price, sale flag, stock, variants and images. No API key. onlyNewSinceLastRun defaults ON, so a repeat run bills only for changes.

Pricing

$4.00 / 1,000 product returneds

Rating

0.0

(0)

Developer

angel nguyen

angel nguyen

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

18 hours ago

Last modified

Share

Shopify Store Product Scraper extracts the full public catalogue of any Shopify storefront and returns one flat, typed row per product — title, vendor, product type, tags, price range, compare-at price, sale flag, stock, every variant with its SKU, images and description text. You give it a domain; you get back a spreadsheet of that store's products.

Shopify Store Product Scraper

Every Shopify storefront publishes its own catalogue at /products.json. It is a first-party endpoint that Shopify serves by design, it is the same document the storefront's own JavaScript reads, and it needs no API key, no app install, no partner account and no login. This Actor reads that endpoint, pages it properly, and normalises what comes back.

Key features

FeatureDetail
Inputone or more storefront domains — allbirds.com or any URL on the store
Coveragethe whole published catalogue, paged 250 at a time up to your cap
Variantsevery variant with id, SKU, price, compare-at price, availability and weight
Derived pricingpriceMin, priceMax, compareAtMax and an onSale flag computed from the variants
Derived stockvariantsInStock and an inStock boolean, from Shopify's own per-variant available
Incremental runsonlyNewSinceLastRun is on by default — a repeat run bills only for products that changed
Pre-charge filtersproduct type and in-stock filters run before billing, so filtering lowers your cost
Honest failuresa domain that is not a Shopify store is unavailable with a reason, and is never charged
Outputone row per product, flat and typed — JSON, CSV, Excel, XML

Shopify store product scraper: how it works

  1. Normalise the domain. allbirds.com, https://allbirds.com and https://allbirds.com/collections/mens all resolve to the same storefront. Anything that is not a usable host is reported as unavailable rather than silently skipped.
  2. Page the catalogue. The Actor requests /products.json?limit=250&page=N until a page comes back short or empty, until your maxProductsPerStore cap is reached, or until an internal 40-page ceiling. Whenever a cap actually truncated a store, it says so in the log — a cap hit silently would read as "that is the whole catalogue".
  3. Check that this is really a Shopify store. A domain that is not one answers /products.json with its own ordinary HTML page, and HTTP 200. The Actor requires a parseable JSON body with a products array before it treats a response as a catalogue. This distinction matters more than it sounds: without it, "this is not a Shopify store" and "this store has no products" collapse into the same empty result, and the second one is a claim about the merchant.
  4. Filter, then charge, then deliver. Filters run first so you are never billed for rows a filter then discards. Charges are applied before records are handed over and delivery is truncated to whatever your budget actually allowed, so the rows you receive always equal the rows you paid for.

Tutorial

  1. Open the Actor and put one or more storefront domains into Shopify store domains. The prefilled example reads allbirds.com and www.kith.com.
  2. Set Max products per store. Start at the default 100 while you look at the shape of the output; raise it when you know you want the whole catalogue.
  3. Leave Only products that are new or changed since your last run switched on. It is on by default and it is what makes a scheduled run cheap.
  4. Click Start. A single store of 100 products finishes in a few seconds.
  5. Open the Storage tab and export to CSV or Excel, or read the same rows from the dataset API.
  6. To keep it current, open Schedules and run it daily. With the incremental setting on, each subsequent run returns only the products whose Shopify updated_at has moved.

Input

The minimum input is a single domain. Everything else has a working default.

{
"storeDomains": ["allbirds.com", "www.kith.com"],
"maxProductsPerStore": 100,
"onlyNewSinceLastRun": true,
"includeOutOfStock": true,
"productType": "",
"includeVariants": true
}
FieldTypeDefaultMeaning
storeDomainsarray of stringStorefront domains or any URL on the store. Required.
maxProductsPerStoreinteger100Hard cap per store. Bounds runtime and spend.
onlyNewSinceLastRunbooleantrueReturn only products whose Shopify updated_at moved since your last run. On by default.
includeOutOfStockbooleantrueKeep products with no available variant. Turning this off filters before charging, so it lowers your bill.
productTypestring(empty)Keep only this Shopify product_type. Also filters before charging.
includeVariantsbooleantrueAttach the full variant array. Does not change what you are charged — billing is one event per product either way.

Output

One row per product. The block below is a row this Actor actually wrote, copied byte for byte out of the dataset of run JAle7TcRM2KTZwbRJ (2026-09-04), whose input was {"storeDomains":["allbirds.com"],"maxProductsPerStore":1,"onlyNewSinceLastRun":false,"includeOutOfStock":true,"includeVariants":false}. Nothing in it is edited, elided or illustrative.

{
"storeDomain": "allbirds.com",
"productId": 7369944137808,
"title": "Free Returns Coverage",
"handle": "free-returns-coverage",
"url": "https://allbirds.com/products/free-returns-coverage",
"vendor": "re:do",
"productType": "return,package_protection",
"tags": [
"exclude_rebuy",
"klaviyo_reviews_exclude",
"NOREVIEWS",
"redo-package-protection"
],
"publishedAt": "2026-09-02T11:33:11-07:00",
"createdAt": "2026-06-05T07:14:57-07:00",
"updatedAt": "2026-09-03T19:41:44-07:00",
"descriptionText": null,
"priceMin": 0.8,
"priceMax": 2.98,
"compareAtMax": 2.98,
"onSale": true,
"variantCount": 2,
"variantsInStock": 2,
"inStock": true,
"imageCount": 1,
"featuredImage": "https://cdn.shopify.com/s/files/1/1104/4168/files/Screenshot_2026-06-11_at_10.12.01_AM.png?v=1781197934",
"options": [
"Price"
],
"scrapedAt": "2026-09-04T02:41:44.455Z"
}

That run had includeVariants switched off, so its rows carry no variant array. With the setting on — which is the default — every row also carries variants. This is the first of the thirteen variants on mens-strider-explore, copied out of the dataset of run fd7s6mPslZ2FcfHjo (2026-09-03, input {"storeDomains":["allbirds.com"],"maxProductsPerStore":5,"onlyNewSinceLastRun":false,"includeOutOfStock":true,"includeVariants":true}):

{
"variantId": 41334293889104,
"title": "8",
"sku": "A11768M080",
"price": 130,
"priceRaw": "130.00",
"compareAtPrice": null,
"available": false,
"requiresShipping": true,
"grams": 919
}

Three fields above are derived, not copied. onSale is true only when a compare-at price actually exceeds the lowest live price — it reads true on the product row because compareAtMax 2.98 is greater than priceMin 0.8. inStock is the OR over Shopify's own per-variant available flag, so a product every one of whose variants is sold out reads false rather than being dropped. priceRaw keeps the exact decimal string Shopify published, so you can reconcile against a merchant's own figures without a float rounding in the way.

Pricing

This Actor is priced per event, so you pay for what it delivers rather than for how long it runs.

EventPriceCharged when
product-returned$0.004Once per product row written to the dataset

There is no per-run start fee, no monthly minimum, and no charge for platform usage beyond Apify's own. A run that returns nothing costs nothing at all.

What a repeat run actually costs, worked through

This is the number that matters, and it is the reason onlyNewSinceLastRun defaults to on. Take one storefront with a 500-product catalogue, polled daily for a month, where roughly 20 products change on a given day:

SettingProducts billed per dayMonthly cost
onlyNewSinceLastRun: true (the default)500 on day one, ~20 a day after$2.00 + $2.32 = $4.32
onlyNewSinceLastRun: false500 every day$60.00

That is a 14x difference, and every dollar of the difference would have been spent re-buying products you already had. The default is the cheap one on purpose. Turn it off when you genuinely want a full re-snapshot — for a one-off export, or to rebuild a catalogue you have lost — and not by accident.

The other two cost levers work the same way and also default to the cheaper outcome where the cheaper outcome is the sensible one:

  • productType filters before charging. Reading only "Shoes" from a 500-product store bills for the shoes.
  • includeOutOfStock: false filters before charging, so sold-out products cost you nothing.
  • includeVariants is not a cost lever, and this README will not pretend it is one. Billing is one event per product whether the variant array is attached or not. It defaults to on because the variants are usually the point.

Frequently asked questions

Is /products.json a public endpoint? Yes. It is a standard part of every Shopify storefront and is served without authentication by design. This Actor accesses nothing behind a login and holds no credentials for any store.

Does it work on every Shopify store? It works on any storefront that serves /products.json, which is the default. A merchant can disable it, and a small number do. When that happens you get an unavailable row naming the reason, and you are not charged.

What happens if I give it a domain that is not a Shopify store? You get unavailable with the reason "the endpoint returned HTML, not JSON — this domain does not serve a Shopify products.json", and no charge. That is deliberate: an unresolvable input must never bill.

Does it return inventory levels? No, and nothing here implies otherwise. /products.json publishes a per-variant available boolean, not a quantity. You get inStock and variantsInStock; you do not get "17 left", because the endpoint does not carry it.

Does it return customer, order or revenue data? No. None of that appears on a public storefront endpoint and this Actor makes no attempt to reach it.

How far back does the catalogue go? /products.json publishes the store's currently listed products. It is a catalogue, not an archive — a product the merchant has delisted is gone from it, and this Actor cannot recover one.

Can I use it to track a competitor's price changes? That is the common use. Schedule it daily with the incremental setting on; every row you receive is a product whose updated_at moved, and priceMin / compareAtMax / onSale give you the price story per product.

What if my budget cap is reached mid-run? Delivery is truncated to the number of records actually charged and the run says so in the log. You are never billed for rows you did not receive.

Limits and troubleshooting

  • A store returns fewer products than you expected. Check maxProductsPerStore first — the log names any store that a cap truncated. Beyond that, /products.json excludes products the merchant has not published to the online-store channel.
  • A 40-page ceiling. The Actor stops at 10,000 products per storefront and says so in the log rather than paging indefinitely. Split a larger catalogue with productType.
  • Rate limiting. Storefronts throttle aggressive paging. The Actor backs off and retries up to three times, then reports the store as unavailable. An unreachable store is reported, never silently treated as an empty one.
  • A store answers 200 with HTML. That is the "not a Shopify store" case, and it is reported as such rather than as an empty catalogue.

Integrations and API

The Actor is callable from anywhere the Apify API reaches.

curl -X POST "https://api.apify.com/v2/acts/praise-most-high~shopify-store-product-catalog/runs?token=$APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"storeDomains":["allbirds.com"],"maxProductsPerStore":50}'
  • Scheduling — run it daily from the Apify scheduler; with the incremental default on, each run is a change feed.
  • Webhooks — fire on ACTOR.RUN.SUCCEEDED to push new rows into your own warehouse.
  • Python and JavaScript SDKs — both call it the same way; see the Apify client docs.
  • MCP — reachable as a tool from any MCP-speaking agent through Apify's own MCP server.

Other Actors on this account that pair with this one:

Unofficial. This Actor is not affiliated with, endorsed by, or sponsored by Shopify Inc., Allbirds, Kith, or any merchant whose storefront you point it at. "Shopify" is a trademark of Shopify Inc. and is used here only to identify the storefront software being read.

This Actor reads a public, unauthenticated storefront endpoint that Shopify serves by design, and returns product catalogue data — titles, prices, variants, images and descriptions. It collects no personal data, no customer records, no order data and nothing behind an authentication boundary.

Product descriptions, images and trade dress belong to the merchant who published them. This Actor retrieves them; it does not license them to you. Whether your intended use of that output is permitted is a question about your jurisdiction, your purpose and the merchant's own terms, and it is yours to answer — this Actor makes no representation about it.

Support and feedback

Open an issue on the Actor's Issues tab. Bug reports that include the run ID and the input that produced the problem are answered fastest. Feature requests are read and are the main source of what gets built next.