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"
}
A few fields need a word:
-
unitPriceis the shop's own figure, as the shelf shows it.aldi.nlgives one on about three quarters of products,aldi-nord.degave one on every record I have seen, andaldi-sued.deon every record I have seen except loose fruit and vegetables. Where a shop shows none, the field is null. The Actor never calculates one. -
depositis the bottle deposit (Pfand) in euros, as the shop shows it, for example0.25. It is filled onaldi-nord.deandaldi-sued.dewhere the shop shows one, and is null otherwise. Onaldi.nlit is always null.pricestays the shelf price as the site gives it. -
wasPriceandpromotionTextare filled while a reduction is shown (for example-10%). Onaldi-nord.dea reduced price has not yet been seen in a real capture. Onaldi-sued.deone was seen in a capture of 28 Oct 2026; that shop gives no end date for a reduction, sopromotionEndsAtis null there. -
eanis always empty, because Aldi shows no barcode on these sites.matchKeyis a name and pack-size key you can join on. -
categoryPathis in Dutch or German, as each shop names it. -
Not returned: images, product descriptions, ingredients, nutrition, reviews and ratings.
imageUrlis always null, andinStockis 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"))
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. -
priceis the shelf price as the site gives it, anddepositis 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.deoffer 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)