DEV Community

Russell Yapp
Russell Yapp

Posted on

Aldi Netherlands, Aldi Nord and Aldi Süd shelf prices in EUR

I have published an Apify Actor that reads shelf prices from three Aldi shops: Aldi Netherlands (aldi.nl), Aldi Nord Germany (aldi-nord.de) and Aldi Süd Germany (aldi-sued.de). All three return the same row shape, in euros. This post covers what a row holds, the three ways to run it, what it costs and what it does not do.

The Actor is Aldi Netherlands & Germany Price Scraper: Shelf Prices (EUR) on the Apify Store. It is an independent tool, not affiliated with, endorsed by or sponsored by ALDI Nederland, ALDI Nord, ALDI SÜD or any ALDI company.

Both German Aldi chains

Germany has two Aldi chains, with their own shops and prices. This Actor reads both: Aldi Nord (aldi-nord.de, retailer aldi_nord_de) and Aldi Süd (aldi-sued.de, retailer aldi_sued_de). They are different sites, and each row names the shop it came from.

What a row holds

Each row is one product. Here is a sample from aldi.nl (from a capture of 27 Oct 2026):

{
  "retailer": "aldi_nl",
  "retailerProductId": "1235974",
  "ean": [],
  "name": "Romige kwark",
  "brand": "MILSANI",
  "packSize": "500g",
  "price": 1.89,
  "wasPrice": null,
  "unitPrice": { "value": 3.78, "measure": "kg" },
  "deposit": null,
  "promotionText": null,
  "categoryPath": ["Zuivel, eieren en boter", "Verse zuivel"],
  "url": "https://www.aldi.nl/product/romige-kwark-1235974.html",
  "matchKey": "milsani romige kwark 500g",
  "currency": "EUR",
  "market": "nl"
}
Enter fullscreen mode Exit fullscreen mode

A few fields need a word:

  • unitPrice is the shop's own figure, as the shelf shows it. aldi.nl gives one on about three quarters of products, aldi-nord.de gave one on every record I have seen, and aldi-sued.de on every record I have seen except loose fruit and vegetables. Where a shop shows none, the field is null. The Actor never calculates one.
  • deposit is the bottle deposit (Pfand) in euros, as the shop shows it, for example 0.25. It is filled on aldi-nord.de and aldi-sued.de where the shop shows one, and is null otherwise. On aldi.nl it is always null. price stays the shelf price as the site gives it.
  • wasPrice and promotionText are filled while a reduction is shown (for example -10%). On aldi-nord.de a reduced price has not yet been seen in a real capture. On aldi-sued.de one was seen in a capture of 28 Oct 2026; that shop gives no end date for a reduction, so promotionEndsAt is null there.
  • ean is always empty, because Aldi shows no barcode on these sites. matchKey is a name and pack-size key you can join on.
  • categoryPath is in Dutch or German, as each shop names it.
  • Not returned: images, product descriptions, ingredients, nutrition, reviews and ratings. imageUrl is always null, and inStock is null because stock is per store and not shown online.

Three modes

Mode Input What it reads
category (default) categoryUrls Every product on a category listing page
product productUrls Named product pages
catalogue retailers, maxItems The listing pages a shop's sitemap names (about 135 for aldi.nl, 120 for aldi-nord.de); for aldi-sued.de, the 49 top-level category pages

All three shops work in all three modes. In category mode, aldi.nl and aldi-nord.de need a subcategory page, because their top-level category pages carry no product list and are refused with a message. On aldi-sued.de a top-level category page lists products too, and a listing is read page by page (30 products a page) until maxItems or the end of the listing. In catalogue mode retailers defaults to ["aldi_nl"]; use aldi_nord_de and aldi_sued_de for Germany.

There is no search mode. Category and product modes take the shop from each page address, so you can mix the shops in one run. maxItems caps the rows saved (default 60).

The default input reads one small aldi.nl page (eggs, about 4 products) and finishes in under a minute.

Running it from Python

With apify-client and an Apify token:

import os
from decimal import Decimal

from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("yappman/nl-de-supermarket-price-scraper").call(
    run_input={
        "mode": "category",
        "categoryUrls": [
            "https://www.aldi.nl/producten/zuivel-eieren-boter/verse-zuivel.html",
            "https://www.aldi-nord.de/sortiment/milchprodukte/milch-milchgetraenke.html",
            "https://www.aldi-sued.de/produkte/kaese/frischkaese/k/1588161425467086",
        ],
        "maxItems": 30,
    },
    max_total_charge_usd=Decimal("0.055"),
)
for row in client.dataset(run.default_dataset_id).iterate_items():
    unit = row["unitPrice"] or {}
    print(row["retailer"], row["name"], row["packSize"], row["price"], unit.get("value"), unit.get("measure"))
Enter fullscreen mode Exit fullscreen mode

That run returns at most 30 rows in total. max_total_charge_usd stops the run if its charges would pass the cap.

Each run also produces a run summary, which counts the records skipped for having no current price.

What it costs

The Actor is pay per event. There is a start fee of $0.005 per run, plus one product event for each row returned, and no other event. On the Apify Free plan a product costs $0.0015, which is $1.50 per 1,000. On higher plans it costs less, down to $0.90 per 1,000.

Worked examples on the Free plan:

  • the default run (about 4 rows) costs $0.005 + 4 x $0.0015 = $0.011;
  • the three-shop run above costs at most $0.005 + 30 x $0.0015 = $0.05;
  • 1,000 rows cost $1.505.

A run that returns nothing costs only the start fee. The Store page has the current prices.

How it reads the shops

At the start of every run the Actor requests each selected shop's robots.txt. Requests go out one at a time, about one every two to three seconds, from the Apify platform's own network. If a shop answers 403 or 429, the run stops for that shop at once, with no retry. A server error is retried a few times, then stops that shop too, and so does any other answer that is not a normal page. You pay only for the rows already returned.

Limits worth knowing

  • Prices are those shown to a visitor with no store chosen, and they are current at the row's scrapedAt.
  • price is the shelf price as the site gives it, and deposit is returned separately. The Actor makes no claim about whether a shelf price includes the deposit.
  • Records without a current price, or not on sale yet, are skipped and counted in the run summary. That covers a promotion that has not started and an aldi-sued.de offer dated later than the run.
  • Aldi Nord and Aldi Süd are separate shops with their own prices, so the same product can differ between them.

The Store page has the full field list, the input reference and the changelog: Aldi Netherlands & Germany Price Scraper: Shelf Prices (EUR).

Top comments (0)