# WooCommerce Products Scraper — Prices, Stock, Variations (`kilndata/woocommerce-products-scraper`) Actor

Scrape products from any WooCommerce shop through its public Store API: name, SKU, price and sale price, currency, stock, categories, tags, images, attributes, variations, rating average and count. No login, no browser. Shops with the API closed are reported, not charged.

- **URL**: https://apify.com/kilndata/woocommerce-products-scraper.md
- **Developed by:** [Datakiln](https://apify.com/kilndata) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 product extracteds

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

## WooCommerce Products Scraper — prices, stock, variations

Give it the address of a WooCommerce shop and it returns the product catalogue: name, SKU, price and sale price, currency, stock, categories, tags, images, attributes, variations, and the average rating with its review count.

It does not scrape the shop's HTML. Almost every WooCommerce shop exposes a public, read-only JSON endpoint (the Store API, `/wp-json/wc/store/v1/products`) that the shop's own front end uses. This actor reads that endpoint, politely, 100 products per request. No browser, no login, no API key.

- **One request per 100 products.** A 700-product shop takes about 8 requests, with a pause between each.
- **Prices in two forms.** The smallest currency unit (`1999`) and a decimal (`19.99`), plus the currency code and decimals, so JPY and KWD come out right.
- **Closed shops are reported, not charged.** If a shop has switched the API off, you get one status line with the reason and the run carries on.
- **Stops at your spending limit.** Charging happens per batch of products saved. Set a max cost per run and the actor stops when it is reached.
- **No personal data.** Ratings are an average and a count. There is no review text, no reviewer name, no customer data. E-mail addresses and phone numbers that a shop writes into a product description are removed.

### What can I use it for?

| Use | How |
|---|---|
| Price monitoring | Run on a schedule against competitor shops, compare `price`, `salePrice`, `onSale` |
| Catalogue import or migration | Export names, SKUs, categories, images and attributes to CSV/JSON |
| Stock watching | `inStockOnly` plus `inStock` and `lowStockRemaining` |
| Market research | Compare category trees, price ranges and rating counts across shops |
| AI agents / MCP | Small, flat JSON; ask an agent to "list the in-stock mugs under 20 USD from shop X" |

### How to use it

1. Paste one or more shop addresses into **Store URLs**. `shop.example.com` and `https://shop.example.com/product/mug/` both work.
2. Set **Max products per store**. The default is 100.
3. Optionally add a **Category filter** (slugs, names or IDs, comma-separated) or tick **In-stock only**.
4. Run it and download the dataset as JSON, CSV or Excel.

Example input:

```json
{
    "storeUrls": ["https://jococups.com", "https://offermanwoodshop.com"],
    "maxProductsPerStore": 100,
    "categoryFilter": "insulated-bottles",
    "inStockOnly": false
}
```

| Field | Default | Meaning |
|---|---|---|
| `storeUrls` | required | Shop addresses |
| `maxProductsPerStore` | 100 | Stop after this many products per shop |
| `categoryFilter` | empty | Category slugs, names or IDs |
| `inStockOnly` | false | Skip out-of-stock products (backorder counts as out of stock) |
| `respectRobotsTxt` | true | Skip a shop whose robots.txt disallows the API; follows Crawl-delay up to 10 s |
| `maxConcurrency` | 3 | Shops read at the same time (each shop is read one request at a time) |
| `perStoreDelayMs` | 1000 | Pause between two requests to the same shop |
| `requestTimeoutSecs` | 30 | Timeout for one request |
| `maxRetries` | 2 | Retries on timeouts, 429 and 5xx; `Retry-After` is honoured |

### Output

One dataset item per product (shortened):

```json
{
    "type": "product",
    "status": "ok",
    "store": "https://jococups.com",
    "productId": 273076,
    "name": "Active Flask Utility Insulated 16oz",
    "url": "https://jococups.com/product/16oz-insulated-flask/",
    "productType": "variable",
    "sku": "AUIFL16",
    "currency": "AUD",
    "minorUnit": 2,
    "priceMinor": 6045,
    "price": 60.45,
    "regularPrice": 54.95,
    "salePrice": null,
    "onSale": false,
    "inStock": false,
    "categories": [{ "id": 214, "name": "Insulated Bottles", "slug": "insulated-bottles" }],
    "attributes": [{ "name": "Colour", "terms": ["Desert Sage", "Sandstone", "Ultra Black"] }],
    "variationCount": 3,
    "averageRating": null,
    "reviewCount": 0
}
```

A shop that returns nothing gets one item with `"type": "store"`, a `status` (`api-closed`, `failed`, `skipped` or `invalid-input`) and an `error` that says why. These items are free. The full field list is in the dataset schema.

### How much does it cost?

You pay per product returned (event `product-extracted`), as set on the pricing tab, plus the usual Apify platform usage. You are not charged for shops with a closed API, failed requests, robots.txt skips or invalid input. Set **Max cost per run** and the actor stops when it is reached. Products are saved first and charged in batches; the actor never saves more products than your limit allows.

### Limitations — read before you buy

- **Only shops with an open Store API.** In a test of 12 shops, 8 answered. The other four: one blocks the endpoint (HTTP 403), two return a normal web page or a 404 (probably not WooCommerce), and one is WordPress without the Store API. You cannot know in advance; the status line tells you.
- **Variations have no price of their own.** The list endpoint gives the variation IDs and their attribute values, plus a price range for the product. Per-variation prices need one extra request per product, which this actor does not make.
- **Short description only.** The full description is not returned.
- **Category filter and stock filter run on what the API returns.** A misspelled category simply returns nothing. Backorder products are treated as out of stock.
- **Prices are what the shop shows to a visitor,** in the shop's currency and its tax display setting (with or without tax). Geo-pricing plugins can show different prices to different countries; the actor reads what a cloud server sees.
- **Large shops are capped at 10,000 products per run** to keep the load reasonable.
- **Some shops sit behind a firewall** (Cloudflare, Sucuri, Wordfence) and answer 403 to anything that is not a browser. Those show up as `api-closed`. The actor does not try to get around that and has no proxy by default.
- Shops can change or remove the API at any time.

### Is it legal?

It reads public product data that the shop serves to anyone without a login, through an endpoint meant for that. It collects no personal data, identifies itself with its own user agent, checks robots.txt, limits request speed per shop, and backs off on HTTP 429. Whether you may use product data for a given purpose depends on the shop's terms and on your local law. Check that for your case.

### Integrations

Use it through the Apify API, schedules, webhooks, or the Apify MCP server. Output goes to JSON, CSV, Excel, Google Sheets, Make, Zapier or n8n through the standard Apify integrations.

### FAQ

**Does it work on any WordPress shop?** Only on shops running a reasonably recent WooCommerce with the Store API reachable. Shopify, Magento and others are not supported.

**Why did a shop return `api-closed`?** The API answered 403/404, or returned a normal web page. Open `https://<shop>/wp-json/wc/store/v1/products` in a browser to see what the shop says.

**Can it read reviews?** Only the average rating and the review count. Review text and reviewer names are deliberately not collected.

This actor is not affiliated with WooCommerce, Automattic or any shop it reads.

# Actor input Schema

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

Shop addresses, one per line, for example "https://shop.example.com" or just "shop.example.com". A product or category page URL also works: the shop root is used. The shop must run WooCommerce with its public Store API open (most do). Shops with the API closed are listed with status "api-closed" and are not charged.

## `maxProductsPerStore` (type: `integer`):

Stop reading a shop after this many products (products are read 100 per request).

## `categoryFilter` (type: `string`):

Optional. Only products in these categories. Comma-separated category slugs, names or IDs, for example "mugs, t-shirts". Leave empty for all products.

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

Skip products that are out of stock. Backorder products count as out of stock.

## `respectRobotsTxt` (type: `boolean`):

Do not read a shop whose robots.txt disallows the Store API path. Also follows a Crawl-delay (up to 10 s).

## `maxConcurrency` (type: `integer`):

How many different shops are read at the same time. Each shop is always read one request at a time.

## `perStoreDelayMs` (type: `integer`):

Pause between two requests to one shop. Small shops run on cheap hosting, so keep it at 1000 or more.

## `requestTimeoutSecs` (type: `integer`):

How long to wait for one answer from a shop before retrying.

## `maxRetries` (type: `integer`):

Retries on timeouts, HTTP 429 and 5xx errors, with growing pauses (Retry-After is honoured). A shop that still fails is listed with status "failed" and not charged.

## Actor input object example

```json
{
  "storeUrls": [
    "https://jococups.com",
    "https://offermanwoodshop.com"
  ],
  "maxProductsPerStore": 5,
  "categoryFilter": "",
  "inStockOnly": false,
  "respectRobotsTxt": true,
  "maxConcurrency": 3,
  "perStoreDelayMs": 1000,
  "requestTimeoutSecs": 30,
  "maxRetries": 2
}
```

# Actor output Schema

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

No description

# 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://jococups.com",
        "https://offermanwoodshop.com"
    ],
    "maxProductsPerStore": 5
};

// Run the Actor and wait for it to finish
const run = await client.actor("kilndata/woocommerce-products-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://jococups.com",
        "https://offermanwoodshop.com",
    ],
    "maxProductsPerStore": 5,
}

# Run the Actor and wait for it to finish
run = client.actor("kilndata/woocommerce-products-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://jococups.com",
    "https://offermanwoodshop.com"
  ],
  "maxProductsPerStore": 5
}' |
apify call kilndata/woocommerce-products-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,kilndata/woocommerce-products-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/0ize7kfuEeaEoMgTR/builds/K9iT5gSUD6XaAbBUq/openapi.json
