# WooCommerce Scraper: Store API Products & Categories (`arman-bd/woocommerce-store-scraper`) Actor

Scrape WooCommerce stores through the public Store API: products, prices, stock status, categories, attributes, variations and reviews. Works on any Woo store with the API enabled.

- **URL**: https://apify.com/arman-bd/woocommerce-store-scraper.md
- **Developed by:** [Arman Hossain](https://apify.com/arman-bd) (community)
- **Categories:** E-commerce, Lead generation, MCP servers
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.56 / 1,000 product scrapeds

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## WooCommerce Scraper: Store API Products & Categories

![WooCommerce Store Scraper: Products, prices, stock status, categories, attributes, variations and clean descriptions from any WooCommerce store](https://api.apify.com/v2/key-value-stores/ZQOcNAOHrIgTacAmy/records/woocommerce-store-scraper.jpg)

**WooCommerce Store Scraper** pulls the full public catalogue from any **WooCommerce** store, product names, prices, stock status, categories, tags, attributes, variations, ratings, images and clean plain-text descriptions.

Every WooCommerce install ships a **Store API** that the storefront itself uses to render products. This Actor reads it directly: **no browser, no proxies, no login.** A 2,000-product catalogue returns in seconds.

> This is the **Store API** (`wc/store/v1`), not the authenticated admin API (`wc/v3`). They are different things, the Store API is the one your storefront already exposes to the public.

**Agent skill: [SKILL.md](https://api.apify.com/v2/key-value-stores/t7YoTxpZEJOWvw4Ug/records/woocommerce-store-scraper.md)**

```
https://api.apify.com/v2/key-value-stores/t7YoTxpZEJOWvw4Ug/records/woocommerce-store-scraper.md
```

### What you get

| Output field | Meaning |
|---|---|
| `store` | Store origin the record came from |
| `productId`, `name`, `slug`, `permalink` | Identity and the public product URL |
| `priceCurrent`, `priceRegular`, `priceSale`, `currency` | Prices as real decimal numbers in the store's currency |
| `priceMin`, `priceMax`, `isPriceRange` | The variation price span a variable product publishes, `null`/`false` when the store publishes none |
| `onSale` | Whether the store is currently discounting the product |
| `stockStatus` | `instock`, `outofstock` or `onbackorder` |
| `stockQuantity` | Units remaining, see the caveat below |
| `categories`, `tags` | Slug lists |
| `attributes` | Attribute name, taxonomy and available terms |
| `variations` | Variation IDs with their attribute name/value pairs |
| `averageRating`, `reviewCount` | Review aggregates |
| `images` | Full-size image URLs |
| `descriptionPlain` | Description as clean plain text: HTML and page-builder shortcodes stripped, entities decoded |
| `scrapedAt` | Run timestamp |

A `RUN_SUMMARY` record in the key-value store holds per-run counts, a per-store breakdown, the filters used, and any store that failed.

### Common use cases

- **Price monitoring.** Track competitor pricing across dozens of independent retailers on a schedule.
- **Product comparison engines.** Build a catalogue index from many small stores at once.
- **Sourcing and dropshipping.** Watch `stockStatus` to find what is actually available.
- **Market research.** Measure catalogue size, price bands and category mix across a niche.

### Quick start

Two stores, everything they have:

```json
{
 "stores": ["barefootbuttons.com", "woocommerce.com"]
}
```

One category, in stock only, capped:

```json
{
 "stores": ["barefootbuttons.com"],
 "categories": ["accessories"],
 "inStockOnly": true,
 "maxProductsPerStore": 200
}
```

Search a niche across many stores:

```json
{
 "stores": ["store-a.com", "store-b.com", "store-c.com"],
 "searchQuery": "hoodie",
 "maxProductsPerStore": 50
}
```

### Input

| Field | Type | Default | Notes |
|---|---|---|---|
| `stores` | array | - | **Required.** Domains or URLs. Paths are stripped, paste a product page if that is what you have. `shop.com`, `www.SHOP.com`, `http://shop.com` and a deep URL on it are one store, scraped and charged once. |
| `categories` | array | `[]` | Category slugs (or numeric term IDs). Multiple values are OR-ed. |
| `searchQuery` | string | `""` | Full-text search, executed by the store. |
| `maxProductsPerStore` | integer | `0` | Cap per store. `0` = whole catalogue. |
| `inStockOnly` | boolean | `false` | Ask the store for in-stock products only. |

`categories`, `searchQuery` and `inStockOnly` all run **server-side** and combine with AND, so narrowing them makes the run faster as well as smaller. `maxProductsPerStore` is applied last, after the store has filtered.

### Output example

A real record, description trimmed:

```json
{
 "store": "https://barefootbuttons.com",
 "productId": 1475,
 "name": "Replacement Set Screw and Allen Wrench Kit",
 "slug": "replacement-set-screw-and-allen-wrench-kit",
 "permalink": "https://barefootbuttons.com/product/replacement-set-screw-and-allen-wrench-kit/",
 "priceCurrent": 1.95,
 "priceRegular": 1.95,
 "priceSale": null,
 "priceMin": null,
 "priceMax": null,
 "isPriceRange": false,
 "currency": "USD",
 "onSale": false,
 "stockStatus": "instock",
 "stockQuantity": null,
 "categories": ["accessories"],
 "tags": [],
 "attributes": [
 {
 "name": "Set Screw Size",
 "taxonomy": null,
 "terms": [
 "For V1/V2 Mini and V1/V2 Tallboy Mini",
 "For V1/V2 Standard, V1/V2 Standard Skirtless & V1/V2 Tallboy",
 "For Big Bore Standard, Big Bore Standard Skirtless, Big Bore Tallboy & Max Bore"
 ]
 }
 ],
 "variations": [
 { "id": 1485, "attributes": [{ "name": "Set Screw Size", "value": null }] }
 ],
 "averageRating": 0,
 "reviewCount": 0,
 "images": ["https://barefootbuttons.com/wp-content/uploads/2026/06/set-screws-allen-wrench-prodcut.png"],
 "descriptionPlain": "Every Barefoot Button includes the hardware needed for installation, but sometimes extra parts come in handy.…",
 "scrapedAt": "2026-08-11T12:37:30.056Z"
}
```

This store publishes no value for the variation's attribute, so `value` is `null` rather than a
guess — see the note on honest nulls below.

A variable product carries its span instead:

```json
{
 "productId": 55151,
 "name": "Chasing Tone Unisex T-Shirt",
 "priceCurrent": 19,
 "priceRegular": 19,
 "priceMin": 19,
 "priceMax": 21,
 "isPriceRange": true,
 "currency": "USD"
}
```

### API example

```bash
curl -X POST "https://api.apify.com/v2/acts/arman-bd~woocommerce-store-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
 -H "Content-Type: application/json" \
 -d '{
 "stores": ["barefootbuttons.com"],
 "inStockOnly": true,
 "maxProductsPerStore": 100
 }'
```

### JavaScript example

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

const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('arman-bd/woocommerce-store-scraper').call({
 stores: ['barefootbuttons.com', 'woocommerce.com'],
 inStockOnly: true,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const p of items) console.log(`${p.name}, ${p.priceCurrent} ${p.currency} (${p.stockStatus})`);
```

### Limits and behaviour

- **Prices are converted, not copied.** The Store API returns money as an integer string in the currency's minor unit (`"5900"` with `currency_minor_unit: 2`). This Actor divides it out, so `priceCurrent` is `59` and not `5900`.
- **`stockQuantity` is usually `null`, and that is correct.** The Store API never publishes an exact inventory figure. The only quantity it exposes is `low_stock_remaining`, which the store fills in only once its low-stock threshold is crossed. A number here is real; `null` means the store did not publish one. Nothing is estimated.
- **Bad stores don't kill the run.** A site without the Store API, a DNS failure or a WAF block is recorded in `RUN_SUMMARY.failures`; the Actor only errors out if *every* store fails.
- **HTML 404 pages are rejected.** Many WordPress hosts answer `/wp-json/wc/.` with a themed 404 page. The response content type is checked, so a 200-status HTML page is treated as "no Store API", not as data.
- **Transient errors are retried.** 429, 5xx and network faults get three attempts with exponential backoff and a 30-second timeout.
- **Pagination is read, not guessed.** Page count comes from the `x-wp-totalpages` response header.
- **One store is one store.** `shop.com`, `www.shop.com`, `HTTP://SHOP.COM` and a pasted deep URL all collapse to the same origin, so a list that names the same shop several ways is fetched once and billed once.
- **Descriptions are prose, not layout.** Page builders such as Divi store their whole layout as `[shortcode]` blocks inside the description, and `<style>`/`<script>` blocks hide CSS and JS there too. All of that is removed and only the text it wraps is kept.
- **Public data only.** No authentication, no personal data, no access-control bypass.

### FAQ

**Do I need WooCommerce API keys?** No. You supply no credentials.

**Why did a store return nothing?** Either the Store API is disabled or firewalled, or your filters matched no products. `RUN_SUMMARY.stores` carries one `{ store, matched, saved }` entry per store — the store's own match count next to the saved count — which separates the two. A store that failed outright is also listed in `RUN_SUMMARY.failures` with the reason.

**How do I find a category slug?** Open a category page on the store, `/product-category/accessories/` means the slug is `accessories`. Numeric term IDs work too.

**Does it include variation prices?** Not one price per variation, no. `variations` carries each variation's ID and attribute values, and the store publishes the span those variations cover as a range on the parent — that arrives as `priceMin`, `priceMax` and `isPriceRange`. When `isPriceRange` is true, `priceCurrent` is the cheapest variation, not the whole story.

**Can I schedule it?** Yes, it is designed for scheduled runs. Diff on `productId` plus `priceCurrent` to build a price-history feed, and include `priceMax` so a variable product's top price is not invisible.

**Can I integrate it with something else?** Yes, Apify API, client libraries, webhooks, scheduled runs, dataset exports (JSON/CSV/Excel) or MCP. Output is structured JSON.

# Actor input Schema

## `stores` (type: `array`):

Store domains or URLs. A bare domain ('barefootbuttons.com'), a hostname with www, or a full product URL are all accepted. the path is stripped and the Store API is read from the site root. Different spellings of the same store are merged, so it is scraped and charged for once.

## `categories` (type: `array`):

Keep only products in these product categories. Use the slug from the store's category URL (/product-category/accessories/ → 'accessories'). Numeric term IDs also work. Multiple values are OR-ed. Leave empty for the whole catalogue.

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

Full-text product search, applied by the store itself. Matches product names and descriptions. Leave empty to list everything.

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

Cap the number of products saved per store. Set 0 for the entire catalogue.

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

Ask the store to return only products currently in stock. Filtering happens server-side, so it also makes the run faster.

## Actor input object example

```json
{
  "stores": [
    "barefootbuttons.com",
    "https://woocommerce.com/products/"
  ],
  "categories": [
    "accessories",
    "clothing"
  ],
  "searchQuery": "hoodie",
  "maxProductsPerStore": 0,
  "inStockOnly": false
}
```

# Actor output Schema

## `items` (type: `string`):

Every record the run produced.

## `runsummary` (type: `string`):

The RUN\_SUMMARY record from the run's key-value store.

# 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 = {
    "stores": [
        "barefootbuttons.com",
        "woocommerce.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("arman-bd/woocommerce-store-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 = { "stores": [
        "barefootbuttons.com",
        "woocommerce.com",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("arman-bd/woocommerce-store-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 '{
  "stores": [
    "barefootbuttons.com",
    "woocommerce.com"
  ]
}' |
apify call arman-bd/woocommerce-store-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,arman-bd/woocommerce-store-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/ALHLGbbE9sZTiz1NH/builds/ijhLjhK72LSSXLo79/openapi.json
