# StoreShelf Product & Price Scraper for WooCommerce® (`creatortsv/storeshelf-product-price-scraper`) Actor

\[From $1.00/1K, no usage fees] Export products from WooCommerce and Shopify stores in one schema: SKUs, prices, discounts, stock status, and variants on Shopify. Shopify is best effort: it can refuse reads; a refused store is not charged. CSV or JSON, no login. Not affiliated with either company.

- **URL**: https://apify.com/creatortsv/storeshelf-product-price-scraper.md
- **Developed by:** [Vladimir Tsaplin](https://apify.com/creatortsv) (community)
- **Categories:** E-commerce, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.70 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## StoreShelf Product & Price Scraper for WooCommerce®

StoreShelf scrapes products from WooCommerce stores in one schema: SKUs, prices, discounts and stock status. Use it to watch competitor prices and assortment. Paste store URLs and get CSV or JSON, with no login. WooCommerce is the main path. [Shopify](https://www.shopify.com) stores are read too: a run on Apify's cloud read two of them, and reads stay best effort, as [How Shopify stores are read](#how-shopify-stores-are-read) explains. Some stores of either kind block cloud servers, see [Limits](#limits).

StoreShelf is an independent product. It is not affiliated with, endorsed by or sponsored by WooCommerce, Automattic, [Shopify](https://www.shopify.com) or any store named on this page. The names only say which systems the Actor can read. WooCommerce and its associated designs are trademarks of Automattic Inc. Shopify is a trademark of Shopify Inc.

Sample record from a real run on a WooCommerce store on 2026-10-31:

```json
{
  "platform": "woocommerce",
  "store_domain": "mandarinstone.com",
  "title": "Dijon Heritage Limestone Pentagon",
  "sku": null,
  "price_min": 7.08,
  "price_max": 7.08,
  "compare_at_price": 9.44,
  "discount_pct": 25.0,
  "currency": "GBP",
  "in_stock": true,
  "variants_count": 1,
  "product_url": "https://www.mandarinstone.com/product/dijon-heritage-limestone-pentagon/"
}
```

### What you get

| Field | Example | Notes |
|---|---|---|
| `title` | Dijon Heritage Limestone Pentagon | As published |
| `price_min` | 7.08 | Lowest variant price. A price of 0 counts as no price, so it is null on a WooCommerce product whose lowest price is 0, see [Limits](#limits) |
| `compare_at_price` | 9.44 | Original price of the deepest discount. Null when nothing is discounted, and null with `discount_pct` on a WooCommerce product whose lowest price is 0 |
| `discount_pct` | 25.0 | Calculated from price and original price |
| `currency` | GBP | Store currency, null when the store does not publish it |
| `in_stock` | true | True when any variant is available, null when the store does not say |
| `variants` | List | ID, title, SKU, price, original price and availability of each variant |
| `sku` | null | First variant with a SKU, null when the product list shows none. On the store in this sample, 8 of the 1,000 products we read had one |
| `product_url` | Link | Public product page |
| `description` | Plain text | As published, with email addresses and phone numbers removed |

The WooCommerce Store API product list gives only a price range for a variable product, and the Actor does not request each variation, so these products show their price range and variant count with an empty `variants` list. Vendor exists on Shopify and in schema.org data, dates on Shopify only.

### How much does it cost to scrape a WooCommerce store?

You pay per product in the output, with no usage fees.

| Apify plan | Price per 1,000 products | Store with 500 products |
|---|---|---|
| Free | $1.00 | $0.50 |
| Starter (Bronze) | $0.90 | $0.45 |
| Scale (Silver) | $0.85 | $0.43 |
| Business (Gold) | $0.70 | $0.35 |

A run also carries Apify's small start fee of $0.00005 per run for each GB of memory. Set **Maximum products in total** to cap a run. Stores that fail are not charged.

### How to run it

1. Paste store URLs. WooCommerce or Shopify is detected for you.
2. Add a search term or **In-stock products only** if you want, then press **Start**.
3. Download the results as CSV, JSON or Excel from the **Output** tab.

To track prices, save the input as a task, add a schedule and compare the runs.

### How Shopify stores are read

On 2026-10-31 one run on Apify's cloud read real products from two Shopify stores, so Shopify reads do work from there. They stay best effort: that is one run on two stores, and it is no promise for any other store or any later run. The prefilled input and the Task Examples use a WooCommerce store for this reason.

- **The Actor does not sign its requests.** Shopify's changelog says bots that do not sign with Web Bot Auth get its strictest rate limits, and the `/agents.md` page each Shopify store publishes says server-to-server clients must use Web Bot Auth. The Actor identifies itself by name. It uses no proxies and no browser identity to get around a limit.
- **What a refusal looks like.** `/products.json` answers HTTP 429 with `Retry-After: 60`, while the rest of the store keeps answering. The Actor waits as asked, for up to 65 seconds, and tries twice more. It then reports that store as `HTTP_429` in `RUN_SUMMARY` and charges nothing for it. Those waits can take about two minutes per store.
- **What we measured away from Apify.** On 2026-10-31, from one address outside Apify, three runs in a row read nothing from the first Shopify store we tried. A later run from the same address read 100 products from that store. The next Shopify run read 50 from a second store and was refused by the first. One later run of 5 products per store read one store and was refused by the other. So the limit comes and goes, and one good run promises nothing about the next.
- **Every release is checked from Apify's cloud.** A release runs the Actor 3 times on two Shopify stores, and the Store description says "Export products from WooCommerce and Shopify stores" only if every run reads both stores. If it says "Export products from WooCommerce stores", that check did not pass for this build, so do not plan on Shopify reads. The check reads 5 products per store, so even when it passes, a full Shopify catalog stays best effort.

If you do read Shopify stores, run them again later when refused, and expect to read fewer at a time than WooCommerce stores.

### Limits

- Up to 1,000 products per store, and two stores at a time.
- A search term or **In-stock products only** reads the catalog page by page and filters it in the Actor, the same way on WooCommerce and Shopify. The store's own search is not used, so a search covers the title, vendor, type, SKU, handle and tags, and not the description.
- A filtered scan stops at 40 catalog pages per store: 4,000 products on WooCommerce, 10,000 on Shopify. If a catalog is larger, matches can be missing. You keep what was found, and the store is named in `not_processed_targets` and `cut_scans` in `RUN_SUMMARY`. A run that stored nothing from such a scan fails, because "no match" would not be proven.
- The Actor reads each store's robots.txt first. It does not request a path the store disallows, and it keeps the store's Crawl-delay, retries included, so a store that asks for 10 seconds between requests reads slowly.
- A price of 0 is read as no price. Stores sometimes list internal items at $0, such as checkout helpers, and they stay in the catalog with null `price_min` and `price_max`. A WooCommerce variable product with a free variation, such as a free sample, also gets a null `price_min`, but next to a real `price_max`, and its `compare_at_price` and `discount_pct` are null too. The Store API product list gives only a price range for a variable product, and the Actor does not request each variation, so it cannot skip the free one. A null `price_min` therefore does not always mean an internal item: check `price_max`. On the store in the prefilled input, which offers free samples, 544 of the 1,000 products we read had a null `price_min`, and all 1,000 had a `price_max`. On Shopify a $0 variant is skipped, and the lowest other variant price is used.
- A store that closes its catalog, as some large brands do, cannot be read. If the page has schema.org product data, you get those products; otherwise the store is reported as failed. Such data can give a price range with no variant count, and `variants_count` is then null.
- Some stores block cloud servers. A store can open fine from a home address and still answer HTTP 403 to Apify's servers, as two WooCommerce stores did in a test on 2026-10-31. Such a store fails with `CATALOG_NOT_PUBLIC` and no product is charged. The Actor uses no proxies and no browser identity to get past a block, so no setting changes this.

### FAQ

**Which stores work?** WooCommerce stores with the Store API on, and Shopify stores that serve `/products.json` when Shopify does not refuse the read. Either way the store has to let Apify's servers in: a store you can open in your browser can still block them. Start with a small run to see whether a store works.

**Is scraping store catalogs legal?** The Actor reads public catalog data that stores serve without login, and it follows each store's robots.txt. Check the terms of the stores you monitor and use the data in line with the law that applies to you. Shopify's own terms also restrict access to its services by automated means, and its stores ask server-to-server clients to sign requests with Web Bot Auth, which this Actor does not do. Whether that binds you is a question for your legal adviser.

**The result is empty. What now?** Open `RUN_SUMMARY` in the run's key-value store. It names each failed store and the reason: for example `CATALOG_NOT_PUBLIC`, `ROBOTS_DISALLOWED` or `HTTP_429`. `CATALOG_NOT_PUBLIC` with the message `homepage answered HTTP 403` usually means the store blocks cloud servers. No product was charged. `not_processed_targets` names the stores a run did not finish, for example because it reached **Maximum products in total** or the page limit of a filtered scan.

**Does it collect store emails or contacts?** No. The output has no contact field. When a store writes an email address or phone number into a product description, the Actor replaces it with `[email removed]` or `[phone removed]`. This goes by pattern, so an address spelled out in words or a short local number can stay, and a part number shaped like a phone number can go. Other description text stays as the store wrote it, including any names or street addresses.

### Call it from your own code

```bash
curl -X POST "https://api.apify.com/v2/acts/creatortsv~storeshelf-product-price-scraper/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"storeUrls": ["https://www.mandarinstone.com"], "maxItemsPerStore": 100}'
```

### Scope

**What's Included:** public catalogs of WooCommerce stores, best-effort reads of public Shopify catalogs, prices, discounts from regular and compare-at prices, stock status, per-store failure report.

**What's Not Included:** guaranteed Shopify reads, Web Bot Auth signing, store discovery, contact fields, order or sales volumes, stock quantities, password-protected stores, stores that block Apify's servers, proxy rotation, and other store platforms unless the page carries schema.org product data.

Keywords: scraper for WooCommerce stores, export products from a WooCommerce store, WooCommerce Store API, price monitoring for WooCommerce stores, competitor price monitoring, ecommerce product data.

# Actor input Schema

## `storeUrls` (type: `array`):

Public WooCommerce store URLs or domains, such as https://www.mandarinstone.com. The platform is detected for you. Shopify stores are read too, best effort: Shopify can refuse automated reads. Some stores block cloud servers and fail with CATALOG_NOT_PUBLIC. See the README.

## `searchQuery` (type: `string`):

Keeps products with this text in the title, vendor, type, SKU, handle or tags. It reads up to 40 catalog pages per store and filters them itself, the same way on every platform. A store it could not finish is named in RUN_SUMMARY.

## `inStockOnly` (type: `boolean`):

Keeps products the store reports as available. It reads up to 40 catalog pages per store and filters them itself, the same way on every platform. A store it could not finish is named in RUN_SUMMARY.

## `maxItemsPerStore` (type: `integer`):

Products to keep from each store (1 to 1000).

## `maxItems` (type: `integer`):

The run stops at this many products. This caps what you pay.

## Actor input object example

```json
{
  "storeUrls": [
    "https://www.mandarinstone.com"
  ],
  "inStockOnly": false,
  "maxItemsPerStore": 5,
  "maxItems": 10
}
```

# Actor output Schema

## `results` (type: `string`):

Products and prices

## `runSummary` (type: `string`):

Per-target report: what succeeded, what failed and why. Failed targets create no result and no charge.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "storeUrls": [
        "https://www.mandarinstone.com"
    ],
    "maxItemsPerStore": 5,
    "maxItems": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("creatortsv/storeshelf-product-price-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "storeUrls": ["https://www.mandarinstone.com"],
    "maxItemsPerStore": 5,
    "maxItems": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("creatortsv/storeshelf-product-price-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "storeUrls": [
    "https://www.mandarinstone.com"
  ],
  "maxItemsPerStore": 5,
  "maxItems": 10
}' |
apify call creatortsv/storeshelf-product-price-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,creatortsv/storeshelf-product-price-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/MHFUxdcijKXKsEc19/builds/3VaflFcCIuvsxGbzi/openapi.json
