# Bunnings Scraper - Products, Reviews & Q\&A (`abotapi/bunnings-com-au-scraper`) Actor

Scrape bunnings.com.au products with full specifications, price, brand, stock, image gallery, warranty, customer reviews, rating stats and Q\&A. Search by keyword with brand, price, rating and sort filters, or paste product / category / search URLs.

- **URL**: https://apify.com/abotapi/bunnings-com-au-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** E-commerce, Automation, Developer tools
- **Stats:** 5 total users, 2 monthly users, 75.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 product results

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

## Bunnings Scraper - Products & Reviews from bunnings.com.au

Extract structured product data from Bunnings Warehouse (bunnings.com.au), Australia's largest hardware and home-improvement retailer. Search by keyword with brand, price, rating and sort filters, or paste product, category and search URLs - and get clean JSON with full specifications, pricing, image galleries, warranty, and **customer reviews with rating statistics**.

Running on the Apify platform gives you API access, scheduling, integrations (Google Sheets, Zapier, webhooks), automatic proxy rotation and full run monitoring out of the box.

### What it does

Give it a keyword (or a list of Bunnings URLs) and it returns one structured record per product. Category and search results are collected across multiple pages, so you can gather hundreds of drills, mowers, paints or fasteners in a single run.

#### Fields per product

| Field | Description |
|-------|-------------|
| `code`, `itemNumber` | Bunnings product code / item number |
| `name`, `title` | Product name and canonical brand-prefixed title |
| `brand`, `brandCode`, `brandUrl`, `brandIconUrl` | Brand name, code, brand page and icon |
| `isLeadingBrand`, `isTradeBrand` | Brand flags |
| `price`, `currency`, `formattedPrice` | Price, currency and formatted price |
| `wasPrice`, `savingsAmount`, `savingsPercent`, `isOnSpecial` | Original / "was" price and discount (see note below - stays empty on Bunnings) |
| `offerTypes`, `isRedemptionOffer` | Site's own product-offer tags (e.g. Redemption Offer, New Arrival) |
| `promotion`, `hasPromotion` | Marketing campaign the product is in (name, dates, OnePass-exclusive flag) |
| `productOffers` | Detail-level product offers list, verbatim (empty on most products) |
| `variants` | Size / colour variant matrix with per-variant code, item number and URL |
| `sustainabilityInformation`, `guideDocuments` | Sustainability info and care / guide documents |
| `fsc`, `colorCount`, `sizeCount`, `hasMultipleOffers` | Certification and variant-count flags |
| `isDeliveryIncluded`, `isPOAProduct`, `isAREnabled` | Additional product flags |
| `priceType`, `priceAvailable` | Price type (e.g. FROM) and availability flag |
| `paymentOptions` | Instalment options (Afterpay, PayPal, Zip) |
| `unitOfPrice` | Unit-of-measure price where shown |
| `rating`, `ratingCount` | Average star rating and number of ratings |
| `reviewStats` | Aggregate rating stats + star distribution |
| `reviews[]` | Review text, star rating, author, date, recommended flag, helpful votes |
| `specifications` | Flattened spec name to value map |
| `features[]` | Full feature list (name, code, value) |
| `summary` | Product summary |
| `warranty` | Warranty & returns text |
| `weight`, `dimension` | Weight and packaged / product dimensions |
| `images[]`, `imageUrl`, `thumbnailUrl` | Full image gallery and primary image |
| `categories`, `superCategories`, `categoryPath` | Category taxonomy |
| `keySellingPoints` | Key selling points |
| `availableForDelivery`, `inStorePickup`, `purchasable` | Availability flags |
| `fulfilment` | Delivery / click-and-collect fulfilment detail |
| `isTradeOnly`, `isSpecialProduct`, `isDangerousGood`, `customMade` | Product flags |
| `bestSeller`, `newArrival`, `forHire`, `ageRestricted` | Merchandising flags |
| `variantCount`, `size`, `productRanges` | Variant and range info |
| `url`, `source`, `searchMode` | Canonical URL and provenance |

### Input

Configure the run in the **Input** tab.

#### Search mode

```json
{
  "mode": "search",
  "queries": ["cordless drill", "exterior paint"],
  "brand": "Ryobi",
  "sortBy": "ratingDesc",
  "minPrice": 20,
  "maxPrice": 500,
  "minRating": 4,
  "productOffers": "redemption-offer",
  "fetchDetails": true,
  "fetchReviews": true,
  "maxItems": 50
}
```

#### URL mode

```json
{
  "mode": "url",
  "urls": [
    "https://www.bunnings.com.au/our-range/tools/power-tools/drills",
    "https://www.bunnings.com.au/search/products?q=hammer",
    "https://www.bunnings.com.au/ryobi-18v-one-drill-driver-starter-kit-r18dd22_p0715241"
  ],
  "fetchDetails": true,
  "fetchReviews": true
}
```

#### Options

- **mode** - `search` (keyword + filters) or `url` (paste links). Product URLs (ending in `_p<id>`) return a single product; category and search URLs return multiple pages of results.
- **queries** - one or more search keywords (search mode).
- **brand** - keep only products whose brand contains this text.
- **sortBy** - `relevance`, `priceAsc`, `priceDesc`, `ratingDesc`, `reviewsDesc`, `nameAsc`.
- **minPrice / maxPrice / minRating** - result filters.
- **productOffers** - keep only products carrying the site's own product-offer tag: `redemption-offer` (bonus / gift-with-purchase promotions) or `new-arrival`. Leave `any` to include everything. In URL mode you can instead paste a search or category URL with `?productoffers=Redemption Offer`.
- **fetchDetails** - open each product page for full specifications, warranty, dimensions, gallery and price breakdown (default on).
- **fetchReviews** - collect customer reviews + rating statistics.
- **maxReviewsPerProduct** - cap reviews per product (0 = all).
- **maxItems** - overall cap on products returned across the run.
- **maxPages** - `0 = walk the whole catalogue` (the run still stops at the storefront's own result total or a page repeat — no artificial ceiling).
- **resumeFromRunId** - prior run ID (or dataset ID) to resume from — seeds the de-dup set so a re-run only appends new items.
- **proxy** - Apify Proxy with residential access in Australia is recommended for reliable results.

### Incremental & dedup mode (recurring monitoring)

`resumeFromRunId` continues **one** interrupted run. `incrementalMode` is for
running the **same search again and again** (daily/scheduled) and getting
only what changed, without pasting a run id every time - the actor remembers
the previous run of the same search itself.

Turn on **`incrementalMode`** and every product is classified against the
last run of the same search/filter setup:

- **NEW** - not seen before
- **UPDATED** - a real catalogue field changed (`changedFields` lists which)
- **UNCHANGED** - nothing changed since last time (suppressed - not
  returned or billed - unless `emitUnchanged` is on)
- **REAPPEARED** - was gone, is back
- **EXPIRED** - was tracked, no longer found (only synthesized when
  `emitExpired` is on AND the run fully scanned every keyword/URL with no
  `maxItems`/`maxPages` cap hit and no `resumeFromRunId` - a partial run
  never marks anything expired)

Every row also carries `firstSeenAt` / `lastSeenAt`. State is scoped
automatically to your exact mode/keywords/URLs/filters (two different
searches never share a baseline), or pin it yourself with **`stateKey`** to
name a monitoring campaign explicitly. `emitUnchanged` and `emitExpired` are
both off by default because they return - and bill - extra rows you already
have.

**What counts as a change:** price, was-price, savings, `isOnSpecial`,
`offerTypes`/`isRedemptionOffer`, `promotion`/`hasPromotion`, `priceType`,
fulfilment and the specifications/variants are all real catalogue data and
trigger UPDATED. The aggregate **`rating`**/**`ratingCount`** and the
**`reviews`**/**`reviewStats`** fields are driven by other customers rather
than the product itself and change far more often than the catalogue data,
so they are deliberately excluded from change detection (still fully
returned as usual) - otherwise almost every recurring run would look like
an update.

### Product offers, was-price & discounts

Bunnings runs an **everyday-low-price** model, so it is important to know what is and is not available:

- **Was-price / discount fields stay empty.** Bunnings publishes no strike-through, "was", RRP or savings price anywhere in its product data - not on the listing card and not on the product page. The `wasPrice`, `savingsAmount`, `savingsPercent` fields are therefore `null` and `isOnSpecial` is `false` for every product. These fields are included for a consistent schema and populate only if a genuine reduced price ever appears - they are **never fabricated** from other numbers.
- **There is no half-price / clearance / catalogue product listing.** Bunnings has no `/specials`, `/clearance` or `/deals` product page (those paths return "not found"), and the online catalogue is a flip-book viewer, not a product feed. So there is no specials category to select.
- **What Bunnings does expose is the "Product offers" tag** on its own search and category pages, with two values: **Redemption Offer** (bonus / gift-with-purchase promotions) and **New Arrival**. Use the `productOffers` input to filter to these. Matching products carry the tag verbatim in `offerTypes` / `isRedemptionOffer`.
- **Marketing campaigns** a product belongs to (for example free-delivery or OnePass-member campaigns) are surfaced verbatim in `promotion` / `hasPromotion`. These are delivery / loyalty campaigns, not price reductions.

### Pricing

You are billed for the results you actually collect: a small charge per product returned, plus optional charges only when detail extraction or reviews are enabled. See the actor's Pricing tab for current rates.

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

Optionally pipe results into the apps you already use via Model Context Protocol (MCP) connectors - Notion, Linear, Airtable or Apify. Authorize a connector under Apify, Settings, API & Integrations, then select it in the input. This never changes the dataset output.

### Notes

- Review availability varies per product; products with no reviews return an empty array.
- **Plan Requirement:** runs on any Apify plan. On the free plan, proxy coverage may be limited and some runs may return fewer results.

# Actor input Schema

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

Choose 'search' to use keywords and filters, or 'url' to scrape specific product, category or search URLs you paste below.

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

One or more keywords to search, for example 'cordless drill' or 'exterior paint'. Each keyword is searched separately.

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

Optional. Keep only products whose brand contains this text, for example 'Ryobi', 'Makita' or 'Ozito'.

## `sortBy` (type: `string`):

Ordering applied to the collected results.

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

Optional. Only keep products priced at or above this amount (AUD).

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

Optional. Only keep products priced at or below this amount (AUD).

## `minRating` (type: `integer`):

Optional. Only keep products with an average customer rating at or above this value (1 to 5).

## `productOffers` (type: `string`):

Optional. Keep only products carrying one of the site's own 'Product offers' tags. 'Redemption offers' are bonus / gift-with-purchase promotions; 'New arrivals' are newly listed products. Note: Bunnings runs everyday-low pricing, so it publishes no strike-through 'was' price and has no half-price / clearance section; the was-price fields stay empty. Leave as 'Any' to include all products.

## `urls` (type: `array`):

Product, category or search URLs to scrape, for example https://www.bunnings.com.au/our-range/tools/power-tools/drills or https://www.bunnings.com.au/search/products?q=hammer. Multiple URLs supported.

## `fetchDetails` (type: `boolean`):

Open each product page for full specifications, warranty, dimensions, image gallery, brand detail and price breakdown. Adds one request per product.

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

Also collect customer reviews for each product (review text, star rating, author, date) plus aggregate rating statistics. Adds requests per product.

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

Cap on reviews collected per product when 'Fetch customer reviews' is on. Use 0 for all available reviews.

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

Maximum number of products to return across the whole run. Use 0 for unlimited.

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

Optional safety cap on result pages walked per keyword / URL. Use 0 to walk every page (the run still stops automatically once Max products is reached, or once a page repeats or the store's own result total is reached).

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

Optional. ID of a previous run of this actor (or a dataset ID). Products already in that dataset are skipped, so this run returns only NEW products (a delta). Combine both runs' datasets for the full set. Max products then counts only the new products. For recurring daily 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. Turn on "Emit unchanged" or "Emit expired" only when you also want those products returned (and billed). State is kept separately for each distinct search/filter setup (or by State key below). Off by default.

## `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 your search/filter 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 products or Max pages capped it, or when Resume was used. This returns - and bills - extra synthetic rows.

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

Apify Proxy with residential access in Australia is recommended for reliable results. On the free plan, coverage may be limited and some runs may return no results.

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

Optionally send results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize one under Apify, Settings, API & Integrations, then select it here. Notion gets a rich page-per-item export; other connectors get a best-effort write/digest. Leave empty to skip; never changes the dataset output. 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": [
    "cordless drill"
  ],
  "sortBy": "relevance",
  "productOffers": "any",
  "urls": [
    "https://www.bunnings.com.au/search/products?q=hammer"
  ],
  "fetchDetails": true,
  "fetchReviews": false,
  "maxReviewsPerProduct": 20,
  "maxItems": 20,
  "maxPages": 0,
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "AU"
  },
  "maxNotifyListings": 50
}
```

# Actor output Schema

## `overview` (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 = {
    "mode": "search",
    "queries": [
        "cordless drill"
    ],
    "urls": [
        "https://www.bunnings.com.au/search/products?q=hammer"
    ],
    "proxy": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "AU"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/bunnings-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 = {
    "mode": "search",
    "queries": ["cordless drill"],
    "urls": ["https://www.bunnings.com.au/search/products?q=hammer"],
    "proxy": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "AU",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/bunnings-com-au-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "mode": "search",
  "queries": [
    "cordless drill"
  ],
  "urls": [
    "https://www.bunnings.com.au/search/products?q=hammer"
  ],
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "AU"
  }
}' |
apify call abotapi/bunnings-com-au-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=abotapi/bunnings-com-au-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/LZY10fF8jq4P912zX/builds/uyyqylPEFsrz2Yvkp/openapi.json
