# ALDI Australia Scraper & Price Tracker (`myth84/aldi-australia-scraper`) Actor

ALDI Australia scraper and price tracker: grocery prices, unit prices per kg or litre, pack sizes, Lower Prices and Special Buys with their on-sale dates, for any keyword, aisle or the whole range, at the ALDI store or postcode you choose. Track price changes between runs. No personal data.

- **URL**: https://apify.com/myth84/aldi-australia-scraper.md
- **Developed by:** [Flatrow Data Studio](https://apify.com/myth84) (community)
- **Categories:** E-commerce, Automation, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.85 / 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

### ALDI Australia Scraper & Price Tracker – grocery prices, unit prices and Special Buys, no personal data

- Verified daily by an automated run on Apify with the default input.
- Price: a fixed price per product saved, nothing else (see the Pricing tab).

ALDI Australia scraper and ALDI price tracker: get ALDI Australia prices from aldi.com.au — shelf price, unit price and price per kg or litre, pack size, Lower Prices, Super Savers and Limited Time Only flags, ALDI Special Buys with their on-sale date — for any keywords, aisles, the Special Buys or the whole range, at the ALDI store or postcode you choose, as a clean table or through an API, no login. Use this ALDI scraper to follow Australian grocery prices, track supermarket prices Australia-wide, compare products per kilo or per litre, and watch a basket with price drop alerts.

**No login, no cookies, no personal data:** nothing to sign in to, no cookies to paste, nothing about shoppers or staff in the output.

- **What you get:** one flat row per ALDI Australia product for any keywords, aisles, ALDI URLs, the Special Buys or the whole range: shelf price, unit price and the price per kg, litre or item, pack size, products sold by weight, promotion flags, Special Buys with their on-sale date and wording, 18+ flag, brand, aisle path, image, link and the store whose prices you read; a price-tracking mode that returns only the price drops, price rises and new products since your previous run. No personal data.
- **For whom:** grocery price trackers and comparison sites, FMCG brands and category managers, retail and inflation analysts, deal hunters and Special Buys fans, budgeting apps, AI agents through MCP.
- **How much:** a fixed price per product saved and nothing else (compute and proxy traffic included, see the Pricing tab); `maxResults` caps every search.

**Sample output**: one real row from a "milk" search at the default ALDI store (G452, Glenmore Park NSW), key columns only (the full row is under *Output example*):

```json
{
  "platformProductId": "000000000000398691",
  "title": "Light Milk 2L",
  "brand": "FARMDALE",
  "price": 3.39,
  "packSize": "2 l",
  "unitPrice": 1.7,
  "unitPriceDisplay": "$1.70 per 1 L",
  "pricePerBaseUnit": 1.7,
  "priceBasis": "l",
  "promotionLabels": [],
  "storeLocationId": "G452",
  "storeRegion": "NSW"
}
```

Scrape ALDI Australia search results for any keywords, any ALDI aisle (Dairy, Eggs & Fridge, Fruits & Vegetables, Pantry, Liquor… or a sub-aisle such as Milk), the Lower Prices, Super Savers and Limited Time Only aisles, the ALDI Special Buys of the coming weeks theme by theme, or the whole ALDI range, and get **one flat row per product**: shelf price, unit price as displayed ("$2.38 per 100 g") and brought to one kilogram, litre or item, pack size, products sold by weight (meat packs and loose fruit and vegetables), promotion labels, on-sale date and "While Stocks Last" wording of each Special Buy, 18+ flag, brand, aisle path, image and link. Choose the store by its id or by a postcode: fresh produce is priced per state. Filter by brand, sort by price or name, and turn on price monitoring to get only what changed since your previous run. This ALDI Australia scraper reads the public product lists of aldi.com.au without logging in, removes duplicates and stops exactly at the number of products you ask for.

**No personal data, by design.** ALDI sells its own products: there is no private seller, product lists carry no customer information, and ALDI Australia publishes no reviews. Stores are shops, not people. Every row goes through a privacy filter before it is saved. The dataset is safe to store, share and feed to AI agents.

### What can you do with ALDI Australia data?

- **Track ALDI prices week after week**: shelf prices and unit prices of a search, an aisle or the whole range, with a scheduled price-monitoring run that returns only the price drops, price rises and new products.
- **Compare products per kilo, per litre or per item**: `pricePerBaseUnit` brings "$2.38 per 100 g" and "$1.70 per 1 L" to one unit, so pack sizes and brands can be ranked by real value.
- **Follow the ALDI Special Buys**: every theme of the coming drops (gardening, toys, tools, Christmas…) with the date each product goes on sale (`availableFrom`) and its wording ("On Sale Wed 04 Nov", "While Stocks Last").
- **Watch the promotions**: products of the Lower Prices, Super Savers and Limited Time Only aisles are flagged in `promotionLabels` wherever they are found.
- **Compare stores and states**: fresh produce prices and the range differ between ALDI stores (see *Choose your store*); every row says which store it comes from.
- **Compare supermarkets** with a stable, documented schema built for groceries (`ecommerce-grocery/1.0`, shared with the studio's Coles scraper), with a `matchKey` column to find the same product at another retailer.

### What data does ALDI Australia Scraper extract?

Every row has every column: a value ALDI does not publish is `null` (or `[]` / `{}` for lists and objects), so CSV and Excel exports keep the same columns from run to run.

| Group | Columns |
|---|---|
| Identity | `schemaVersion`, `platform`, `platformProductId`, `variantId`, `url`, `title`, `brand`, `category`, `categoryPath` |
| Prices and stock | `currency`, `price`, `originalPrice`, `discountPercent`, `inStock`, `stockQuantity` |
| Popularity | `rating`, `ratingMax`, `reviewCount`, `soldCount`, `isBestSeller`, `isChoice`, `listedAt` |
| Seller | `storeType`, `storeName`, `storeId`, `storeUrl`, `storeRating` |
| Shipping | `shippingFree`, `shippingCost`, `deliveryEstimate`, `shipsFrom` |
| Media and attributes | `imageUrl`, `imageCount`, `attributes` |
| Search context | `isSponsored`, `searchQuery`, `searchRank`, `searchPage`, `viewCountryCode`, `scrapedAt` |
| Price monitoring | `changeType`, `previousPrice`, `priceDelta`, `priceDeltaPercent` (filled in price-monitoring runs only, see *Price monitoring*; `null` otherwise) |
| Pack and unit price | `packSize`, `unitPrice`, `unitPriceQuantity`, `unitPriceUnit`, `unitPriceDisplay`, `pricePerBaseUnit`, `priceBasis`, `soldByWeight`, `priceIsEstimate`, `rawPrice` |
| Promotions and Special Buys | `promotionLabels`, `promotionType`, `specialType`, `saveAmount`, `onlineOnlySpecial`, `multiBuyQuantity`, `multiBuyUnitPrice`, `multiBuyTotalPrice`, `isSpecialBuy`, `availableFrom`, `availabilityLabel` |
| Limits and matching | `retailLimit`, `promotionalLimit`, `ageRestricted`, `ean`, `matchKey` |
| Store of the prices | `storeLocationId`, `storeLocationName`, `storeRegion`, `storePostcode` |

ALDI Australia sells in store only and shows no rating, review count, stock, sales figure, was price or barcode online, so `rating`, `reviewCount`, `soldCount`, `inStock`, `stockQuantity`, `ean` and, so far, `originalPrice`, `discountPercent` and `saveAmount` stay empty (they are read if ALDI ever shows a was price); so do `variantId` (each size is its own product), `storeId` and `storeRating` (`storeType` is always `platform`, `storeName` "ALDI" and `storeUrl` the ALDI website), the shipping columns, `isBestSeller`, `isChoice`, `listedAt`, `rawPrice` (ALDI prices are whole cents), `isSponsored` (always `false`: no sponsored placement) and the columns other supermarkets of the schema fill: `promotionType`, `specialType`, `onlineOnlySpecial`, the multibuy columns, `retailLimit` and `promotionalLimit`.

`attributes` holds the other facts of the product as flat strings, only the keys the product shows: `quantityUnit` (ea, pac, kg, bt…), `weightType` (0 sold by the unit, 1 a pack sold by weight, 3 loose by the kilo), `abstractSku` (the sku of the product family), `alcohol` (alcohol by volume, rare), `quantityMax` (most units per purchase on the site), `comparisonDisplay` (the unit price wording) and, for products read from their URL, `countryOrigin` when ALDI fills it.

### How to scrape ALDI Australia products

1. **Enter search keywords**, one per line ("milk", "coffee beans"), and/or **ALDI aisles** (`Fruits & Vegetables`, `dairy-eggs-fridge/milk`, `Lower Prices` or an aisle URL copied from aldi.com.au), and/or ALDI URLs (search, aisle, product or Special Buys pages). Tick **Special Buys** for every upcoming theme, or **Whole range** for every product.
2. **Choose the store**: an ALDI store id (`storeId`, G452 by default) or a postcode (`postcode`): the nearest store is used.
3. **Optionally filter by brand** (`brands`, as ALDI writes them: FARMDALE, CHOCEUR…), pick the sort order (relevance, price or name) and the maximum number of products per search.
4. **Run** and open the **Output** tab: the *Overview*, *Prices*, *Promotions* and *Basket* views show the main columns; export everything as JSON, CSV, Excel or through the API.
5. **To track prices**, turn on `monitorPrices` and `onlyChanges` and schedule the run: from the second run on you get only the price drops, price rises and new products, in the *Changes* view.

### Input

Give at least one keyword in `searchQueries`, one aisle in `categories`, one URL in `startUrls`, or turn on `specialBuys` or `wholeRange` (with nothing at all, "milk" is searched). The form is prefilled with the keyword "milk": clear it when you list only aisles, URLs, the Special Buys or the whole range. Give either `storeId` or `postcode`, not both. Field names and defaults are those of the input form.

The input of our validation run: the first 60 products for "milk" at the default store (every other field keeps its default):

```json
{
  "searchQueries": [
    "milk"
  ],
  "maxResults": 60
}
```

Every field, with its type and default:

| Field | Type | Default | Description |
|---|---|---|---|
| `searchQueries` | string\[] | — | Search keywords, one search per entry, as typed on aldi.com.au. Prefilled with `["milk"]` in the form (clear it to list only aisles, URLs, the Special Buys or the whole range); with nothing else to read, the Actor searches "milk". |
| `categories` | string\[] | `[]` | ALDI aisles to list in full: a name (`Fruits & Vegetables`), a slug or slug path (`dairy-eggs-fridge/milk`), an aisle key (`960000000`) or an aisle URL, read by the Actor and never opened. An unknown aisle is left out with a message. |
| `startUrls` | string\[] | `[]` | URLs copied from aldi.com.au: an aisle, the whole range (`/products`), a search (`/results?q=…`: only the words are read, the page is never requested), a product (`/product/<name>-<sku>`, one request each) or Special Buys (`/special-buys/<date>?theme=…`). |
| `specialBuys` | boolean | `false` | Every current and upcoming Special Buy: one search per theme of the Special Buys calendar; `maxResults` caps all the themes together. |
| `wholeRange` | boolean | `false` | The whole range of the store (about 3,300 products at build time): set `maxResults` to 0 to get everything. |
| `storeId` | string | — | ALDI store whose prices and range you read, e.g. `G452` (Glenmore Park NSW, used when neither `storeId` nor `postcode` is given), `G048` (Joondalup WA), `G284` (Fitzroy VIC). |
| `postcode` | string | — | Australian postcode (4 digits): the nearest ALDI store is used and reported in the store columns. Not with `storeId`. |
| `brands` | string\[] | `[]` | Keep only these brands, as ALDI writes them (`FARMDALE`); each brand is searched separately and `maxResults` caps each source with all its brands. |
| `sortBy` | string | `"relevance"` | `relevance` (ALDI default), `price_asc`, `price_desc` and `name_asc`, validated; `name_desc` is used by other tools and not verified yet. |
| `maxResults` | integer | `60` | Stop each source (keyword, aisle, URL, the Special Buys, the whole range) after this many unique products (0 = everything, up to 5,000). You are never charged for more. The form is prefilled with 20 for a quick first run. |
| `markPromotions` | boolean | `true` | Fill `promotionLabels` with the promotion aisles listing each product at the store (Lower Prices, Super Savers, Limited Time Only): about 12 extra requests per run, no extra row, no extra charge. |
| `monitorPrices` | boolean | `false` | Price monitoring: compare every product with the previous run of the same search or product list (by ALDI sku and store) and fill `changeType`, `previousPrice`, `priceDelta`, `priceDeltaPercent`. `maxResults` then counts the products compared per source. See *Price monitoring*. |
| `onlyChanges` | boolean | `false` | With `monitorPrices`: return and charge only the new products and the price changes; a run without any change succeeds with an empty dataset. |
| `monitorStoreName` | string | `"aldi-au-price-monitor"` | Key-value store of your account that keeps the previous prices, one record per search and store, one for the product URLs (letters, digits and dashes, lower-cased). One name per schedule. |
| `proxyConfiguration` | object | `{"useApifyProxy":true}` | Apify proxy, automatic datacenter group: the configuration validated for ALDI Australia, its traffic included in the price. See *Proxy*. |
| `maxRequests` | integer | `500` | Safety cap on the requests of one run, preparation, retries and product URLs included. |
| `maxConcurrency` | integer | `2` | Searches read at the same time (1 to 4). |
| `delayMs` | integer | `400` | Pause between two pages of the same search, in milliseconds. |
| `debugStoreHtml` | boolean | `false` | Debugging only: store every answer of ALDI in the key-value store. |

### Output example

One real row, produced by the Actor from a "milk" search at the default ALDI store G452 (the third product of the results page). All columns are present, in this order, for every product:

```json
{
  "schemaVersion": "ecommerce-grocery/1.0",
  "platform": "aldi_au",
  "platformProductId": "000000000000398691",
  "variantId": null,
  "url": "https://www.aldi.com.au/product/farmdale-light-milk-2l-000000000000398691",
  "title": "Light Milk 2L",
  "brand": "FARMDALE",
  "category": "Milk",
  "categoryPath": [
    "Dairy, Eggs & Fridge",
    "Milk"
  ],
  "currency": "AUD",
  "price": 3.39,
  "originalPrice": null,
  "discountPercent": null,
  "inStock": null,
  "stockQuantity": null,
  "rating": null,
  "ratingMax": 5,
  "reviewCount": null,
  "soldCount": null,
  "storeType": "platform",
  "storeName": "ALDI",
  "storeId": null,
  "storeUrl": "https://www.aldi.com.au",
  "storeRating": null,
  "shippingFree": null,
  "shippingCost": null,
  "deliveryEstimate": null,
  "shipsFrom": null,
  "imageUrl": "https://dm.apac.cms.aldi.cx/is/image/aldiprodapac/product/jpg/scaleWidth/600/93645b60-eb11-4046-b28e-2b441f8585a4/farmdale-light-milk-2l",
  "imageCount": 3,
  "attributes": {
    "quantityUnit": "ea",
    "weightType": "0",
    "abstractSku": "000000000000398691A",
    "quantityMax": "99",
    "comparisonDisplay": "$1.70 per 1 L"
  },
  "isSponsored": false,
  "isBestSeller": null,
  "isChoice": null,
  "listedAt": null,
  "searchQuery": "milk",
  "searchRank": 3,
  "searchPage": 1,
  "viewCountryCode": "AU",
  "scrapedAt": "2026-10-24T00:00:00.000Z",
  "changeType": null,
  "previousPrice": null,
  "priceDelta": null,
  "priceDeltaPercent": null,
  "packSize": "2 l",
  "unitPrice": 1.7,
  "unitPriceQuantity": 1,
  "unitPriceUnit": "l",
  "unitPriceDisplay": "$1.70 per 1 L",
  "pricePerBaseUnit": 1.7,
  "priceBasis": "l",
  "soldByWeight": false,
  "priceIsEstimate": false,
  "rawPrice": null,
  "promotionLabels": [],
  "promotionType": null,
  "specialType": null,
  "saveAmount": null,
  "onlineOnlySpecial": null,
  "multiBuyQuantity": null,
  "multiBuyUnitPrice": null,
  "multiBuyTotalPrice": null,
  "isSpecialBuy": false,
  "availableFrom": null,
  "availabilityLabel": null,
  "retailLimit": null,
  "promotionalLimit": null,
  "ageRestricted": false,
  "ean": null,
  "matchKey": "light milk|2000ml",
  "storeLocationId": "G452",
  "storeLocationName": "Glenmore Park",
  "storeRegion": "NSW",
  "storePostcode": "2745"
}
```

`price` is the shelf price at the store read. `unitPrice` is the unit price as ALDI displays it (`unitPriceDisplay`), for `unitPriceQuantity` × `unitPriceUnit`, and `pricePerBaseUnit` the same price brought to one kilogram, litre or item (`priceBasis`). For a pack of meat sold by weight (`soldByWeight`, `priceIsEstimate`), `price` is the estimated price of the pack ("approx. 1.8 kg per package") and `unitPrice` the price per kilo; for loose fruit and vegetables, `price` and `unitPrice` are the price per kilo. A Special Buy has `isSpecialBuy` true, its on-sale date in `availableFrom` and ALDI's wording in `availabilityLabel`. The four monitoring columns are filled in price-monitoring runs only (see *Price monitoring*).

The **Output** tab offers five views of the same rows: **Overview** (title, brand, size, price, currency, unit price, price per kg / L / item, promotions, availability, aisle, link, image), **Prices** (product id, title, price, unit price and its quantity and unit, price per base unit, sold by weight, estimated price, size, promotions, store and state), **Promotions** (title, price, promotions, Special Buy, on-sale date, availability, aisle, query, 18+ flag, link, image), **Basket** (match key, title, brand, size, price, price per base unit, store id, suburb, state and postcode, product id, link) and, for price-monitoring runs, **Changes** (change, title, price, previous price, change and change %, currency, promotions, unit price, store, query, product id, link).

### Choose your store

ALDI Australia prices its fresh produce per state, and the range differs from store to store. `storeId` takes an ALDI store id (a letter and 3 digits); `postcode` takes an Australian postcode and uses the nearest ALDI store. The default, G452, is the store the ALDI website shows when no store is chosen (Glenmore Park, NSW 2745). Every row carries the store it was read at: `storeLocationId`, `storeLocationName` (suburb), `storeRegion` (state) and `storePostcode`.

Measured at build time in the Fruits & Vegetables aisle: store G048 (Joondalup, WA 6027) listed 141 products against 172 at store G452 (Glenmore Park, NSW), and **21 of the 46 products of the first page found at both stores had another price** — red seedless grapes at $9.99 per kg in NSW and $12.99 in WA, strawberries 250 g at $3.49 and $2.69, gourmet tomatoes 500 g at $4.49 and $5.49. Groceries and liquor had the same price in both states (all 54 milk products and 59 liquor products compared), so choose the store for fresh produce, and for the range.

### Proxy

The default Apify proxy (automatic datacenter group, no country) is enough for ALDI Australia: it served 106 of 109 requests at build time, and the 3 refused ones went through from another IP address. A refused page is tried again on a new proxy session after a short pause, up to 4 times (`RUN_STATS.extra.sessionRotations`); the search is then given up with a message in the log (`RUN_STATS.extra.pagesAbandoned`) and the other searches go on.

If refusals keep coming for a long time, choose the `RESIDENTIAL` group with country `AU` in `proxyConfiguration`: it served every request at build time, about twice as slowly. A page of 60 products is about 15 KB of traffic, so residential proxies cost us a few tenths of a cent per 1,000 products instead of a negligible amount on datacenter proxies; you pay the same price per product either way. Your own proxy URLs are accepted too. Without any proxy, ALDI answered the Apify servers directly at build time, but a refused request cannot then be retried from another IP address.

### Price monitoring

Turn on `monitorPrices` and `onlyChanges`, then **schedule the run** (daily or weekly): the Actor becomes an ALDI price tracker that sends you the price drops. It keeps the last shelf price of every product it compared in a key-value store of your account (`monitorStoreName`, `aldi-au-price-monitor` by default), with one record per search (same keyword, aisle, theme or whole range, brand, sort order and store) and one record per store for the product URLs; a product is identified by its ALDI sku and its store. The first run is the reference: every product comes back with `changeType` = `"new"` (and `RUN_STATS.monitorBaseline` is true). From the second run on, with `onlyChanges` the dataset holds **only the new products and the products whose price changed**, with the previous price and the difference, and **only those rows are charged**; a run where nothing changed succeeds with an empty dataset. Without `onlyChanges`, every compared product is returned with its `changeType` (`new`, `price_changed` or `unchanged`).

A scheduled input that watches the Dairy, Eggs & Fridge aisle at the Joondalup store:

```json
{
  "categories": ["dairy-eggs-fridge"],
  "storeId": "G048",
  "maxResults": 0,
  "monitorPrices": true,
  "onlyChanges": true,
  "monitorStoreName": "aldi-dairy-weekly"
}
```

A `price_changed` row, as the Actor produces it from the row of the output example when the previous run had recorded $3.59 (the other columns are the same as above):

```json
{
  "platformProductId": "000000000000398691",
  "title": "Light Milk 2L",
  "currency": "AUD",
  "price": 3.39,
  "changeType": "price_changed",
  "previousPrice": 3.59,
  "priceDelta": -0.2,
  "priceDeltaPercent": -5.6,
  "storeLocationId": "G452"
}
```

What to know before relying on it:

- **The record is written only after a search that ended cleanly.** A failed or empty search, a page refused on every attempt, a run stopped by its budget (`maxRequests` or your charge limit) or an interrupted run never overwrites the reference, so a bad day cannot erase your prices; the log says when a record was kept unchanged (`RUN_STATS.monitor` counts the records written and skipped).
- **Shelf prices are compared, in AUD.** A product moving to Lower Prices shows as a price drop; the price of a meat pack sold by weight is the estimate of the pack, so it can move with the weight of the packs on sale.
- **Several searches in one run.** Each search compares its own products, so a product found by two searches of the run is recorded in both; it is written to the dataset (and charged) only once (`RUN_STATS.extra.monitorDuplicatesNotWritten`).
- **One store per schedule when searches collide.** Runs sharing a `monitorStoreName` share the records: two schedules with the same search and store would overwrite each other's reference, so give them different store names. Delete the key-value store in Apify Console to start over.

### How much does it cost to scrape ALDI Australia?

This Actor uses **pay-per-event pricing: you pay a fixed price per product** saved to your dataset, and nothing else. The price of every plan tier is on the **Pricing tab** of this page; paid plans pay less per product.

| What you pay for | How it is counted | Price |
|---|---|---|
| `result` event: one product saved to your dataset | once per unique product of the run, capped by `maxResults` for each source; with `onlyChanges`, only the new products and the price changes | Pricing tab, per plan tier |
| Run start | a symbolic amount, once per run | Pricing tab |
| Compute, proxies, storage and data transfer | included: no platform usage is passed on to you; the promotion-label lookups add no row and no charge | — |

The cost of a run is known before you start: 1,000 products cost 1,000 times the `result` price of your tier, whatever the number of pages fetched. A page of 60 products takes about 1.5 to 2 seconds; each run first reads the store and, with `markPromotions`, the three promotion aisles (about 20 seconds). The 20 products of the prefilled form take about half a minute, 1,000 products about a minute, the whole range (about 3,300 products) about two minutes. A product read from its URL costs the same `result` event but takes one request per product.

### Limits and behaviour worth knowing

- **No was price, stock or barcode.** ALDI Australia does not publish them online: `originalPrice` (read if ALDI ever shows one), `inStock`, `stockQuantity` and `ean` stay empty. No rating or review either.
- **In-store prices.** ALDI Australia does not sell groceries online: the prices are those of the store read, and the shipping columns stay empty.
- **Not verified yet.** The `name_desc` sort order (marked as not verified in the input form) and the postcode search outside New South Wales were not exercised at build time; they are confirmed at the first runs. Store ids and postcodes are checked against ALDI's store list at the start of each run.
- **Small range, complete searches.** ALDI keeps a small range (about 3,300 products): a keyword returns every product that matches (97 for "milk", 79 for "coffee" at build time); for more rows, list an aisle (Pantry about 1,050, Lower Prices 540) or the whole range.
- **Products without an aisle.** Many Special Buys have no aisle on aldi.com.au: `category` is `null` and `categoryPath` `[]`.
- **Brands.** A brand filter runs one search per brand and source; a brand ALDI does not know returns nothing for it.
- **Refused pages.** See *Proxy*: a refused page is tried again from another IP address, up to 4 times, then the search is given up with a message (`RUN_STATS.extra.pagesAbandoned`). Products found by several searches are written once (`RUN_STATS.extra.duplicatesSkipped`). Unknown aisles and product URLs are left out with a message (`RUN_STATS.extra.productsNotFound`).
- **No empty successes.** A run that finds no product ends as failed with an explicit message (refused requests, unknown aisles, products or store, misspelt keywords), so that your schedules and integrations notice. The one exception is a price-monitoring run that compared products and found no change: it succeeds with an empty dataset.

### Integrations and API

Start the Actor from Apify Console, schedule it, or call it from the [Apify API](https://docs.apify.com/api/v2) with any language (the **API** tab shows ready-made code for Node.js, Python and curl). Webhooks notify your systems when a run finishes; the dataset can be pulled as JSON, CSV, Excel, XML or RSS, or pushed to Google Sheets, Make, Zapier, n8n, Slack, Airtable, Google Drive and more through the Apify integrations.

### Use it from your AI assistant

Add this Actor as a tool to Claude, ChatGPT, Cursor, VS Code or any MCP client with the Apify MCP server URL below (sign in with your Apify account when asked):

`https://mcp.apify.com?tools=myth84/aldi-australia-scraper`

Then ask in plain words. Copy this prompt and change the details:

```text
Use myth84/aldi-australia-scraper to find the cheapest coffee per kilo at ALDI in Perth this week and tell me which ones are on Lower Prices. Input: {"searchQueries": ["coffee"], "postcode": "6000", "sortBy": "price_asc", "maxResults": 30}. Show the title, pack size, price, price per kg, promotions and link of each product.
```

### For AI agents (MCP)

Through the [Apify MCP server](https://docs.apify.com/mcp) this Actor is a tool that answers questions such as *"how much is a litre of milk at ALDI right now?"*, *"which ALDI Special Buys go on sale next Wednesday?"* or *"what do strawberries cost at ALDI in Perth compared with Sydney?"*.

**Minimal input** (every other field keeps its default; raise `maxResults` only when the question needs more rows):

```json
{
  "searchQueries": [
    "olive oil"
  ],
  "postcode": "2000",
  "sortBy": "price_asc",
  "maxResults": 20
}
```

- **Key output fields**: `platformProductId`, `title`, `brand`, `url`, `price`, `currency`, `packSize`, `unitPrice`, `unitPriceDisplay`, `pricePerBaseUnit`, `priceBasis`, `soldByWeight`, `promotionLabels`, `isSpecialBuy`, `availableFrom`, `availabilityLabel`, `category`, `storeLocationId`, `storeRegion`; in price-monitoring runs `changeType`, `previousPrice`, `priceDelta`, `priceDeltaPercent`. Every column is always present, in a fixed order; `null` means ALDI did not show the value. The full list is in *What data does ALDI Australia Scraper extract?* above.
- **Use it when** you need current ALDI Australia prices: shelf prices, unit prices, promotions, Special Buys and their dates, prices at a given store or postcode. Pass `searchQueries` (or `categories`, `startUrls`, `specialBuys`, `wholeRange`), `storeId` or `postcode`, and a small `maxResults` (20–60 is enough for an answer and fits in one page). To report price changes between two calls, pass `monitorPrices: true`, `onlyChanges: true` and a stable `monitorStoreName`.
- **Do not use it for** other supermarkets, nutrition or ingredients, stock levels, online orders or anything behind a login: it only reads public product lists and collects no personal data.
- **Cost per 1,000 results**: 1,000 `result` events at the price of your plan tier (Pricing tab); nothing else is charged (in price-monitoring runs with `onlyChanges`, unchanged prices are free).
- **Typical time**: about half a minute for 20 products (store and promotion lookups included), about a minute for 1,000.

### Alternatives and switching

- **Official API.** ALDI Australia does not document a public API for its product catalogue. This Actor reads the same public product lists any visitor of aldi.com.au sees (shelf prices, unit prices, promotions, Special Buys), with no account, no keys and no approval.
- **Your own scraper.** Nothing to build or maintain: proxies, retries, pagination, deduplication, store lookup and ALDI website changes are handled for you. The columns stay the same from run to run, a run that finds nothing fails with an explicit message instead of succeeding empty, and a scheduled test run catches ALDI changes early.
- **Another scraper from the Store.** Most read the default store only; here you choose the store or postcode. The input names differ from one Actor to the next; the usual ones map to this Actor as follows:

| Usual input | Here | Note |
|---|---|---|
| `search`, `query`, `keyword` | `searchQueries` | Several keywords per run |
| `category`, `categoryUrls` | `categories` | Aisle names, slugs, keys or URLs |
| `startUrls`, `urls`, `productUrls` | `startUrls` | Aisle, search, product and Special Buys URLs |
| `specialBuys`, `mode: special-buys` | `specialBuys` | Every theme of the coming drops |
| `store`, `servicePoint`, `postcode` | `storeId` or `postcode` | One store per run |
| `maxItems`, `limit` | `maxResults` | Unique products per source, never charged beyond (0 = all) |
| `sort` | `sortBy` | Relevance, price or name |
| `proxy` | `proxyConfiguration` | Keep the default: proxies are included in the price |

### FAQ

#### Does ALDI Australia Scraper collect personal data?

No. ALDI sells its own products, product lists carry no customer information, and ALDI Australia publishes no reviews. The store columns describe a shop, not a person. Every row goes through a privacy filter before it is saved.

#### Is it legal to scrape ALDI Australia?

The Actor only reads product lists that ALDI serves to any visitor without an account, and it stores no personal data. Scraping public data is generally lawful, but ALDI's terms of use may restrict automated access, and you are responsible for how you use the results and for complying with the laws that apply to you (including copyright on product texts and images). Use the data for analysis, not to republish the ALDI catalogue.

#### Do I need proxies, and do I pay for them?

No extra setup: the default proxy configuration (Apify proxy, automatic datacenter group) is the one validated for ALDI Australia, and its traffic is included in the price per product. A refused request is retried from another IP address automatically. See *Proxy* for the residential option.

#### Which store's prices do I get, and why do prices differ?

The prices of the store you choose: `storeId` (G452 by default) or the nearest store of `postcode`. ALDI prices fresh produce per state: at build time 21 of 46 fruit and vegetables cost something else in Joondalup (WA) than in Glenmore Park (NSW), while groceries and liquor had the same price (see *Choose your store*). Every row says which store it comes from.

#### How do I compare prices per kilo or per litre?

Use `pricePerBaseUnit` with `priceBasis`: "$2.38 per 100 g" becomes 23.8 per kg and "$1.70 per 1 L" 1.7 per litre, so every pack size and brand is comparable. For meat sold by weight, `price` is the estimated price of the pack and `unitPrice` the price per kilo; loose fruit and vegetables are priced per kilo.

#### How do I get the ALDI Special Buys?

Tick `specialBuys` (in the form, clear the prefilled "milk" keyword): the Actor reads ALDI's Special Buys calendar and lists every theme of the coming drops, each product with its on-sale date (`availableFrom`) and wording (`availabilityLabel`, e.g. "On Sale Wed 04 Nov", "While Stocks Last"). For one theme, paste its Special Buys URL in `startUrls`. Products already on sale keep their date; their `promotionLabels` include "Special Buy".

#### Can I scrape a whole ALDI aisle, or everything?

Yes. Put the aisle name, slug or URL in `categories` (`Pantry`, `fruits-vegetables`, `dairy-eggs-fridge/milk`) and raise `maxResults` (0 = everything): Pantry holds about 1,050 products, Dairy, Eggs & Fridge about 240, at 60 per page. For the whole range, tick `wholeRange` and set `maxResults` to 0 (about 3,300 products, sorted by name with `sortBy: "name_asc"`).

#### How often should I run it, and how do I get only price changes?

Every run reads live prices. Lower Prices and Special Buys change every week, so schedule the Actor as often as your use case needs (daily or weekly is typical) and compare runs by `platformProductId` and `storeLocationId`, or let the Actor compare them for you: turn on `monitorPrices` and `onlyChanges`, keep the default `monitorStoreName` (or one per schedule) and schedule the run. The first run records every product of the search and returns it as `new`; each later run returns only the products that appeared since and the products whose shelf price changed, with `previousPrice`, `priceDelta` and `priceDeltaPercent`, and charges only those rows. A run without any change succeeds with an empty dataset. Keep the same keywords, aisles, sort, brands and store from run to run.

#### How do I export or integrate the results, and where do I get support?

From the run's Output tab (JSON, CSV, Excel, XML, RSS) or automatically: Apify API, webhooks, scheduled runs and integrations with Google Sheets, Make, Zapier, n8n, Slack and others; AI agents can call the Actor through the Apify MCP server. For bugs or questions, open an issue in the **Issues** tab of this Actor with your run id and input: website changes are fixed as a priority, feature requests (ingredients and nutrition, stock of Special Buys per store, other Australian supermarkets) are welcome.

### More scrapers by Flatrow Data Studio

The e-commerce scrapers of the studio share their first columns, so products from several stores land in comparable tables:

- [Walmart Products Scraper & Price Tracker](https://apify.com/myth84/walmart-search-scraper): Walmart prices, was-prices, stock, ratings and badges on walmart.com and walmart.ca, with price-drop alerts.
- [Amazon Products Scraper & Price Tracker](https://apify.com/myth84/amazon-search-scraper): Amazon prices, list prices, coupons, ratings and "bought in past month" on nine Amazon marketplaces, with sort orders, departments and search URLs.
- [AliExpress Products Scraper & Price Tracker](https://apify.com/myth84/aliexpress-search-scraper): AliExpress prices, discounts, units sold and shipping for any keywords and delivery country, with price-drop alerts.

Did this scraper give you the data you needed? A rating on its Store page helps other people find it. For a bug or a missing field, open an issue in the **Issues** tab.

If this Actor saved you time, a short review helps others find it.

### Legal note

This Actor reads publicly available ALDI Australia product lists without logging in and collects no personal data. You are responsible for your use of the data and for complying with ALDI's terms and the laws that apply to you.

Not affiliated with, endorsed or sponsored by ALDI. ALDI is a trademark of its owner.

# Changelog

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

# Actor input Schema

## `searchQueries` (type: `array`):

Enter one search per line, as you would type it on aldi.com.au, e.g. <code>milk</code>, <code>coffee beans</code> or <code>chicken</code>. A keyword returns every ALDI product that matches (ALDI's range is small). Prefilled with <code>milk</code>: clear it to list only aisles, URLs, the Special Buys or the whole range. Optional: with nothing else to read, the Actor searches milk.

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

ALDI aisles to list in full: a name, a slug, a slug path, an aisle key or an aisle URL copied from aldi.com.au, e.g. <code>Fruits & Vegetables</code>, <code>dairy-eggs-fridge/milk</code>, <code>lower-prices</code> or <code>https://www.aldi.com.au/products/pantry/k/970000000</code> (read by the Actor, never opened). An unknown aisle is left out with a message. Default empty: keywords are enough.

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

URLs copied from aldi.com.au, read by the Actor (never opened): an aisle (<code>https://www.aldi.com.au/products/dairy-eggs-fridge/k/960000000</code>), the whole range (<code>/products</code>), a search (<code>/results?q=milk</code>: only the words are read), a product (<code>/product/\<name>-\<sku></code>, one request each) or Special Buys (<code>/special-buys/2026-10-28?theme=Pet%20Essentials</code>). Default empty.

## `specialBuys` (type: `boolean`):

List every current and upcoming ALDI Special Buy: one search per theme of the Special Buys calendar (e.g. Gardening, Pet Essentials), with the on-sale date of each product. maxResults caps all the themes together. Default off.

## `wholeRange` (type: `boolean`):

List the whole ALDI range of the store (about 3,300 products at build time, 60 per request): set maxResults to 0 to get everything, and sort by name for a stable order. Default off.

## `storeId` (type: `string`):

Id of the ALDI store whose prices and range you want: a letter and 3 digits, e.g. <code>G452</code> (Glenmore Park NSW, the site's default), <code>G048</code> (Joondalup WA) or <code>G284</code> (Fitzroy VIC). Leave empty to use a postcode instead, or the default G452. Not with postcode.

## `postcode` (type: `string`):

Australian postcode: the Actor uses the nearest ALDI store and reports it in the store columns, e.g. <code>6000</code> (Perth), <code>3000</code> (Melbourne) or <code>4000</code> (Brisbane). Leave empty to use the store id (default G452). Not with storeId.

## `brands` (type: `array`):

Keep only these brands, as ALDI writes them, e.g. <code>FARMDALE</code>, <code>CHOCEUR</code> or <code>DAIRY FINE</code> (upper case is applied). Each brand is searched separately for every keyword, aisle and theme; maxResults caps each source with all its brands. Default empty: every brand.

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

Order of the results. Relevance (the ALDI default), price low to high, price high to low and name A to Z were validated at build time; name Z to A is used by other tools but not verified yet. Default relevance.

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

Stop each source (a keyword, an aisle, a URL, the Special Buys, the whole range) after this many unique products; you are never charged for more. One request holds 60 products. 0 means everything, up to 5,000. Default 60; the form is prefilled with 20 for a quick first run.

## `markPromotions` (type: `boolean`):

Fill promotionLabels with the promotion aisles listing each product (Lower Prices, Super Savers, Limited Time Only) at the store: about 12 extra requests per run, at no charge, these lookups add no row. Special Buys are flagged in any case. Default on.

## `monitorPrices` (type: `boolean`):

Off (default): every product is returned and the four monitoring columns stay null. On: every product is compared with the previous run of the same search or product list, by ALDI sku and store, and maxResults becomes the number of products compared per source.

## `onlyChanges` (type: `boolean`):

With price monitoring on: return and charge only the new products and the products whose price changed since the previous run; a run without any change succeeds with an empty dataset. Off (default): every compared product is returned with its changeType (new, price_changed, unchanged). Recommended for scheduled runs.

## `monitorStoreName` (type: `string`):

Name of the key-value store, in your account, that keeps the previous prices (one record per search and ALDI store, one for the product URLs), e.g. <code>aldi-dairy-weekly</code>. Runs sharing a name share the records; use one name per schedule. Letters, digits and dashes only, at most 63 characters; upper case is lowered. Default aldi-au-price-monitor.

## `proxyConfiguration` (type: `object`):

Proxy used for the requests to ALDI. Default: Apify proxy, automatic datacenter group, the configuration validated for ALDI Australia, its traffic included in the price. If ALDI refuses it for a long time, choose the RESIDENTIAL group with country AU; your own proxy URLs are accepted too.

## `maxRequests` (type: `integer`):

Safety cap on the requests of one run, preparation, retries and product URLs included. Default 500: about 29,000 products at 60 per request, far above a usual run (the whole range takes about 70 requests).

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

Searches read at the same time, each on its own proxy session. Lower it to 1 if ALDI refuses many requests (the log says so). Default 2: the pace validated at build time.

## `delayMs` (type: `integer`):

Pause between two page requests of the same search, in milliseconds; 0 disables the pause. Default 400: ALDI served every page at this pace at build time.

## `debugStoreHtml` (type: `boolean`):

Debugging only: save every answer of ALDI (JSON, or the refusal page) in the run's key-value store. Uses storage and slows the run down a little. Default off: not needed to get the rows.

## Actor input object example

```json
{
  "searchQueries": [
    "milk"
  ],
  "categories": [],
  "startUrls": [],
  "specialBuys": false,
  "wholeRange": false,
  "brands": [],
  "sortBy": "relevance",
  "maxResults": 20,
  "markPromotions": true,
  "monitorPrices": false,
  "onlyChanges": false,
  "monitorStoreName": "aldi-au-price-monitor",
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "maxRequests": 500,
  "maxConcurrency": 2,
  "delayMs": 400,
  "debugStoreHtml": false
}
```

# Actor output Schema

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

All extracted products as dataset items, one flat row per product. Append ?format=csv or ?format=xlsx for spreadsheets. The Overview view is selected by default; Prices, Promotions, Basket and Changes views are also available.

## `runStats` (type: `string`):

Requests made (preparation included), refusals of ALDI's CDN and retries on a new proxy session, bytes transferred, products seen, duplicates skipped and products pushed, promotion labels, Special Buys themes, price-monitoring summary, the store read and the estimated platform cost of the run.

# 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 = {
    "searchQueries": [
        "milk"
    ],
    "maxResults": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("myth84/aldi-australia-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 = {
    "searchQueries": ["milk"],
    "maxResults": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("myth84/aldi-australia-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 '{
  "searchQueries": [
    "milk"
  ],
  "maxResults": 20
}' |
apify call myth84/aldi-australia-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,myth84/aldi-australia-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/0ekI6T7BtBqfCP0Fq/builds/YOwUFV5VmYXDeMoBT/openapi.json
