# WooCommerce Scraper (`autofacts/woocommerce-scraper`) Actor

Scrape products, variations, categories and customer reviews from any WooCommerce store. Detects each store's available data surface and adapts, so it works whether the JSON API is open, disabled, or behind a firewall.

- **URL**: https://apify.com/autofacts/woocommerce-scraper.md
- **Developed by:** [Richard Feng](https://apify.com/autofacts) (community)
- **Categories:** E-commerce, Developer tools, MCP servers
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.20 / 1,000 products

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 Scraper

Extracts products, variations, categories and customer reviews from any WooCommerce
store. Give it a domain or any store URL. It also handles stores whose JSON API is
disabled or firewalled, by falling back to the WordPress REST API, the product sitemap
and the product pages themselves.

### 🚀 Quick start

The smallest useful input is a domain and a product cap:

```json
{
  "domains": ["mystore.com"],
  "maxProducts": 100
}
```

To include customer reviews and keep several stores from starving each other:

```json
{
  "domains": ["mystore.com", "otherstore.co.uk"],
  "startUrls": [{ "url": "https://third-store.com/product-category/shoes/" }],
  "maxProducts": 500,
  "maxProductsPerStore": 200,
  "includeReviews": true,
  "maxReviewsPerProduct": 20
}
```

Run it from the Apify Console, or from your own code with the Apify client:

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

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('autofacts/woocommerce-scraper').call({
    domains: ['mystore.com'],
    maxProducts: 100,
    includeReviews: true,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
const products = items.filter((r) => r.variants);
const reviews = items.filter((r) => r.reviewer);
const categories = items.filter((r) => r.productsCount !== undefined);
const summaries = items.filter((r) => r.host);
```

```python
import os
from apify_client import ApifyClient

client = ApifyClient(token=os.environ["APIFY_TOKEN"])
run = client.actor("autofacts/woocommerce-scraper").call(run_input={
    "domains": ["mystore.com"],
    "maxProducts": 100,
    "includeReviews": True,
})
items = client.dataset(run["defaultDatasetId"]).list_items().items
products = [r for r in items if "variants" in r]
reviews = [r for r in items if "reviewer" in r]
```

```bash
curl -X POST "https://api.apify.com/v2/acts/autofacts~woocommerce-scraper/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{ "domains": ["mystore.com"], "maxProducts": 100 }'
```

The dataset mixes four record kinds (see **Output** below). Tell them apart by the
field only that kind has: `variants` means a product, `reviewer` a review,
`productsCount` a category, `host` a store summary. In the Console the dataset has
**Products**, **Variants** and **Reviews** tabs.

### ❓ Is the site a WooCommerce store?

The Actor only works on WooCommerce (WordPress) stores, and it tells you when a URL
is not one. To check a site yourself first, in order of reliability:

**1. Ask the store's API.** Open this in a browser, replacing the domain:

```
https://store.com/wp-json/wc/store/v1/products?per_page=1
```

| Response | Meaning |
|---|---|
| a JSON array with one product (`id`, `name`, `prices`, …) | WooCommerce, API open. The best case. |
| `{"code":"rest_no_route", …}` | WordPress, but no WooCommerce Store API. Either not WooCommerce, or the API is disabled. Check the page markers below. |
| a 404 HTML page | Possibly WordPress with plain permalinks. Try `https://store.com/?rest_route=/wc/store/v1/products&per_page=1`. |
| 403, a captcha or a "checking your browser" page | Firewalled. It may still be WooCommerce; the Actor tries the other surfaces. |
| anything else (a normal HTML page, connection error) | Probably not WordPress at all. |

**2. Look at the page source** (right click → View Page Source, or the browser's
developer tools) of the home page or any product page. WooCommerce leaves these marks,
and the Actor itself relies on the same ones:

- `woocommerce` in the `<body class="…">` list. A product page also has
  `single-product` and `postid-<number>`; a category page has `tax-product_cat`.
- `<meta name="generator" content="WooCommerce x.y.z">` in the `<head>` (many themes
  remove it, so its absence proves nothing).
- Asset URLs containing `/wp-content/plugins/woocommerce/`.
- On a product page, a `<form class="cart">` or `<form class="variations_form">`, an
  `?add-to-cart=<id>` link, or a `woocommerce-product-gallery` element.

**3. URL shapes are only a hint.** `/product/…`, `/product-category/…`, `/shop/`,
`/cart/`, `/checkout/` and `/my-account/` are WooCommerce defaults, but every one of
them can be renamed, and other platforms use some of the same words. Do not decide on
the URL alone.

**4. Rule out look-alikes.** A WordPress site can sell through something else:

- cart or checkout on `checkout.shopify.com` or a `*.myshopify.com` domain: Shopify
  with a WordPress front, not WooCommerce;
- `edd-` classes and `/edd-` URLs: Easy Digital Downloads;
- an embedded `ecwid` or `bigcommerce` widget: those platforms, not WooCommerce.

**5. Or let the Actor check.** Run it with the domain and `"maxProducts": 1`. A
WooCommerce store yields a free store summary record (with `productCount`, and
`detail` telling you whether it was read through the API or the HTML fallback) and one
product. Anything else ends with `not a WooCommerce catalog URL: <url>` or
`no usable data surface on <host>: …` in the log, and nothing is billed.

Browser extensions such as Wappalyzer or BuiltWith also detect WooCommerce, and are
usually right when they say yes. When they say no, still try step 1: a headless store
(a custom front end on a WooCommerce back end) shows none of the page markers but
answers the API.

### 🔗 What to put in `startUrls`

Any of these, for any WooCommerce store, in any permalink style the store uses:

| URL | What is crawled |
|---|---|
| `https://store.com/` (or a bare domain in `domains`) | the whole catalog, plus its categories |
| a category page, e.g. `https://store.com/product-category/shoes/` or `https://store.com/shoes/` | the products in that category, plus one category record |
| a tag page | the products with that tag |
| a search page, `https://store.com/?s=knife&post_type=product` | the products matching the search |
| a product page | that one product |

You do not need to know which kind a URL is. The Actor asks the store. Several
stores can be mixed in one run; two URLs on the same store share that store's cap and
never store the same product twice.

### 📋 Input

| Field | Default | Description |
|---|---|---|
| `startUrls` | — | Store, category, tag, search or product URLs. See above. |
| `domains` | — | Bare domains, one per line. `mystore.com` is the same as a start URL of `https://mystore.com/`. |
| `maxProducts` | `20` | Product records to save in the whole run. `0` = no limit; bound spend with the run's **Max total charge** instead. Categories and reviews do not count against this. |
| `maxProductsPerStore` | `0` | Product records per store. `0` = no per-store limit. Set this whenever a run has more than one store, so one large catalog cannot use up the whole budget before the next store is reached. |
| `includeVariations` | `true` | Fetch every variation of a variable product (its own SKU, price, stock and options), nested inside the product record. Variations are not billed separately but do cost requests. When off, the record still carries one variant and `extraInfo.variationsOmitted` says how many were skipped. |
| `includeReviews` | `false` | Save customer reviews as separate `Review` records. Billed per review. |
| `maxReviewsPerProduct` | `20` | Newest reviews kept per product. `0` = all of them. Some stores carry hundreds of reviews per product, so keep this small unless reviews are what you came for. |
| `query` | — | Search term. Every store root or domain in the run is searched for it instead of crawled whole; a category or tag URL is searched within that category or tag. Needs a store with an open API. |
| `allowHtmlFallback` | `true` | Let the Actor read product pages directly when a store has no open API. Turn off to restrict the run to stores with an open API. |
| `maxHtmlDetailRecords` | `200` | Cap on products produced through that fallback in one run. `0` = no cap. |
| `maxConcurrency` | `2` | Parallel requests per store. Most WooCommerce stores are small self-hosted sites and start failing under load; raise this only for stores you know are on solid hosting. |
| `proxy` | Apify proxy | Datacenter proxies are fine. The Actor switches to residential on its own when a store's firewall blocks it. |

`maxRequestsPerCrawl`, the name this field had before version 0.2.0, still works as an
alias of `maxProducts`.

### 📤 Output

#### Product

One record per product, variations nested. `price` values are the store's major
currency unit times 100: `2000` with `"currency": "USD"` is $20.00, and
`currentFormatted` carries the store's own rendering.

```json
{
  "source": {
    "id": "1251",
    "canonicalUrl": "https://www.shoprootscience.com/shop/sample-kit",
    "retailer": "shoprootscience.com",
    "language": "en",
    "currency": "USD"
  },
  "title": "Sample Kit",
  "brand": "",
  "categories": [
    "Samples"
  ],
  "price": {
    "current": 2000,
    "currentFormatted": "$20.00",
    "previous": 2000,
    "stockStatus": "InStock",
    "stockCount": 0
  },
  "stats": {
    "rating": 4.75,
    "reviewCount": 4
  },
  "options": [
    {
      "type": "Sample 1",
      "values": [
        {
          "id": "Bare Facial Serum",
          "name": "Bare Facial Serum"
        },
        {
          "id": "Youth Facial Serum",
          "name": "Youth Facial Serum"
        },
        {
          "id": "Restore Facial Serum",
          "name": "Restore Facial Serum"
        },
        {
          "...": "6 more"
        }
      ]
    },
    {
      "type": "Sample 2",
      "values": [
        {
          "id": "Bare Facial Serum",
          "name": "Bare Facial Serum"
        },
        {
          "id": "Youth Facial Serum",
          "name": "Youth Facial Serum"
        },
        {
          "id": "Restore Facial Serum",
          "name": "Restore Facial Serum"
        },
        {
          "...": "6 more"
        }
      ]
    },
    {
      "type": "Sample 3",
      "values": [
        {
          "id": "Bare Facial Serum",
          "name": "Bare Facial Serum"
        },
        {
          "id": "Youth Facial Serum",
          "name": "Youth Facial Serum"
        },
        {
          "id": "Restore Facial Serum",
          "name": "Restore Facial Serum"
        },
        {
          "...": "6 more"
        }
      ]
    }
  ],
  "variants": [
    {
      "id": "232607",
      "options": [
        "Bare Facial Serum",
        "Arctic-C Vitamin C Serum",
        "Bright Eye Serum"
      ],
      "price": {
        "current": 2000,
        "currentFormatted": "$20.00",
        "previous": 2000,
        "stockStatus": "InStock",
        "stockCount": 0
      },
      "title": "Sample 1: Bare Facial Serum, Sample 2: Arctic-C Vitamin C Serum, Sample 3: Bright Eye Serum",
      "sku": "SAM-BAR-ARCC-BRI"
    },
    {
      "id": "232144",
      "options": [
        "Bare Facial Serum",
        "Arctic-C Vitamin C Serum",
        "Detox Facial Mask"
      ],
      "price": {
        "current": 2000,
        "currentFormatted": "$20.00",
        "previous": 2000,
        "stockStatus": "InStock",
        "stockCount": 0
      },
      "title": "Sample 1: Bare Facial Serum, Sample 2: Arctic-C Vitamin C Serum, Sample 3: Detox Facial Mask",
      "sku": "SAM-BAR-ARCC-DET"
    },
    {
      "...": "13 more variants"
    }
  ],
  "extraInfo": {
    "scrapedAt": "2026-09-05T06:11:16.431Z",
    "dataSource": "store_api",
    "productType": "variable",
    "storeHost": "shoprootscience.com",
    "stockCountSource": "unknown",
    "attributes": [
      {
        "name": "Sample 1",
        "isVariationAxis": true,
        "values": [
          "Bare Facial Serum",
          "Youth Facial Serum",
          "Restore Facial Serum",
          "Bright Eye Serum",
          "Botanic-A Retinol Alternative",
          "Arctic-C Vitamin C Serum",
          "Reborn Facial Mask",
          "Detox Facial Mask",
          "Polish Facial Exfoliant"
        ]
      },
      {
        "...": "2 more"
      }
    ]
  }
}
```

Fields worth knowing:

- **`source.id`** is the store's own product id. It is unique within one store only;
  use it together with `extraInfo.storeHost`. `source.canonicalUrl` is the public
  product page.
- **`price.current`** is the selling price, `price.previous` the regular price (equal
  to `current` when the product is not on sale). For a variable product the record's
  price is the lowest variant price; each variant carries its own.
- **`price.stockStatus`** is `InStock`, `LowInStock` or `OutOfStock`.
  `price.stockCount` is a unit count when the store reveals one; `extraInfo.stockCountSource`
  says whether it did (`low_stock_remaining`, exact), whether the count was inferred
  from the purchase maximum (`add_to_cart_maximum`, an estimate), or `unknown`, in
  which case `stockCount` is `0` for want of data.
- **`variants[]`** always has at least one entry. A simple product carries one variant
  with the product's own id and price. `options[]` lists the variation axes, and
  `variant.options[i]` is the chosen value on axis `options[i]`. A variant's `sku` can
  equal a sibling's (WooCommerce falls back to the parent SKU), so it is not a key.
- **`extraInfo.dataSource`** is `store_api` or `html`. Records read through the HTML
  fallback have no rating, no weight or dimensions, and only the attributes that
  drive variations. Filter on this before comparing records across stores.
- **`extraInfo.productType`** is the store's own product type string (`simple`,
  `variable`, `external`, `subscription`, `bundle` and whatever plugins add). Branch
  on `variants` rather than on this value.
- **`extraInfo.attributes[]`** holds specifications and variation axes, including the
  store's custom ones. **`extraInfo.extensions`** holds raw plugin data when a store
  publishes any. Long and short descriptions (`description`, `details.short_desc`) are
  HTML as the store renders it.

#### Review

One record per review, only when `includeReviews` is on. Only the newest
`maxReviewsPerProduct` reviews of each product are kept, and only reviews the store
has approved. Join to the product on `extraInfo.storeHost` + `productId`.

```json
{
  "id": "137626",
  "productId": "1251",
  "productTitle": "Sample Kit",
  "productUrl": "https://www.shoprootscience.com/shop/sample-kit",
  "rating": 4,
  "text": "98% love. My only two complaints are:\n\n1. It says you get to choice your minis, which is not entirely true. I had to purchase two whole kits to try the things I wanted since you’re only allowed to choose one serum. I really was in between two serums but couldn’t select them as trial sizes unless I purchased two kits separately with a bunch of other products I wasn’t wanting.\n\n2. The serum tubes are complicated. Droppers would be better, but I get not wanting to be wasteful. It’s just that it’s impossible to get the 2-4 drops recommend out of the vial without touching it to your skin or getting way to much product.\n\nOther than that, love this and have already made full sized purchases!",
  "reviewer": "kmgbutler",
  "verified": true,
  "createdAt": "2023-11-30T05:32:52.000Z",
  "extraInfo": {
    "scrapedAt": "2026-09-05T06:11:18.385Z",
    "storeHost": "shoprootscience.com",
    "dataSource": "store_api"
  }
}
```

`text` is plain text with paragraph breaks kept. `verified` is WooCommerce's
verified-owner flag. `createdAt` is UTC.

#### Category

One record per category, with `id`, `title`, `handle` (slug), `description`,
`productsCount`, `parentId` and `canonicalUrl`. Emitted for a category start URL (that
category) and for a whole-store crawl (the store's non-empty categories, at most 50
and never more than a fifth of `maxProducts`).

#### Store summary

One free record per store per run, written before its products: `host`, `currency`,
`language`, `productCount`, `variationCount`, `priceRange`, `stockStatusCounts`,
`productTypeCounts`, and how the store was read (`enumerate`, `detail`, `degraded`).
Use `productCount` and `variationCount` to size a run before paying for it, and
`detail: "html"` to spot a store that was read through the fallback.

### 💳 What you are charged for

Every product record, every category record and every review record is a billed
result. Two surcharges can apply to a product: `html-detail` when it was read through
the HTML fallback, and `real-inventory` when the store published an exact stock count
for it. Variations and the store summary are never billed. Prices are on the actor's
pricing tab.

### ⚠️ Notes and limitations

- **Category, tag and search URLs need a store with an open API.** On a store without
  one the Actor reports an error for that URL instead of silently crawling and
  billing the whole catalog. Point it at the store root to crawl everything.
- **Reviews come from the store's API only.** A store read through the HTML fallback
  yields none; the run log says so for that store.
- **The HTML fallback is slow and capped.** It costs one page fetch per product and
  carries the `html-detail` surcharge. `maxHtmlDetailRecords` bounds it per run;
  `allowHtmlFallback: false` skips such stores entirely.
- **Stores that expose neither an API nor a product sitemap cannot be crawled.** The
  error names every surface that was tried, for example
  `no usable data surface on example.com: store_api=403/waf, wp_rest=403/waf, sitemap=403/waf`.
  A store that lists its products but blocks every way of reading one produces no
  records and no charges (`can be enumerated but not read`).
- **Not a WooCommerce store** is reported as `not a WooCommerce catalog URL: <url>`.
- **All ids are per store.** Products, categories and reviews from two stores can share
  an id. Key on `extraInfo.storeHost` (or the store summary's `host`) as well.
- **Keep concurrency low.** The default of 2 exists because small stores start
  returning errors after a few hundred fast requests. The Actor backs off and
  retries, but a lower rate is cheaper than recovery.
- **What the Actor learned about a store is remembered for 30 days** (24 hours if the
  store misbehaved), in a key-value store named `WOO_STORE_PROFILES` under Storage in
  your Apify account. If a store changes its setup and you want it re-examined now,
  delete that store's entry there.
- **Descriptions are raw HTML.** Strip them yourself if you need text.
- Older WooCommerce versions do not publish `weight`, `dimensions` or `brands`; those
  fields are absent, not empty, when the store does not have them.
- Category records carry `publishedUTC: 0` and `updatedUTC: 0`: WooCommerce categories
  have no timestamps.

***

### 🤖 Use with AI agents

This Actor is callable as a tool by any MCP-capable agent — Claude, Cursor, VS Code — or by your
own code, with no wrapper and nothing extra to deploy.

**Connect over MCP**

```
https://mcp.apify.com?tools=autofacts/woocommerce-scraper
```

In a client that reads an `mcpServers` configuration block:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=autofacts/woocommerce-scraper",
      "headers": { "Authorization": "Bearer YOUR_APIFY_TOKEN" }
    }
  }
}
```

The agent reads this Actor's parameters and their descriptions straight from the input
schema, and the hosted server infers the result field types from the dataset schema — so a
model knows what to send and what comes back before it ever calls anything.

**Or call the API directly**

```bash
curl -X POST "https://api.apify.com/v2/acts/autofacts~woocommerce-scraper/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"startUrls": [{"url": "https://godinguitars.com/"}], "maxProducts": 20}'
```

The response body is the dataset records described above.

### 🧰 Other Actors by autofacts

Apify only auto-recommends Actors in the same category, so here are the ones that actually pair with this scraper:

| Actor | What it's for |
| :--- | :--- |
| [Shopify Scraper](https://apify.com/autofacts/shopify) | The other half of self-hosted ecommerce - same record shape, so the two datasets merge |
| [Shopify Store Leads](https://apify.com/autofacts/shopify-store-leads) | Find and qualify stores before you scrape them |
| [Schema Markup Scraper & SEO Auditor](https://apify.com/autofacts/metadata-scraper) | Audit a store's structured data, Open Graph tags and canonical setup |
| [Universal Web Printer](https://apify.com/autofacts/universal-web-printer) | Render any product page to PDF/PNG for archiving or evidence |

All of them: [apify.com/autofacts](https://apify.com/autofacts)

# Changelog

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

# Actor input Schema

## `startUrls` (type: `array`):

Any WooCommerce store URL: the store root, a category page, a tag page, a search URL or a single product page. The scraper asks the store what a URL is, so you do not need to know how it configured its permalinks. Mix several stores in one run. To name stores by bare domain, use the <b>Store domains</b> field below instead.

## `domains` (type: `array`):

Bare domains, one per line (<code>mystore.com</code>) — the simplest way to name whole stores. Each one is crawled like a start URL of <code>https://mystore.com/</code>. Can be combined with start URLs.

## `maxProducts` (type: `integer`):

How many product records to save in this run, across all stores. Each product is one billed result, so this is the run's cost cap for products. <code>0</code> = no limit — bound spend with the run's <b>Max total charge</b> setting instead. Categories and reviews are capped separately and never count against this number.

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

Cap for each individual store in the run, so one large catalog cannot consume the whole budget before the next start URL is reached. <code>0</code> = no per-store limit. Two start URLs on the same store share one cap.

## `includeVariations` (type: `boolean`):

Fetch every variation of variable products (size/colour combinations) with its own SKU, price and stock, nested inside the product record. Variations are <b>not billed separately</b>, but they cost extra requests: stores average about 7 variations per variable product, so a variation-heavy catalog takes noticeably longer. When off, a variable product still produces one record with a single synthetic variant, and <code>extraInfo.variationsOmitted</code> reports how many were skipped.

## `includeReviews` (type: `boolean`):

Save customer reviews as separate <code>Review</code> records (rating, text, reviewer, verified-buyer flag, date), one per review, joined to the product by <code>productId</code>. Reviews are billed per review. Only available on stores with an open Store API — the HTML fallback has no review surface, and the run says so in its log rather than silently producing none.

## `maxReviewsPerProduct` (type: `integer`):

Newest reviews to keep for each product. <code>0</code> = all of them. Review counts are unbounded — one surveyed store carries 15,152 reviews on 96 products — so keep this small unless reviews are what you came for.

## `query` (type: `string`):

Search term. Every store root or domain in the run is searched for it instead of being crawled whole, and a category or tag URL is searched within that category or tag. Requires a store with an open API — stores on the HTML fallback path have no search surface and report an error for that URL rather than silently crawling the whole catalog.

## `allowHtmlFallback` (type: `boolean`):

A meaningful share of WooCommerce stores have their JSON API disabled or firewalled (28 of 53 stores had it open in our own 2026-09-03 survey). For those, the scraper falls back to parsing product pages directly. This works, but costs roughly <b>100x more requests and 15x more traffic per product</b> than the API path, so it is billed as a separate surcharge event and capped below. Turn this off to scrape only stores with an open API.

## `maxHtmlDetailRecords` (type: `integer`):

Hard cap on how many products may be produced through the expensive HTML fallback path in one run. Only applies when the fallback is enabled. Set to <code>0</code> for unlimited.

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

Most WooCommerce stores are small self-hosted WordPress sites and start returning errors under load — the default of 2 is deliberately conservative and is what keeps runs from being throttled or blocked. Raise it only for stores you know are on serious hosting.

## `proxy` (type: `object`):

Select proxies to be used by your crawler. The scraper escalates on its own when a store's firewall blocks a request, so datacenter proxies are a reasonable default — residential is only needed for the minority of heavily protected stores.

## `maxResults` (type: `integer`):

Maximum number of products to return. Hidden parameter, overrides maxProducts if set.

## `maxRequestsPerCrawl` (type: `integer`):

Deprecated alias of maxProducts, kept so tasks saved before version 0.2.0 keep working. Ignored when maxProducts is set.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://godinguitars.com/"
    }
  ],
  "maxProducts": 20,
  "maxProductsPerStore": 0,
  "includeVariations": true,
  "includeReviews": false,
  "maxReviewsPerProduct": 20,
  "allowHtmlFallback": true,
  "maxHtmlDetailRecords": 200,
  "maxConcurrency": 2,
  "proxy": {
    "useApifyProxy": true
  },
  "maxResults": 0
}
```

# Actor output Schema

## `products` (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 = {
    "startUrls": [
        {
            "url": "https://godinguitars.com/"
        }
    ],
    "maxProducts": 20,
    "proxy": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("autofacts/woocommerce-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 = {
    "startUrls": [{ "url": "https://godinguitars.com/" }],
    "maxProducts": 20,
    "proxy": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("autofacts/woocommerce-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 '{
  "startUrls": [
    {
      "url": "https://godinguitars.com/"
    }
  ],
  "maxProducts": 20,
  "proxy": {
    "useApifyProxy": true
  }
}' |
apify call autofacts/woocommerce-scraper --silent --output-dataset

```

## MCP server setup

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