# WooCommerce Product Scraper (`ozer-studio/woocommerce-product-scraper`) Actor

Extract product prices, stock status, SKUs, images and categories from supported WooCommerce stores—just paste a store URL. Export to CSV or JSON, or schedule repeat runs to track price drops, restocks and new products. No API key required.

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

## Pricing

from $3.00 / 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.

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 Product Scraper — Prices, Stock & Change Monitor

**Turn a WooCommerce store URL into a clean product catalog—or a watchlist that tells you what changed.**

Export product names, prices, sale prices, currency, SKUs, availability, images
and categories. Schedule repeat runs to identify price drops, restocks and new
listings without comparing spreadsheets by hand.

No store login, data-provider subscription or API key is required for supported
public stores. No browser, proxy rotation or CAPTCHA solver is used.

> Works only where the store exposes an accessible public WooCommerce Store API
> and permits this access. It does not work on every shop. Blocked, redirected,
> robots-disallowed and unsupported stores are reported individually.

### 🔎 Two useful modes

| Mode | What you receive |
| --- | --- |
| **Export product catalog** | Current product records, suitable for CSV, spreadsheets and product research. |
| **Monitor price and stock changes** | First complete baseline, then only changed products on subsequent runs. |

The input is a **store homepage**, not a marketplace search URL. For example,
`https://store.com`, or `https://store.com/wordpress` if that is the WordPress
installation. A full `/wp-json/wc/store/v1/products` collection URL is accepted
without query parameters. Product, `/shop/` and category URLs are rejected instead
of silently expanding them into an entire-store scrape.

The prefilled example is **WooCommerce.com**, which sells extensions and themes.
It returns real products, not mock rows. Replace it with your desired store.

### 🚀 Quick start

1. Paste one or more store homepage URLs.
2. Choose **Export product catalog** or **Monitor price and stock changes**.
3. Optionally enter words in **Search for products** to search inside each store.
4. Set the maximum products/pages per store and start the run.
5. Open **Dataset** for results and **Storage → Key-value store** for the CSV
   and `RUN_SUMMARY.json`.

#### Export ten products

```json
{
  "storeUrls": ["https://woocommerce.com"],
  "mode": "catalog",
  "maxProductsPerStore": 10,
  "saveCsvFile": true
}
```

#### Monitor a filtered catalog

```json
{
  "storeUrls": ["https://woocommerce.com"],
  "mode": "monitor",
  "searchQuery": "Per Product Shipping",
  "maxProductsPerStore": 100,
  "monitorKey": "demo-extensions",
  "outputInitialSnapshot": true,
  "saveCsvFile": true
}
```

The first **complete** run saves the baseline. Later runs using the same monitor
name, store and filters compare against it. Changing a filter starts a different
baseline, avoiding misleading missing-product alerts across different searches.
Different initiating Apify users and Actor deployments have separate named state stores.

**Increase the limits for large catalogs.** If the store reports 1,759 matching
products but your limit is 500, catalog mode can export the first 500; monitor
mode will preserve the prior baseline rather than comparing a truncated list.

### What changes are reported?

- `price_drop` / `price_rise`, with old price, new price and percentage change.
- `back_in_stock` / `out_of_stock` when known boolean availability changes.
- `new_product` when an ID first appears in the same result scope.
- `missing_from_results` when a previous ID is absent from a complete scan.
- Currency, sale status, price-range or product-detail changes when observed.

Multiple changes to one product share **one output record**. A new baseline is
labelled `record_type: baseline`, not presented as hundreds of new-product alerts.
Set `outputInitialSnapshot: false` to save the first baseline silently.

An unchanged monitor produces zero dataset rows and a header-only CSV. This is a
successful check, not an error. Check the run summary for the store status.

#### No false disappearance alerts from failed scans

The Actor checks pagination totals, page sizes, duplicate IDs and changing totals.
A blocked, failed, capped or inconsistent scan never advances the monitoring
baseline. If output is truncated by the result spending limit, the baseline is
also preserved so unreported changes are not silently consumed.

`missing_from_results` is **not proof that a product was deleted**, discontinued
or sold. Store visibility, filtering and catalog changes can affect exposure.
Pagination is not a transactional database snapshot; changes during scanning
cannot be ruled out completely.

### 📁 Output and downloads

| Field | Meaning |
| --- | --- |
| `record_type` | `product`, `baseline` or `change` |
| `store_url`, `product_id`, `product_url` | Store-scoped product identity and source link |
| `name`, `sku`, `product_type` | Published product fields when available |
| `price`, `regular_price`, `sale_price` | Decimal strings converted using the API's currency precision |
| `min_price`, `max_price` | Public price range when supplied |
| `currency`, `in_stock`, `on_sale` | Observed currency and availability/sale flags |
| `old_price`, `old_in_stock`, `change_types` | Previous values and detected changes |
| `price_change_percent` | Percentage change when prices are comparable and old price is nonzero |
| `image_urls`, `categories` | Up to ten image URLs and twenty categories |
| `scan_complete`, `observed_at` | Completeness and collection time |

Files in the default key-value store:

- **`woocommerce_results.csv`** — dataset output; configurable filename, enabled
  by default. CSV cells are escaped to reduce spreadsheet formula injection.
- **`RUN_SUMMARY.json`** — every store's status, limits, counts and errors.
- **`PARTIAL_PRODUCTS_01.json`**, etc. — per-store page checkpoints, including
  incomplete scans. Recovery data, not automatic resume or a monitor baseline.

Monitoring baselines are compact records in a separate persistent named
key-value store. Do not delete that store if you want the watchlist to continue.
Run schedules with the same monitor name/store **serially**. Conflict detection
is optimistic, not a distributed lock, so overlapping runs are unsupported.

### Who is this for?

- E-commerce sellers watching competitor pricing and promotions.
- Sourcing teams comparing retail price points with supplier quotations.
- Agencies exporting client-owned catalogs or maintaining product watchlists.
- Researchers collecting public catalog snapshots with clear source evidence.

It does not automatically match products to Alibaba, calculate profit margins,
change store prices, publish listings or make purchases.

### Important limits

- Reads only the documented public Store API. No Admin API, customer/order
  records, cookies, credentials, private data or paid provider are accessed.
- Public stock status is **not exact inventory, sales or demand**. Individual
  variation prices/SKUs and reviews are not separately crawled in this version.
- Names are limited to 1,000 characters and SKUs to 255. Descriptions are omitted.
- Prices reflect the store's response context. Currency changes are not treated
  as price drops; no exchange-rate conversion or tax/shipping calculation occurs.
- HTTPS only. Internal addresses and IP-literal URLs are rejected. DNS is checked
  and connections pinned to a public address with certificate verification.
- Respects robots rules, uses one paced read-only request at a time, and stops on
  access denial/rate limiting. Redirects are not followed; provide the final
  canonical installation URL. No workaround is attempted for protected stores.
- Default limits: 500 products/store, 100 pages/store, up to 20 stores. Hard
  maximum: 10,000 products/store. These are limits, not completeness guarantees.
- A hard Apify timeout can interrupt final CSV/state writes. Earlier dataset
  writes and per-page checkpoints can still be available.

### API, MCP and scheduling

Deploy to your own Apify account first. Use the **actual deployed Actor ID** with
the [Apify API](https://docs.apify.com/api/v2/acts-runs-post), or load that Actor
through the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp).
The JSON inputs are the same as the form; no deployed Actor ID is invented here.

For monitoring, schedule the same input and monitor name in Apify. Connect the
result dataset to your existing Sheets, Make, n8n or webhook workflow. The Actor
does not independently send emails or messages.

If you enable per-result monetization, one dataset row is one product/baseline/
changed product. CSVs and status reports add no dataset rows. No subscription,
paid data source, monetization setting or public publication has been activated
by this repository. See [deployment](DEPLOYMENT.md).

### Validation and development

See [VALIDATION.md](VALIDATION.md) for observed live results and remaining limits.
Do not equate tests against one official store with universal store coverage.

```bash
python -m pip install -r requirements-dev.txt
python -m src.main --check
python -m unittest discover -s tests -v
```

Tests use synthetic public API responses and mocks; ordinary CI never contacts
stores. `python -m src.live_check <public-demo-url>` is a separate, explicit,
small live check that respects the same access safeguards.

Built against the official [Store API](https://developer.woocommerce.com/docs/apis/store-api/)
and [Products API](https://developer.woocommerce.com/docs/apis/store-api/resources-endpoints/products/).
Independent project, not affiliated with or endorsed by WooCommerce/Automattic.
Use only data you are authorized to access and respect applicable store terms.

# Changelog

This Actor's version history is a separate document: https://apify.com/ozer-studio/woocommerce-product-scraper/changelog.md

# Actor input Schema

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

One HTTPS store homepage per line, such as https://store.com. If WordPress is installed under a subdirectory, include it. Do not paste product, /shop/, category or filtered search URLs. The full public /wp-json/wc/store/v1/products endpoint is also accepted without query parameters.

## `mode` (type: `string`):

Catalog: export the current products. Monitor: save a first complete baseline, then output only changed products on later runs. Use the same monitor name and filters. Incomplete scans never replace the baseline.

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

Optional words to search within EACH supplied store. This is not a search across the internet. Leave blank to export the catalog. Changing this filter starts a separate monitoring baseline.

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

Separate limit for each store. In monitor mode, set this high enough to cover the entire matching catalog. A capped scan does not create/update the baseline; RUN_SUMMARY.json shows the store's reported total.

## `maxPagesPerStore` (type: `integer`):

Up to 100 products per API page. Also stops at the product limit or last verified page. A partial scan cannot be used to infer missing products.

## `onlyInStock` (type: `boolean`):

Filter using the store's own public API. In monitor mode, an item leaving this filtered result is reported as missing_from_results, not proof it was deleted or sold.

## `onlyOnSale` (type: `boolean`):

Filter using the store's own sale flag. Changing this filter creates a separate monitoring baseline.

## `monitorKey` (type: `string`):

Monitor mode only. Reuse this name for later runs of the same watchlist. Use different names for independent schedules. Do not run overlapping jobs with the same name and store. Catalog mode ignores this field.

## `outputInitialSnapshot` (type: `boolean`):

On: first-run products appear as baseline records, not new-product alerts. Off: save the baseline silently, with zero dataset rows. Later monitor runs emit changed products only. Catalog mode ignores this setting.

## `saveCsvFile` (type: `boolean`):

Save dataset output to a CSV in Storage → Key-value store. Catalog rows and monitor changes use the same columns. Store diagnostics and run summaries are separate, uncharged report files.

## `csvFilename` (type: `string`):

A simple .csv filename, not a folder path. An unchanged monitor may produce a header-only CSV, which is expected.

## Actor input object example

```json
{
  "storeUrls": [
    "https://woocommerce.com"
  ],
  "mode": "catalog",
  "searchQuery": "",
  "maxProductsPerStore": 500,
  "maxPagesPerStore": 100,
  "onlyInStock": false,
  "onlyOnSale": false,
  "monitorKey": "default",
  "outputInitialSnapshot": true,
  "saveCsvFile": true,
  "csvFilename": "woocommerce_results.csv"
}
```

# Actor output Schema

## `products` (type: `string`):

No description

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

No description

## `downloads` (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://woocommerce.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("ozer-studio/woocommerce-product-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://woocommerce.com"] }

# Run the Actor and wait for it to finish
run = client.actor("ozer-studio/woocommerce-product-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://woocommerce.com"
  ]
}' |
apify call ozer-studio/woocommerce-product-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,ozer-studio/woocommerce-product-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/cVySmPYIvKDEtnYTS/builds/IAA5e24vwI8FalI9n/openapi.json
