German Supermarket Offers — Aldi, Penny, Edeka, Lidl, REWE avatar

German Supermarket Offers — Aldi, Penny, Edeka, Lidl, REWE

Pricing

from $2.00 / 1,000 offers

Go to Apify Store
German Supermarket Offers — Aldi, Penny, Edeka, Lidl, REWE

German Supermarket Offers — Aldi, Penny, Edeka, Lidl, REWE

Weekly offers and prices from 6 German supermarket chains (Aldi Süd, Aldi Nord, Penny, Edeka, Lidl, REWE) in one unified schema: price, old price, discount, unit, validity dates, image. Keyword filter, per-chain limits. Pay per offer.

Pricing

from $2.00 / 1,000 offers

Rating

0.0

(0)

Developer

Nerdrx

Nerdrx

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 days ago

Last modified

Categories

Share

German Supermarket Offers

Unofficial weekly-offer scraper for six German supermarket chains. Not affiliated with, endorsed by, or officially connected to ALDI SÜD, ALDI Nord, PENNY, EDEKA, Lidl, REWE, Kaufland, or Netto Marken-Discount. It reads each chain's own public offers page or the JSON endpoint that page itself calls — the same data anyone sees by opening the chain's "Angebote" page in a browser.

What is German Supermarket Offers?

One Apify actor run that collects this week's advertised offers from ALDI SÜD, ALDI Nord, PENNY, EDEKA, Lidl and REWE and hands them back as a single flat dataset with one row per offer — instead of you writing and maintaining six separate scrapers against six completely different stacks (a REST API, a Next.js payload, a Magnolia REST fragment, a legacy offers service, a leaflet API, and server-rendered HTML).

What can German Supermarket Offers do?

  • Pull the current week's offers from all six chains (or any subset) in one run.
  • Give every offer the same shape: price, previous price, discount percent, pack size, base price, validity window, category, image.
  • Reconstruct what a chain leaves implicit — e.g. EDEKA publishes a discount but no previous price, so the previous price is computed; ALDI SÜD publishes a previous price but the discount only sometimes.
  • Filter by keywords: OR-matched, case-insensitive, and umlaut/ß-insensitivekaese matches Käse and vice versa, weissbier matches Weißbier.
  • Target a specific store for the chains whose prices are regional (PENNY, EDEKA, ALDI SÜD).
  • Keep going when one chain breaks: a failing chain is logged loudly as an error and the run continues with the rest. Individual requests retry twice with backoff and randomized realistic browser headers.
  • Interleave the chains round-robin, so a run capped by maxItems still contains offers from every chain rather than filling up with whichever chain ran first.

What data does it extract?

FieldTypeDescription
idstringStable, chain-scoped offer id, e.g. edeka:500000377.
chainstringChain slug: aldi-sued, aldi-nord, penny, edeka, lidl, rewe.
chainNamestringHuman-readable chain name, e.g. ALDI SÜD.
titlestringProduct name as advertised.
brandstring | nullBrand — only where the chain publishes it separately from the title.
descriptionstring | nullAdvertising sub-line: origin, variety, pack details.
pricenumber | nullOffer price in EUR.
priceOldnumber | nullReference/previous price in EUR, where published or derivable.
discountPercentinteger | nullDiscount in percent, as stated by the chain or derived from price/priceOld.
unitstring | nullPack size, e.g. 500 g, 0,75 l, Stück.
pricePerUnitstring | nullBase price exactly as printed, e.g. 1 kg = 3,18 €.
validFromstring | nullFirst day of the promotion (ISO date).
validTostring | nullLast day of the promotion (ISO date).
categorystring | nullCategory the chain files the offer under.
imageUrlstring | nullProduct image URL.
storeIdstring | nullThe store these prices belong to, for the store-specific chains.
sourceUrlstringA page a human can open to see this offer.
scrapedAtstringISO-8601 timestamp this item was scraped.

Chains and sources

ChainSlugSource typeEndpoint / pageStore id needed?robots.txt
ALDI SÜDaldi-suedJSON APIapi.aldi-sued.de/v3/product-search (category Wochenangebote)Optional (servicePoint)Allowed — aldi-sued.de/robots.txt only disallows /tools and /*?q=
ALDI Nordaldi-nordEmbedded JSON in HTMLaldi-nord.de/angebote.html__NEXT_DATA__apiDataOFFER_GETNo (national)Allowed — /angebote.html is not in the disallow list
PENNYpennyHTML + JSON APIpenny.de/angebote (week + categories) → /.rest/offers/by-category/{week}/{category}?region=…, markets from /.rest/marketYes (wwIdent; a default market is picked otherwise)Allowed — penny.de/robots.txt is Allow: /
EDEKAedekaJSON APIedeka.de/eh/service/eh/offers?limit=…&marketId=…Optional (marketId; 1 = national list)Allowed — /eh/ is not disallowed
LidllidlHTML + JSON APIlidl.de/c/online-prospekte/s10005610endpoints.leaflets.schwarz/v4/flyer?flyer_identifier=…No (national leaflet, region 0)Allowed — none of Lidl's disallow rules cover /c/ or /l/prospekte/
REWEreweServer-rendered HTMLrewe.de/angebote/nationale-angebote/<category>/No (national offers only)Explicitly allowedrewe.de/robots.txt lists every /angebote/nationale-angebote/… path under Allow: while disallowing /restservices/

Chains that are deliberately not included

ChainWhy it was dropped
Kauflandkaufland.de/angebote/aktuelle-woche.html serves a Cloudflare "Verifizierung erforderlich" bot challenge instead of the page. Getting past it would mean defeating bot detection, which this actor does not do.
Netto Marken-DiscountOffers are only rendered after a store is chosen in a session, the site runs Akamai Bot Manager, and netto-online.de/robots.txt disallows /INTERSHOP/ (its data endpoints) except for a short whitelist that does not include offers.

The actor's title mentions Netto and Kaufland because that is the market it addresses; the dataset does not contain them. See NOTES.md.

These are the chains' own public pages and the endpoints those pages call, not a paid data license. Endpoint shapes, selectors and category lists can change without notice — see NOTES.md for exactly what was live-verified and when.

How to use

  1. (Optional) Pick which chains to run — all six by default.
  2. (Optional) Add keywords, e.g. ["Käse", "Kaffee", "Bio"]. Leave empty to get every offer.
  3. (Optional) Set storeIds for the regional chains, e.g. {"penny": "4030085", "edeka": "10000327"}.
  4. (Optional) Adjust maxItems (total) and maxPerChain.
  5. Run it. Results land in the run's dataset; the Overview view shows chain, product, price, discount and validity as a table.

Scheduling once a week — early on the day the new week starts (Monday for most chains) — gives you a clean weekly price history. Enable proxyConfiguration if you run it often.

How much does it cost?

This actor is pay per event:

EventWhen it is chargedPlanned price
offerOnce per offer successfully pushed to the dataset.$0.002

A default run capped at maxItems: 500 therefore costs at most $1.00 plus Apify platform compute (the actor is HTTP-only and runs comfortably in 256–512 MB, so compute is a few cents). Nothing is charged for offers that are filtered out by your keywords, for chains that fail, or for the store/category lookup requests.

If you set a maximum charge on the run and it is reached, the actor stops pushing further offers, logs a warning, and exits cleanly — you are never charged for an item that did not make it into the dataset.

Input

FieldTypeDefaultDescription
chainsarray of enumall sixWhich chains to scrape.
keywordsarray of string[]Keep only offers matching at least one keyword (umlaut-insensitive substring match on title, brand, description, category). Empty = keep everything.
storeIdsobject{}Per-chain store id, e.g. {"penny": "4030085"}. PENNY takes a wwIdent, EDEKA a market id, ALDI SÜD a servicePoint. ALDI Nord, Lidl and REWE publish national offers and ignore it.
maxItemsinteger500Maximum offers written to the dataset across all chains.
maxPerChaininteger200Maximum offers taken from each chain before keyword filtering.
proxyConfigurationobjectnoneOptional proxy for all outbound requests.
{
"chains": ["aldi-sued", "penny", "edeka", "rewe"],
"keywords": ["Käse", "Kaffee"],
"storeIds": { "penny": "4030085" },
"maxItems": 300,
"maxPerChain": 150
}

Output (sample)

{
"id": "aldi-sued:000000000000230773",
"chain": "aldi-sued",
"chainName": "ALDI SÜD",
"title": "Mandarinen 750 g",
"brand": "NATUR LIEBLINGE",
"description": null,
"price": 1.49,
"priceOld": 1.79,
"discountPercent": 16,
"unit": "0,75 kg",
"pricePerUnit": "1,99 €/1 kg",
"validFrom": "2026-08-31",
"validTo": "2026-09-05",
"category": "Frischeprodukte im Angebot",
"imageUrl": "https://dm.emea.cms.aldi.cx/is/image/aldiprodeu/product/jpg/scaleWidth/600/706b9aa7-f1fb-484e-b14a-898a64e945f8",
"storeId": null,
"sourceUrl": "https://www.aldi-sued.de/produkte/wochenangebote/k/1588161426582123",
"scrapedAt": "2026-09-02T10:00:00.000Z"
}

FAQ & support

Is this official? No. This is an unofficial scraper. It is not affiliated with or endorsed by any of the chains named here. Use the data for personal price comparison and research; check each chain's terms before redistributing it.

Why aren't Kaufland and Netto in the output? Kaufland answers with a Cloudflare bot challenge and Netto sits behind Akamai Bot Manager with its data endpoints disallowed in robots.txt. Rather than work around either, they are excluded. See the table above and NOTES.md.

Why do some offers have no priceOld or discountPercent? Because the chain does not publish one. Lidl's leaflet feed and REWE's national offer tiles state only the offer price; a "Knaller" or "Aktion" badge is not a reference price, so inventing one would be misleading. EDEKA publishes the discount without the old price, so priceOld there is derived from price and the stated discount — treat it as a reconstruction, not as a figure EDEKA printed.

Are the prices what I will pay in my local store? Only approximately. ALDI Nord, Lidl and REWE (national) offers are country-wide. PENNY, EDEKA and ALDI SÜD prices vary by region or store — pass a storeId for those to get the list for a specific store. App-only prices (EDEKA App, Lidl Plus, PENNY Coupons) are not the shelf price and are not separated out in this dataset.

How accurate are validFrom / validTo? ALDI Nord, EDEKA and Lidl publish machine-readable dates and those are passed through. PENNY's dates are derived from its week id plus the promotion period ("ab Montag" = Mo–Sa, "ab Donnerstag" = Do–Sa). ALDI SÜD publishes no dates on its API at all, so the current Monday–Saturday week is used. REWE's come from the "Diese Woche" tab text. NOTES.md lists which is which.

REWE returned fewer categories than usual. REWE rate-limits by IP and can answer 403 for some categories when the run comes from a datacenter address. Each request already retries twice with backoff and browser-realistic headers; if categories are still blocked the run logs one warning naming them and returns the rest. Enabling proxyConfiguration with residential proxies is the reliable fix.

Why does EDEKA return only ~20 offers? Because that is the whole national list. EDEKA's service reports the total result-set size in anzahl, and it reads 20 regardless of the limit you send — there is no paging to exhaust. Pass a real storeIds.edeka market id (from EDEKA's market search) to get that market's larger list.

Something broke / a chain returns nothing. Chain sources change. Open an issue with the run id and the log line for the failing chain. Individual chain failures never abort the run — you still get the other chains' data.