# Chemist Warehouse AU - Products, Prices & Reviews (`abotapi/chemistwarehouse-com-au-scraper`) Actor

Scrape chemistwarehouse.com.au products with prices, RRP, discounts, stock, images, ingredients, directions, warnings, categories, and full customer reviews. Search by keyword or product URLs, with brand, category, price, in-stock filters, and price sorting.

- **URL**: https://apify.com/abotapi/chemistwarehouse-com-au-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** E-commerce, Developer tools, Automation
- **Features:** Limited permissions
- **Stats:** 14 total users, 6 monthly users, 99.3% runs succeeded, 1 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

## Chemist Warehouse Scraper: Products, Pricing & Reviews

Chemist Warehouse Scraper turns the Chemist Warehouse pharmacy and beauty catalogue into structured product data. Search by keyword, browse the Promotions or Clearance collections, or paste product and listing URLs, and get pricing with the was-price and savings the site itself shows, stock and delivery flags, pharmacy scheduling details, full descriptions, and every individual customer review. Export the dataset as JSON, CSV or Excel, or pull it straight into your app through the API.

### Why This Scraper?

- **Three ways in.** Search by keyword, browse the Promotions or Clearance collections, or paste product and catalogue/category listing URLs.
- **Real was-price and savings, never invented.** `wasPrice`, `savingsAmount` and `savingsPercent` are filled in only when the site itself shows a reduction below RRP; equal pricing returns `null` instead of a fabricated saving.
- **Pharmacy detail beyond the product card.** Prescription type, poison schedule, purchase limits, Appendix H eligibility and instant-consult eligibility all come through as separate fields.
- **Every individual review, not just a rating average.** Title, author, body, date, helpful-vote counts and the star-rating breakdown, with an adjustable cap per product.
- **Real filters that actually apply.** Brand and category narrow a keyword search. Price range and in-stock only narrow both a keyword search and a pasted catalogue/category listing URL.
- **Built for schedules.** Incremental mode returns only new and changed products on recurring runs, and a resume option continues one interrupted catalogue pull without re-scraping what is already saved.
- **Send results straight into your apps** through MCP connectors, no extra code required.

### Use Cases

- **Price and promotion tracking:** monitor RRP, savings and promo labels across the Promotions and Clearance collections to catch new discounts.
- **Pharmacy compliance and catalogue audits:** check schedule, prescription type, Appendix H and purchase-limit fields across a brand or category.
- **Review and reputation research:** pull every review for a product line to analyse sentiment, ratings trends and complaint patterns.
- **Competitor and assortment analysis:** build a dataset of a category or brand's full range, pricing and stock status.
- **Deal alerts and dashboards:** feed a price-comparison site, alert bot or inventory dashboard with a recurring incremental feed.

### Data You Get

> Sample shape: values are illustrative placeholders, not from a live record.

| Field | Example |
| --- | --- |
| `productId` / `sku` | `57134` / `"2720171"` |
| `name` / `title` | `"Blackmores Bio C 1000 150 Tablets"` |
| `brand` | `"Blackmores"` |
| `url` | `"https://www.chemistwarehouse.com.au/buy/57134-blackmores-bio-c-1000-150-tablets"` |
| `price` / `rrp` | `21.99` / `48.99` |
| `wasPrice` | `48.99` (only set when below RRP) |
| `savingsAmount` / `savingsPercent` | `27.00` / `55.11` |
| `isOnSpecial` / `promoLabel` | `true` / `"Better Than Half Price"` (the site's own catalogue badge) |
| `currency` / `isInStock` | `"AUD"` / `true` |
| `isClickAndCollect` / `isFastDelivery` | `true` / `true` |
| `prescriptionType` / `schedule` | `"Pharmacist Only Medicine"` / `"S3"` |
| `isAppendixH` / `purchaseLimit` / `transactionLimit` | `false` / `null` / `6` |
| `rating` / `reviewCount` | `4.6286` / `35` |
| `imageUrl` | `"https://static.chemistwarehouse.com.au/products/57134/1.jpg"` (first image) |
| `categories` | `["Chemist Warehouse Australia", "Vitamins & Supplements"]` |
| `categorySlugs` | `["258-medicines", "196-pain-relief"]` (the site's category ids and slugs) |
| `salesProjectionScore` / `isTopPerformer` | `1.1` / `true` (the site's own popularity ranking; `salesRankBuckets` carries its ranking buckets) |
| `ingredients` | `"Ascorbic Acid (Vitamin C) ..."` |
| `directions` / `warnings` | `"Adults take 1 tablet a day ..."` / `"Vitamin supplements should not replace ..."` |
| `images` | `["https://static.chemistwarehouse.com.au/products/57134/1.jpg"]` |
| `createdAt` / `scrapedAt` | `"2019-03-04T00:00:00.000Z"` / `"2026-09-29T04:00:00.000Z"` |

When Fetch reviews is on, each product record also carries a `reviews` array plus `reviewsTotal` and `ratingBreakdown`. Every review entry carries `rating`, `title`, `author`, `content`, `createdAt`, `positiveFeedbackCount`, `negativeFeedbackCount` and `syndicationName` (the review platform that supplied it, when the site reports one); `reviewsTotal` is the site's own total review count for the product, which can be higher than the number of entries returned if Max reviews per product capped it, and `ratingBreakdown` is the star-by-star distribution the site publishes.

### How to Use

1. Pick a **mode**: `search` (keywords and filters) or `url` (paste product pages or catalogue/category listing links).
2. For search mode, add one or more **Search keywords**, and optionally narrow with **Specials / offers**, **Brand**, **Category**, a price range or **In-stock only**.
3. Turn on **Fetch reviews** to collect individual customer reviews, and set **Max reviews per product** to cap how many are pulled per item.
4. Set **Max items** (and optionally **Max pages**) to control run size and cost, then click **Start**.

**Search by keyword:**

```json
{
  "mode": "search",
  "queries": ["panadol"],
  "maxItems": 30
}
```

**Browse a specials collection with price and stock filters:**

```json
{
  "mode": "search",
  "specialsCategory": "promotions",
  "minPrice": 5,
  "maxPrice": 50,
  "inStockOnly": true,
  "sort": "price_asc",
  "maxItems": 50
}
```

#### URL shapes

| URL shape | Example | Behaviour |
| --- | --- | --- |
| Product page | `https://www.chemistwarehouse.com.au/buy/145683/panadol-500mg-16-tablets` | One product, unfiltered. |
| Product page, bare host | `https://chemistwarehouse.com.au/buy/145683/panadol-500mg-16-tablets` | Host normalised to `www`, same as above. |
| Catalogue | `https://www.chemistwarehouse.com.au/catalogue` | Promotions listing. |
| Specials collection | `https://www.chemistwarehouse.com.au/shop-online/3240/clearance` | Clearance listing. |
| Category | `https://www.chemistwarehouse.com.au/shop-online/130/shampoo-conditioner` | Category name resolved against the site's real category list. |
| Brand | `https://www.chemistwarehouse.com.au/shop-online/587/swisse` | Brand name resolved against the site's real brand list. |
| Category with `?page=2` | `https://www.chemistwarehouse.com.au/shop-online/130/shampoo-conditioner?page=2` | The site ignores the page number: the walk starts from the first page, a warning is logged, and the URL counts as a partial scope (nothing is marked EXPIRED from it). |
| Site search link | `https://www.chemistwarehouse.com.au/search?q=panadol` | Scraped as the keyword search "panadol" (the search page itself is never fetched). A search link without a `q` parameter is skipped. |
| Sorted listing | `https://www.chemistwarehouse.com.au/shop-online/3240/clearance?sort=price` | `sort=price` maps to Price: low to high unless the Sort input is a price sort. |

Links the site answers with 404 (`/buy/{id}` without a slug, `/shop-online/{id}` without a slug, `/brands/...`) and site search links without a `q` parameter are skipped with a warning in the log. A category or brand name that cannot be matched logs an error and scrapes nothing for that URL.

A product page link whose product is missing from the site's search catalogue is still collected: the actor reads that product page itself for the record. This covers recently launched and re-listed products that the catalogue has not picked up yet. The product page carries the same product fields (price, RRP, stock, images, ingredients, ratings); catalogue-only extras (sales ranking, full category path) come from the catalogue record and are simply absent on such rows. A URL is reported as not found only when the site answers 404 for it (the product is gone). If a product page cannot be read or does not contain the product record, the URL is reported as unreadable in the log and the run counts as incomplete rather than declaring the product gone.

**Paste product and listing URLs, with reviews:**

```json
{
  "mode": "url",
  "productUrls": [
    "https://www.chemistwarehouse.com.au/buy/145683/panadol-500mg-16-tablets",
    "https://www.chemistwarehouse.com.au/shop-online/3240/clearance"
  ],
  "fetchReviews": true,
  "maxReviewsPerProduct": 20
}
```

**Narrow a whole category by brand:**

```json
{
  "mode": "search",
  "brand": "Blackmores",
  "category": "Vitamins & Supplements",
  "maxItems": 100
}
```

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/chemistwarehouse-com-au-scraper").call(run_input={"mode": "search", "queries": ["panadol"], "maxItems": 30})
for product in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(product["name"], product["price"], product["isOnSpecial"])
```

JavaScript:

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/chemistwarehouse-com-au-scraper').call({ mode: 'search', queries: ['panadol'], maxItems: 30 });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the **Integrations** tab.

#### How Max items and Max pages shape a run

Chemist Warehouse's own product search only returns up to 1,000 results for a single query or filter combination. A run stops once **Max items** is reached, or once **Max pages** result pages have been walked for the current query, whichever comes first. To reach deeper into a large catalogue, narrow with brand, category, price or a specials collection rather than raising Max items alone.

#### Resume and recurring updates

- **Resume** (`resumeFromRunId`) continues one interrupted catalogue pull: paste its run or dataset ID and the actor skips every product already saved there, so you don't pay twice. It cannot be combined with an incremental-mode search that already has saved state; the run fails with a clear message asking you to pick one or the other.
- **Incremental mode** (`incrementalMode`) is for scheduled runs over the same search. Each product is classified `NEW`, `UPDATED` (with `changedFields`), `UNCHANGED` (suppressed and not billed unless `emitUnchanged` is on), or `EXPIRED` (only after a run that scanned the whole tracked search, and only with `emitExpired` on). `REAPPEARED` is produced only for a product that a previous `emitExpired` run had already marked `EXPIRED`, and that is then found again in a later run; until that has happened once, a product that comes back after being missed simply classifies as `UPDATED` or `UNCHANGED`, never `REAPPEARED`. What is compared: every output field (price, was-price, stock, promo flags, name, brand, categories, images, rating, review count and, with Fetch reviews, review entries) decides `UPDATED`; the scrape timestamp, per-review helpful-vote counters and the site's popularity-ranking fields (`salesProjectionScore`, `isTopPerformer`, `salesRankBuckets`) never do, plus anything listed in `ignoreFieldsForChanges`. Ignored fields stay in the output. A run capped by Max items or Max pages, or one that hit a search error, never produces `EXPIRED` rows. `stateKey` names or shares the stored state; leaving it empty derives a separate state automatically per mode/query/filter/fetch-reviews combination.

### Send results into your apps (MCP connectors)

Optionally pipe the scraped products into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step after the scrape: the Apify dataset is never changed.

**What gets written to the connector:** a condensed, human-readable summary of each product, not the full JSON. Each item becomes one entry with a title and its key fields flattened to plain text. The complete record always stays in the Apify dataset.

1. Authorize a connector once under **Apify > Settings > Integrations** (Notion, Linear, Airtable, or Apify).
2. Select it in the **"Pipe results into your apps"** input field. (If the picker is empty, you haven't authorized a connector yet.)
3. For **Notion**, also set `notionParentPageUrl` to the page where items should be created.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.

### Input Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `mode` | string | `search` | `search` (keyword and filters) or `url` (paste product or listing URLs). |
| `queries` | array | `["vitamin c"]` | Search mode only. One or more keywords. Leave empty to browse the whole catalogue with the filters below. |
| `brand` | string | (none) | Search mode only. Restrict to one brand, exact spelling as shown on the site. |
| `category` | string | (none) | Search mode only. Restrict to one category, exact spelling as shown on the site. |
| `specialsCategory` | string | \`\` (Any, all products) | Search mode only. `promotions` or `clearance`, or empty for all products. |
| `productUrls` | array | one example per URL shape (see the URL shapes table) | URL mode only. Product page URLs return that single product, unfiltered. Catalogue, specials, category and brand listing URLs return multiple products, narrowed by price range, in-stock only and sort order. Leaving this field untouched in URL mode scrapes one example per shape; replace it with your own links. |
| `minPrice` | integer | (none) | Only products priced at or above this amount, in whole dollars. |
| `maxPrice` | integer | (none) | Only products priced at or below this amount, in whole dollars. |
| `inStockOnly` | boolean | `false` | Exclude out-of-stock products. |
| `sort` | string | `relevance` | `relevance`, `price_asc`, or `price_desc`. |
| `fetchReviews` | boolean | `false` | Collect individual customer reviews for each product. Adds one review-enrichment unit per full 10 reviews delivered. |
| `maxReviewsPerProduct` | integer | `20` | Cap on reviews collected per product when Fetch reviews is on (maximum 1000). |
| `maxItems` | integer | `20` | Maximum number of products to collect. `0` for no item limit. |
| `maxPages` | integer | `0` | Maximum result pages walked per query. `0` means no limit of your own; a hard cap of 1000 pages per query always applies. The run normally stops at Max items first. |
| `resumeFromRunId` | string | (none) | Continue one interrupted run without re-scraping or re-charging products already collected there. |
| `incrementalMode` | boolean | `false` | Classify each product as NEW, UPDATED, UNCHANGED, REAPPEARED, or EXPIRED against the last run's saved state. |
| `ignoreFieldsForChanges` | array | `[]` | Incremental mode only: output fields (or dotted nested paths such as `reviews.createdAt`) that never make a product UPDATED or appear in `changedFields`. They stay in the output. |
| `stateKey` | string | (none) | Name or share an incremental-mode state. Leave empty to derive one automatically from the search settings. |
| `emitUnchanged` | boolean | `false` | Also return (and bill) products that have not changed since the last run. |
| `emitExpired` | boolean | `false` | Also return (and bill) products no longer found, once a run has fully scanned the tracked search. |
| `proxy` | object | `{ "useApifyProxy": true }` | Connection settings. |
| `mcpConnectors` | array | (none) | Optional: send a summary of each product to apps you authorized under Integrations. |
| `notionParentPageUrl` | string | (none) | Notion connector only: page under which product pages are created. |
| `maxNotifyListings` | integer | `50` | Cap on items written to each connector per run. Does not affect the dataset. |

### Output Example

> Sample shape: values are illustrative placeholders, not from a live record.

```json
{
  "productId": 57134,
  "sku": "2720171",
  "name": "Blackmores Bio C 1000 150 Tablets",
  "title": "Blackmores Bio C 1000 150 Tablets",
  "brand": "Blackmores",
  "url": "https://www.chemistwarehouse.com.au/buy/57134-blackmores-bio-c-1000-150-tablets",
  "price": 21.99,
  "rrp": 48.99,
  "wasPrice": 48.99,
  "savingsAmount": 27.00,
  "savingsPercent": 55.11,
  "isOnSpecial": true,
  "promoLabel": "Better Than Half Price",
  "currency": "AUD",
  "isInStock": true,
  "isClickAndCollect": true,
  "isFastDelivery": true,
  "prescriptionType": null,
  "schedule": null,
  "isAppendixH": false,
  "purchaseLimit": null,
  "transactionLimit": null,
  "rating": 4.6286,
  "reviewCount": 35,
  "categories": ["Chemist Warehouse Australia", "Categories", "Vitamins & Supplements"],
  "ingredients": "Ascorbic Acid (Vitamin C) ...",
  "directions": "Adults take 1 tablet a day ...",
  "warnings": "Vitamin supplements should not replace ...",
  "images": ["https://static.chemistwarehouse.com.au/products/57134/1.jpg"],
  "reviews": [
    {
      "rating": 5,
      "title": "Great product",
      "author": "Reviewer123",
      "content": "Works really well ...",
      "createdAt": "2025-11-20T00:44:12.000+00:00",
      "positiveFeedbackCount": 2,
      "negativeFeedbackCount": 0
    }
  ],
  "reviewsTotal": 35,
  "createdAt": "2019-03-04T00:00:00.000Z",
  "scrapedAt": "2026-09-29T04:00:00.000Z"
}
```

### Plan Requirement

The default proxy setting works out of the box: product, catalogue and category pages load through the datacenter lane, and the actor upgrades to a backup connection automatically after repeated blocks within a run (the log then says "Served over the backup connection"). Search pages can be challenged more often, so for large or frequent runs the residential proxy group gives more headroom; pick it under Proxy & connection. If your account has no proxy access, the run fails with a clear message pointing to https://apify.com/pricing instead of returning an empty dataset.

### FAQ

#### How much does it cost?

Pay-per-event pricing: a start charge, a per-product charge for every record in the dataset, and a review-enrichment charge (the detail-enrichment event) when Fetch reviews is on: one unit per full 10 reviews delivered on a product, charged right after that product is saved, so a product with fewer than 10 reviews adds nothing; never charged for a suppressed unchanged row. Max reviews per product and Max pages are capped at 1000. The **Pricing** tab shows the current rates. Use **Max items** to cap the cost of any run.

#### Is it legal to scrape Chemist Warehouse?

This actor collects only publicly available product and review data. You are responsible for how you use it: follow Chemist Warehouse's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Product descriptions, images and customer reviews may carry their own rights.

#### Can I get only new or changed products on a schedule?

Yes. Schedule the actor from the **Schedules** tab and turn on **Incremental mode**. Each run then classifies every product as NEW, UPDATED, UNCHANGED, REAPPEARED or EXPIRED against the previous run's saved state, and unchanged products are not billed unless you turn Emit unchanged on. REAPPEARED only appears for a product that a previous run had already marked EXPIRED, which itself needs Emit expired on and a complete, uncapped scan; until that has happened once, a product that comes back simply classifies as UPDATED or UNCHANGED.

#### Why does was-price sometimes stay empty?

`wasPrice`, `savingsAmount`, `savingsPercent` and `isOnSpecial` are filled in only when the current price is genuinely below the site's own RRP for that product. When a product is priced at RRP, those fields are `null` or `false` rather than a fabricated saving; `promoLabel` separately surfaces the site's own catalogue badge (for example "Clearance" or "In Catalogue") when one is set.

#### Why did my run fail instead of returning an empty dataset?

The run fails outright in only two cases: a Resume from a previous run value that doesn't match a run or dataset this Apify account can read, or turning on Incremental mode together with Resume from a previous run when that search already has saved incremental state. Outside those two cases, a connectivity problem on part of the catalogue is logged as a warning and the run still finishes, returning whatever was found. An empty result can mean either that nothing matched, or that the source could not be reached for that search; check the run log for warnings to tell them apart.

#### Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the connector field to push a summary of each product into Notion, Linear or Airtable.

### 🔗 Want more e-commerce data?

Pair this actor with these related scrapers from the same team:

<table>
<tr><td>🛒 <a href="https://apify.com/abotapi/target-au-scraper"><b>Target AU Scraper</b></a><br>Scrape products and customer reviews from Target.com.au. Search by keyword or use...</td><td>🛒 <a href="https://apify.com/abotapi/kmart-au-scraper"><b>Kmart Scraper</b></a><br>Scrape products and customer reviews from Kmart.com.au. Search by keyword or use...</td></tr>
<tr><td>🛒 <a href="https://apify.com/abotapi/myer-au-scraper"><b>Myer Scraper</b></a><br>Scrape Myer (myer.com.au) department-store products: name, brand, price, was-price...</td><td>🛒 <a href="https://apify.com/abotapi/officeworks-scraper"><b>Officeworks Scraper</b></a><br>Scrape Officeworks products by keyword, category, or URL. Extract names, brands, prices...</td></tr>
<tr><td>🛒 <a href="https://apify.com/abotapi/costco-au-scraper"><b>Costco AU Scraper</b></a><br>Scrape Costco.com.au products by keyword or product, search and category URLs. Extract...</td><td>🛒 <a href="https://apify.com/abotapi/amazon-au-scraper"><b>Amazon Australia Product &amp; Reviews Scraper</b></a><br>Scrape Amazon Australia (amazon.com.au) products and customer reviews. Search by keyword...</td></tr>
</table>

👉 [Browse all abotapi scrapers](https://apify.com/abotapi)

### 💬 Support

- 🐞 **Found a bug or a missing field?** Open a ticket on the [Issues tab](https://apify.com/abotapi/chemistwarehouse-com-au-scraper/issues/open). We usually reply within hours.
- 🛠️ **Need another site, extra fields or a private build?** Email <contact@abotapi.com>.
- ⭐ **Enjoying it?** A quick review on the actor page helps other users find it.

# Actor input Schema

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

How to collect products. Search runs keyword/filter queries. URL scrapes whatever chemistwarehouse.com.au links you paste: product pages, brand pages, category pages or the specials collections. Discontinued product URLs are reported as not found in the log.

## `queries` (type: `array`):

Search mode only. One or more keywords, e.g. "panadol", "vitamin c". Leave empty to browse the whole catalogue with the filters below.

## `brand` (type: `string`):

Search mode only. Restrict results to one brand, e.g. "Blackmores" or "Panadol" (exact spelling as shown on site).

## `category` (type: `string`):

Search mode only. Restrict results to one category, e.g. "Vitamins & Supplements" or "Pain Relief" (exact spelling as shown on site).

## `specialsCategory` (type: `string`):

Search mode only. Limit results to a Chemist Warehouse specials collection. Promotions covers the current weekly-catalogue offers (½-price, 50% off, better-than-half-price); Clearance is reduced-to-clear stock. Combine with keywords, brand or price to narrow within the specials. Leave as 'Any' for all products.

## `productUrls` (type: `array`):

URL mode only. Paste product pages (/buy/{id}/{slug}, with or without www), the catalogue, a specials collection, a category or a brand page (/shop-online/{id}/{slug}). Product URLs return that single product, unfiltered; listing URLs return multiple products, filtered like a keyword search by the Filters section. ?page=N is ignored by the site (the walk starts at the first page and counts as a partial scope), ?sort=price maps to Price: low to high. Site search links with a q parameter (/search?q=panadol) are scraped as that keyword search. Forms the site answers with 404 (/buy/{id}, /shop-online/{id}, /brands/x) are skipped with a warning. Leaving this untouched in URL mode scrapes one example per URL shape.

## `minPrice` (type: `integer`):

Only products priced at or above this amount, in whole dollars.

## `maxPrice` (type: `integer`):

Only products priced at or below this amount, in whole dollars.

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

Exclude out-of-stock products.

## `sort` (type: `string`):

Result ordering.

## `fetchReviews` (type: `boolean`):

Fetch individual customer reviews (rating, title, author, body, date, helpful counts) for each product. Adds a review-enrichment charge per product.

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

Cap on reviews collected per product when Fetch reviews is on (maximum 1000).

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

Maximum number of products to collect. This is the run cap. Set to 0 for no item limit (the run then stops at Max pages).

## `maxPages` (type: `integer`):

Maximum number of search result pages to walk per query. 0 (the default) means no page limit from you; the actor enforces a hard cap of 1000 pages per query, and the run normally stops at Max items first.

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

Proxy configuration. Apify Proxy is recommended.

## `resumeFromRunId` (type: `string`):

Paste a previous run ID or dataset ID to continue a large catalogue pull without returning or charging for products already collected there. Use this after an interrupted run. For recurring monitoring of the same search, use Incremental mode below instead.

## `incrementalMode` (type: `boolean`):

Turn this on for daily or recurring monitoring. The first run returns all matching products as NEW. Later runs normally return only NEW, UPDATED, and REAPPEARED products. What is compared: every output field (price, was-price and savings, stock, promo flags, name, brand, categories, images, rating, review count and, with Fetch reviews, the review entries) decides UPDATED, and changedFields names the fields that moved; the scrape timestamp, per-review helpful-vote counters and the site's popularity-ranking fields (salesProjectionScore, isTopPerformer, salesRankBuckets) never do, plus anything you list in the ignore-fields input below. Rows with no change are UNCHANGED and are neither returned nor billed. Turn on "Emit unchanged" or "Emit expired" only when you also want those products returned (and billed). State is kept separately for each mode/query/filter/fetch-reviews setup; use State key when you want to name or deliberately share a monitoring campaign. To continue one specific interrupted run instead, use Resume from a previous run above.

## `stateKey` (type: `string`):

Optional. Name this monitoring campaign to keep its state stable, or to deliberately share state across differently-configured runs. Leave empty to let the actor derive a key automatically from the mode/query/filter/fetch-reviews settings — different searches then never mix state with each other.

## `emitUnchanged` (type: `boolean`):

Off by default. Turn on to also return products that have not changed since the last run, marked UNCHANGED. This returns — and bills — extra rows you already have, so leave it off unless you specifically want the full snapshot every run.

## `emitExpired` (type: `boolean`):

Off by default. Turn on to also return products that were present in a previous run but are no longer found, marked EXPIRED. Only produced once a run has fully scanned the tracked search — not when Max items capped it or when Resume was used. This returns — and bills — extra synthetic rows, so leave it off unless you need expiry tracking.

## `ignoreFieldsForChanges` (type: `array`):

Output fields that must never make a product UPDATED or appear in changedFields in incremental mode, for example images or rating. Use a field name, or a dotted path for nested data such as reviews.createdAt (lists are searched element by element). The fields stay in the output; only change detection skips them. The scrape timestamp and the site's popularity-ranking fields (salesProjectionScore, isTopPerformer, salesRankBuckets) are always ignored. Changing this list does not re-bill your saved products.

## `mcpConnectors` (type: `array`):

Optionally send the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize a connector once under Apify Settings, then select it here. The connector receives a condensed, human-readable summary per item (title + key fields), not the full JSON; the complete record stays in the dataset. Leave empty to skip. Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com).

## `notionParentPageUrl` (type: `string`):

URL (or id) of the Notion page under which item pages are created. Required to enable the Notion export; ignored by other connectors.

## `maxNotifyListings` (type: `integer`):

Cap on items written to each connector per run. Does not affect the dataset.

## Actor input object example

```json
{
  "mode": "search",
  "queries": [
    "vitamin c"
  ],
  "specialsCategory": "",
  "productUrls": [
    "https://www.chemistwarehouse.com.au/buy/145683/panadol-500mg-16-tablets",
    "https://www.chemistwarehouse.com.au/catalogue",
    "https://www.chemistwarehouse.com.au/shop-online/3240/clearance",
    "https://www.chemistwarehouse.com.au/shop-online/130/shampoo-conditioner"
  ],
  "inStockOnly": false,
  "sort": "relevance",
  "fetchReviews": false,
  "maxReviewsPerProduct": 20,
  "maxItems": 20,
  "maxPages": 0,
  "proxy": {
    "useApifyProxy": true
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": [],
  "maxNotifyListings": 50
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `detail` (type: `string`):

No description

## `reviews` (type: `string`):

No description

## `changes` (type: `string`):

No description

## `summary` (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 = {
    "queries": [
        "vitamin c"
    ],
    "productUrls": [
        "https://www.chemistwarehouse.com.au/buy/145683/panadol-500mg-16-tablets",
        "https://www.chemistwarehouse.com.au/catalogue",
        "https://www.chemistwarehouse.com.au/shop-online/3240/clearance",
        "https://www.chemistwarehouse.com.au/shop-online/130/shampoo-conditioner"
    ],
    "maxPages": 0,
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false,
    "ignoreFieldsForChanges": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/chemistwarehouse-com-au-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 = {
    "queries": ["vitamin c"],
    "productUrls": [
        "https://www.chemistwarehouse.com.au/buy/145683/panadol-500mg-16-tablets",
        "https://www.chemistwarehouse.com.au/catalogue",
        "https://www.chemistwarehouse.com.au/shop-online/3240/clearance",
        "https://www.chemistwarehouse.com.au/shop-online/130/shampoo-conditioner",
    ],
    "maxPages": 0,
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
    "ignoreFieldsForChanges": [],
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/chemistwarehouse-com-au-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 '{
  "queries": [
    "vitamin c"
  ],
  "productUrls": [
    "https://www.chemistwarehouse.com.au/buy/145683/panadol-500mg-16-tablets",
    "https://www.chemistwarehouse.com.au/catalogue",
    "https://www.chemistwarehouse.com.au/shop-online/3240/clearance",
    "https://www.chemistwarehouse.com.au/shop-online/130/shampoo-conditioner"
  ],
  "maxPages": 0,
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": []
}' |
apify call abotapi/chemistwarehouse-com-au-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,abotapi/chemistwarehouse-com-au-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/IH1pxf2wKuTuseJdd/builds/UNbhDe0oIP89Uw829/openapi.json
