SFOLN

Catalogue / Company & lead intelligence

Shopify Products Scraper + WooCommerce

sfoln/shopify-products-scraper · Ecommerce, Lead generation, Automation On request

Scrape every product of any Shopify or WooCommerce store: title, vendor, type, tags, price and compare-at price, discount, stock, variants, images and dates, one row per product or per variant. No login or API key. $0.80 per 1,000 products + $0.001 per store.

About

What it does

Scrape every product of any Shopify or WooCommerce store into clean rows: give store URLs and get one row per product (or per variant) with title, vendor, product type, tags, price and compare-at price, discount, stock availability, variants, images and creation / update dates. The platform is detected per store and the catalog is read from the store's public storefront JSON (Shopify /products.json, the WooCommerce Store API), so there is no login, no API key and no browser. Built for e-commerce analysts, competitor price monitoring, dropshippers, agencies, market research and AI agents.

Price

Price per delivered record

EventPricePer 1,000Charged for
Store product
product
$0.0008$0.80 / 1,000One product (or, with includeVariants, one variant) delivered as a normalized dataset row with prices, availability, variants and images.
Store catalog
store
$0.001$1 / 1,000One store whose catalog delivered at least one row in this run (platform detection and paging).

Apify free plan: up to 10 items per run. Paid plans have no per-run cap; set maxTotalChargeUsd to cap spend.

Run

Call it from code, an agent or the Console

This API is private on Apify for now. Email us and we will publish it for you, usually within two working days. The calls below work once it is public. contact@sfoln.com

curl · synchronous run, rows in the response
curl -X POST "https://api.apify.com/v2/actors/sfoln~shopify-products-scraper/run-sync-get-dataset-items" \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"storeUrls":["https://www.allbirds.com"],"maxProductsPerStore":10}'
mcp.json · hosted Apify MCP server
{
  "mcpServers": {
    "apify": {
      "type": "http",
      "url": "https://mcp.apify.com?tools=sfoln/shopify-products-scraper"
    }
  }
}
asynchronous run · for large batches
POST https://api.apify.com/v2/actors/sfoln~shopify-products-scraper/runs?maxTotalChargeUsd=1
GET  https://api.apify.com/v2/actor-runs/{runId}            # until status is SUCCEEDED
GET  https://api.apify.com/v2/datasets/{defaultDatasetId}/items?format=json

Authenticate with an Apify API token. MCP clients can instead sign in with OAuth when prompted. Example prompt for an agent: List the 20 newest products of allbirds.com with their prices, compare-at prices and whether they are in stock, then tell me which ones are on sale.

Input

Input fields

Add Shopify or WooCommerce store URLs. Each store produces one dataset row per product (or per variant) plus one summary record.

FieldTypeDefaultMeaning
storeUrls *arrayrequiredStore URLs
Store homepages or domains (https://www.allbirds.com, allbirds.com) or Shopify collection URLs (https://store.com/collections/sale, also under a market folder such as /en-ca/collections/sale), one per line. The platform is detected per store: Shopify (/products.json) or WooCommerce (Store API). Duplicates are removed. Accounts on the Apify free plan can process up to 10 stores per run.
maxProductsPerStoreinteger1000Max products per store
Rows per store (1–50,000): products, or variants with includeVariants. Each delivered row is one charged event. Range 1–50000.
collectionsarray—Collections (Shopify)
Shopify collection handles or URLs (sale, new-arrivals, https://store.com/collections/sale) to list instead of the whole catalog. Products in several collections are listed once.
includeVariantsbooleanfalseOne row per variant (Shopify)
Deliver one row per variant (size, colour…) with its own SKU, price, compare-at price, availability and image, instead of one row per product. WooCommerce stores are always listed per product.
includeDescriptionbooleanfalseInclude product descriptions
Add the plain-text product description to each product row (up to 20,000 characters).
maxStoresinteger1000Max stores
Safety cap on the number of stores processed in this run. Range 1–10000.
concurrencyinteger5Concurrency
How many stores are processed in parallel (1–10). Pages of one store are read one at a time at up to 2 requests per second. Range 1–10.
itemTimeoutSecsinteger600Time limit per store (seconds)
Paging for one store stops cleanly shortly before this limit (60–3600). Range 60–3600.
includeRawbooleanfalseInclude raw upstream response
Attach the store's original product JSON to each row (variant rows: the variant's own JSON; larger output).
Example input
{
  "storeUrls": [
    "https://www.allbirds.com"
  ],
  "maxProductsPerStore": 10
}

Output

One record per input item

Records land in the run's dataset; a synchronous call returns them directly. Each has a status (ok is charged; everything else is free) and a charged flag. The run's OUTPUT record totals items and charges.

FieldTypeMeaning
inputstring | objectInput
The store entry as given.
recordTypestringRecord type
product (one per delivered product, charged product) | variant (with includeVariants, one per variant, charged product) | summary (one per store; charged store when at least one row was delivered) e.g. "product"
positionintegerPosition
Product and variant rows: position within the store (1-based).
platformstring | nullPlatform
shopify | woocommerce
storestring | nullStore
The store's host (after redirects, e.g. www.allbirds.com).
productIdstringProduct ID
handlestring | nullHandle
URL handle (Shopify) or slug (WooCommerce).
titlestringTitle
Product title (variant rows: the product's title).
urlstring | nullProduct URL
Variant rows: the product URL with ?variant=<id> (Shopify).
vendorstring | nullVendor
Shopify vendor; WooCommerce brand when the store uses brands.
productTypestring | nullProduct type
Shopify product type; WooCommerce: the first category.
tagsarrayTags
categoriesarrayCategories
WooCommerce categories (Shopify products have none in the storefront JSON).
pricenumber | nullPrice
Product rows: the lowest variant price. Variant rows: the variant's price.
priceMinnumber | nullLowest price
priceMaxnumber | nullHighest price
compareAtPricenumber | nullCompare-at price
The regular price of discounted variants (highest of them); null when nothing is on sale.
currencystring | nullCurrency
ISO 4217 code (Shopify: from the store's /meta.json).
onSalebooleanOn sale
At least one variant is below its compare-at price.
discountPctinteger | nullDiscount %
Largest discount among variants, in percent of the compare-at price.
availableboolean | nullAvailable
At least one variant can be bought (variant rows: this variant).
variantCountintegerVariants
variantsarrayVariants
Product rows: up to 100 { variantId, title, sku, price, compareAtPrice, available, options, grams }.
optionsarray | objectOptions
Product rows: [{ name, values }]. Variant rows: { optionName: value }.
imagesarrayImages
Up to 20 image URLs.
imageCountintegerImages (count)
createdAtstring | nullCreated at
ISO 8601 UTC (Shopify only).
updatedAtstring | nullUpdated at
ISO 8601 UTC (Shopify only).
publishedAtstring | nullPublished at
ISO 8601 UTC (Shopify only).
descriptionstring | nullDescription
Plain text, product rows only, with includeDescription.
variantIdstringVariant ID
Variant rows only.
variantTitlestring | nullVariant title
Variant rows only (e.g. 9 / Black).
skustring | nullSKU
Variant rows only.
gramsnumber | nullWeight (g)
Variant rows only.
imagestring | nullImage
Variant rows only: the variant's image, else the product's first image.
storeNamestring | nullStore name
Summary records only (Shopify /meta.json).
productCountinteger | nullProducts in store
Summary records only: published products the store reports (Shopify /meta.json, WooCommerce X-WP-Total).
collectionsarrayCollections
Summary records only: the Shopify collections listed.
resultsDeliveredintegerRows delivered
Summary records only.
duplicatesintegerDuplicates
Summary records only: rows already delivered for another entry of this run (not charged).
stopReasonstringStop reason
Summary records only: end | limit | free_plan | budget | deadline | aborted | max_pages | empty_pages | error
statusstringStatus
ok | not_found | error | skipped_invalid | skipped_budget | skipped_free_plan | skipped_unmetered e.g. "ok"
chargedbooleanCharged
Product and variant rows: charged product. Summary records: charged store when at least one row was delivered.
chargedEventstringCharged event
Pay-per-event event charged for this record (present only when charged).
chargedCountintegerEvents charged
Events actually charged when the charge came back short of the requested count (present only then; a warning explains it).
sourcestringSource
Upstream data source (storefront).
fetchedAtstringFetched at
ISO timestamp.
errorobjectError
{ code, message, provider?, status? } when status is not ok. Actor codes: invalid_input, unsupported_platform, store_password_protected, blocked, collection_not_found. Upstream codes: http_error (see status), timeout, network, dns_not_found, blocked_url, too_large, invalid_json, unexpected_type, invalid_response. Codes set by the runtime: budget_exhausted, free_plan_limit, unmetered, aborted, charge_failed, record_too_large, invalid_record, dataset_push_failed.
warningsarrayWarnings
Notes about this record, e.g. a free-plan cap, the budget, or a collection that was not found.
Example record
{
  "input": "https://www.allbirds.com",
  "recordType": "product",
  "position": 1,
  "platform": "shopify",
  "store": "www.allbirds.com",
  "productId": "7340901859408",
  "handle": "womens-allbirds-flip-flop-dusty-pink",
  "title": "Women's Allbirds Flip Flop - Dusty Pink",
  "url": "https://www.allbirds.com/products/womens-allbirds-flip-flop-dusty-pink",
  "vendor": "Allbirds",
  "productType": "Shoes",
  "tags": [
    "allbirds::edition => limited",
    "shoprunner"
  ],
  "categories": [],
  "price": 25,
  "priceMin": 25,
  "priceMax": 25,
  "compareAtPrice": 50,
  "currency": "USD",
  "onSale": true,
  "discountPct": 50,
  "available": true,
  "variantCount": 7,
  "variants": [
    {
      "variantId": "42146889039952",
      "title": "5",
      "sku": "A12513W050",
      "price": 25,
      "compareAtPrice": 50,
      "available": false,
      "options": [
        "5"
      ],
      "grams": 455
    },
    {
      "variantId": "42146889072720",
      "title": "6",
      "sku": "A12513W060",
      "price": 25,
      "compareAtPrice": 50,
      "available": true,
      "options": [
        "6"
      ],
      "grams": 455
    }
  ],
  "options": [
    {
      "name": "Size",
      "values": [
        "5",
        "6",
        "7",
        "8",
        "9",
        "10",
        "11"
      ]
    }
  ],
  "images": [
    "https://cdn.shopify.com/s/files/1/1104/4168/files/A12513_26Q2_Allbirds-Flip-Flop-Dusty-Pink_PDP_LEFT.png?v=1774646345"
  ],
  "imageCount": 6,
  "createdAt": "2026-03-27T21:03:50.000Z",
  "updatedAt": "2026-10-14T21:56:35.000Z",
  "publishedAt": "2026-09-25T23:58:13.000Z",
  "status": "ok",
  "charged": true,
  "chargedEvent": "product",
  "source": "storefront",
  "fetchedAt": "2026-10-14T12:00:00.000Z"
}

FAQ

Questions

Which stores work?
Shopify stores that serve their public /products.json (almost all do) and WooCommerce stores with the Store API (WooCommerce 6.0 and later). The platform is detected per store. Password-protected stores, stores that block automated requests and other platforms return not_found or error and are not charged.
Do I get stock quantities or sales numbers?
No. Storefronts publish whether a variant is available, not how many units are left, and never sales. available is true when at least one variant can be bought.
How do I scrape only a collection?
Give the collection URL (https://store.com/collections/sale) as the store, or list collection handles in collections. Products in several collections are delivered once.

Machine-readable

This page for agents