# REWE.de Scraper - Prices, Offers & Postal-Code Matching (`abotapi/rewe-de-scraper`) Actor

Scrape REWE Germany (rewe.de) grocery products: current price, was-price and discount when genuinely on offer, brand, category, price per unit, images and tags. Prices match a real postal code you choose. Search keywords or paste product/listing links.

- **URL**: https://apify.com/abotapi/rewe-de-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** E-commerce, Developer tools, Automation
- **Features:** Limited permissions
- **Stats:** 16 total users, 4 monthly users, 94.4% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

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

## REWE Scraper: German Grocery Prices, Offers and Product Details by Postal Code

REWE Scraper pulls product data from REWE Germany's own grocery catalog (rewe.de), matched to the real market for a German postal code you choose, the same way the site itself scopes prices, offers and assortment to your address. Search by keyword and/or REWE's own category, brand and dietary/sourcing filters, or paste product and listing links directly. Was-price and discount are only ever reported when REWE's own data genuinely carries a reduced price, never guessed. Export to JSON, CSV or Excel, or pull results straight into your app through the API.

### Why This Scraper?

- **Postal-code matched pricing and assortment.** A German postal code (PLZ) resolves your local REWE market before anything else runs. It changes which products even show up, not just their prices, the same way the site itself works.
- **Was-price and discount, gated on the site's own signal.** When a product genuinely carries a reduced price, the original price plus the discount amount and percentage are captured. A full-price product simply carries `originalPrice: null`, nothing is fabricated.
- **Real site filters.** Brand and REWE's own dietary/sourcing quick filter (Bio, Vegan, Regional, New, Gekühlt, Tiefgefroren) apply in search mode and to a pasted listing link, because a pasted listing link is rebuilt through the same filters, not just replayed; category applies in search mode only, since a pasted link already carries its own category.
- **Two ways in.** Keyword and/or category search, or paste any product, category, search or listing link. Category and brand accept plain names.
- **Optional detail enrichment.** Turn on Fetch product detail to add the description and ingredient statement from the product page, on top of the price, brand, category and images already included by default.
- **Optional export to your apps.** Send results into Notion, Linear, Airtable or any Apify MCP connector alongside the dataset.
- **Resume and recurring updates.** Continue one interrupted crawl with a run or dataset id, or turn on Incremental mode to track the same search over time and get only what changed.

### Use Cases

- **Grocery price monitoring:** track price and promotion changes for specific products or categories in one or more postal-code markets over time.
- **Competitive and market intelligence:** compare REWE's assortment, pricing and current offers against other supermarkets for a given region.
- **Market research and analytics:** build a structured dataset of product names, brands, categories and pricing for a German market.
- **Brand and MAP monitoring:** brands and agencies checking how their products are listed and priced on REWE in specific markets.
- **Grocery comparison and content sites:** aggregate real product details and current specials into a comparison tool or feed.

### Data You Get

| Field | Example |
| --- | --- |
| `productId` | `"677067"` |
| `name` | `"Sample Fresh Whole Milk 3.5% 1l"` |
| `brand` | `"Sample Dairy"` |
| `category` / `categoryPath` | `"Frischmilch"` / `["Kühlregal", "Milchprodukte", "Frischmilch"]` |
| `categoryId` | `"175"` |
| `tags` | `["Gekühlt", "Regional"]` |
| `url` | `"https://www.rewe.de/shop/p/sample-fresh-whole-milk-3-5-1l/677067"` |
| `hasVariants` | `false` |
| `freeShipping` | `false` |
| `price` / `currency` | `1.29` / `"EUR"` |
| `originalPrice` / `discountAmount` / `discountPercent` | `1.59` / `0.3` / `19` (all `null` off-special) |
| `isOnSpecial` | `true` |
| `pricePerUnit` / `grammage` | `"1.29 EUR / l"` / `"1l"` |
| `available` | `true` |
| `offerValidTo` | `"2026-10-05T23:59:59Z"` (on-special only) |
| `merchant` | `"REWE"` |
| `gtin` | `"4008400205746"` |
| `image` / `images` | `"https://img.rewe-static.de/sample/digital-image.png"` / `[...]` |
| `description` / `ingredients` | only when **Fetch product detail** is on |

> Sample shape: values above are illustrative placeholders, not from a live product.

When **Incremental mode** is on, every returned record also carries `changeType`, `changedFields`, `firstSeenAt` and `lastSeenAt`. See "Resume and recurring updates" below for exactly what those mean.

### How to Use

1. Pick a **mode**: `search` (keyword and/or category, plus REWE's own brand and dietary/sourcing filters) or `url` (paste product or listing links).
2. Set your **postal code (PLZ)** so prices, offers and the product assortment match the REWE market for that address.
3. Fill in the fields for your mode, and turn on **Fetch product detail** if you also want description and ingredients.
4. Set **Max products** and **Max pages** to control run size and cost, then click **Start**.

**Keyword search, first page:**

```json
{
  "mode": "search",
  "searchTerm": "milch",
  "zipCode": "50667",
  "maxItems": 20
}
```

**Category browse, filtered to a brand and a dietary tag:**

```json
{
  "mode": "search",
  "categorySlug": "milch",
  "brand": "Alpro",
  "attribute": "VEGAN",
  "zipCode": "50667",
  "maxItems": 50,
  "maxPages": 5
}
```

**URL mode shapes.** Supported links: a product page (`/p/<slug>/<id>`), a category page (`/shop/c/<slug>/`), a search page (`/suche/produkte?search=<term>`), and listing links with `search=` or `categorySlug=`, optionally with `page`, `objectsPerPage` (1-100), `brand`, `attribute` and `sorting` (`RELEVANCE_DESC`, `TOPSELLER_DESC`, `PRICE_ASC`, `PRICE_DESC`, `NAME_ASC`). Any other link is skipped with a warning, and a link that yields no products is reported in the log. If you leave the URLs field untouched, URL mode scrapes one example of each of these shapes, so clear the list before running it with your own links.

**Paste product or listing links, with detail enrichment on:**

```json
{
  "mode": "url",
  "urls": ["https://www.rewe.de/shop/p/weihenstephan-h-milch-3-5-1l/677067"],
  "zipCode": "50667",
  "fetchDetails": true
}
```

**Recurring search, tracked with Incremental mode:**

```json
{
  "mode": "search",
  "searchTerm": "milch",
  "zipCode": "50667",
  "incrementalMode": true,
  "stateKey": "cologne-milk-watch"
}
```

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/rewe-de-scraper").call(run_input={
    "mode": "search",
    "searchTerm": "milch",
    "zipCode": "50667",
    "maxItems": 20,
})
for product in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(product["name"], product["price"], product["currency"])
```

JavaScript:

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/rewe-de-scraper').call({
  mode: 'search',
  searchTerm: 'milch',
  zipCode: '50667',
  maxItems: 20,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the **Integrations** tab.

#### How price filtering is bounded

REWE offers no price filter of its own, so `minPrice`/`maxPrice` narrow what this actor returns to you, after a page has already been fetched, rather than what REWE hands back. To stop a very selective band from paging through an entire category or search looking for survivors, a search stops after **5 consecutive result pages that contained new (not-yet-seen) products but none of them matched the price band**. A stop like this is reported in the run log, and it means the search was not read to the end: more matches may exist further down. Narrow the search or category, or widen the price band, to see more. Pages with nothing new on them at all, for example when resuming a run whose early pages are already collected, never count toward this limit.

#### Resume and recurring updates

- **Resume** (`resumeFromRunId`) continues one specific interrupted crawl: paste a run id or dataset id and this run skips products already collected there, returning only the remaining new products.
- **Incremental mode** (`incrementalMode`) is for running the same search again and again (daily, weekly) and getting only the changes. The actor remembers the previous run of the *same* search by itself, so you never paste a run id. The first run returns everything as `NEW`. By default (with `emitExpired` off), later runs return only `NEW` and `UPDATED` products; `UNCHANGED` products are suppressed and not billed unless `emitUnchanged` is on. `REAPPEARED` and `EXPIRED` are a pair: `EXPIRED` tombstones are only created when `emitExpired` is on for a run that fully scans the tracked search (not capped by Max products/Max pages, not stopped early by a connection problem, and never on a Resume run). A product only classifies as `REAPPEARED` once it had previously been tombstoned that way and is then seen again, so if `emitExpired` has never been turned on for a given search, `REAPPEARED` will never appear for it either. State is isolated automatically per mode/postal code/search term/category/brand/attribute/price filters/URLs/detail setting, and, once set, per extra keywords, offers-only, available-only, market id and shopping-list product ids (postal code is included because it selects a different REWE market); set `stateKey` to name or deliberately share a monitoring campaign.

In **Incremental mode**, a product is `UPDATED` when its price, was-price, unit price, availability, name, brand, tags or category path change (named in `changedFields`). Image links and the `scrapedAt` timestamp never count as a change, nor do fields you list in `ignoreFieldsForChanges`; they stay in the output. `EXPIRED` is never produced from a partial scope: a pasted listing URL starting past page 1, or a walk stopped by Max products or Max pages. `EXPIRED` rows count against Max products.

In **Incremental mode**, if **Fetch product detail** is on and a detail-page fetch fails on a later run, the previously fetched `description`/`ingredients` are carried forward from state rather than dropped, so a transient fetch failure never looks like real content disappearing, and never re-bills the detail-enrichment surcharge for it.

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

Optionally pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step **after** the scrape: the Apify dataset is never changed.

**What gets written to the connector:** a condensed, human-readable **summary** of each product, not the full JSON. Each item becomes one entry with a **title** and its key fields flattened to plain text. The **complete record always stays in the Apify dataset**.

1. Authorize a connector once under **Apify, Settings, Integrations** (Notion, Linear, Airtable, or Apify).
2. Select it in the **"Pipe results into your apps"** input field. If the picker is empty, you have not authorized a connector yet.
3. For **Notion**, also set `notionParentPageUrl` to the page where items should be created.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.

### Input Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `mode` | string | `search` | `search` (keyword/category/filters) or `url` (paste product, category, search or listing links). |
| `zipCode` | string | `50667` | German postal code REWE uses to match your local market. Required. Scopes prices, offers and which products the market stocks. |
| `searchTerm` | string | none (prefill: `milch`) | Free-text keyword. Search mode only; leave empty to browse a category instead. |
| `categorySlug` | string | none | Category name or REWE's own token, e.g. `Milch` or `milch`; a name matching nothing stops the run with suggestions. Search mode only; URL mode reads this from the pasted link. |
| `urls` | array | one example of each supported shape (also the prefill) | Product or listing URLs to scrape. URL mode only. Leaving the field untouched in URL mode scrapes these examples. A link's own `brand=` or `attribute=` filter is honoured and wins over the Filters section. |
| `brand` | string | none | Exact brand name to keep, matched as REWE's own brand filter shows it. Applies in search mode and to pasted listing links in URL mode (a link's own `brand=` wins); a single pasted product link is not filtered by it. |
| `attribute` | string | none (`All products`) | `ORGANIC`, `VEGAN`, `REGIONAL`, `NEW`, `CHILLED`, `FROZEN` or `HASLOYALTYBONUS`, REWE's own single-select dietary/sourcing quick filter. Applies in search mode and to pasted listing links in URL mode; a single pasted product link is not filtered by it. |
| `minPrice` | number | none | Minimum EUR price to keep. REWE offers no price filter of its own, so this narrows the actor's own results; see "How price filtering is bounded" above. Applies in both search mode and URL mode. |
| `maxPrice` | number | none | Maximum EUR price to keep. REWE offers no price filter of its own, so this narrows the actor's own results; see "How price filtering is bounded" above. Applies in both search mode and URL mode. |
| `fetchDetails` | boolean | `false` | Adds description and ingredient statement from the product detail page. Price, brand, category and images are already included without this. |
| `sortBy` | string | `RELEVANCE_DESC` | `RELEVANCE_DESC`, `TOPSELLER_DESC`, `PRICE_ASC`, `PRICE_DESC` or `NAME_ASC`, sorted by REWE itself. |
| `campaignsOnly` | boolean | `false` | Keeps only products currently on offer. REWE has no server-side filter for this, so the run reads the normal result pages and drops the rest (bounded by Max pages and the empty-page guard). |
| `inStockOnly` | boolean | `false` | Keeps only products orderable at your market. REWE has no server-side filter for this, so the run reads the normal result pages and drops the rest (bounded by Max pages and the empty-page guard). |
| `marketId` | string | none | Exact REWE market id; overrides the postal code. |
| `searchTerms` | array | none | Extra keywords searched one after another, in addition to the Search keyword. |
| `productIds` | array | none | Shopping list: price exact product ids; rows get `quantity` and `lineTotal` (price x quantity). |
| `quantities` | array | none | Quantity for each id in `productIds`, in the same order. |
| `compact` | boolean | `false` | Core fields only. Description and ingredients are not part of the compact set, so with Fetch product detail on they are dropped and the detail-enrichment event is not charged for those rows. |
| `excludeEmptyFields` | boolean | `false` | Drop empty fields from each row. |
| `descriptionMaxLength` | integer | `0` | Truncate the description (Fetch product detail) to this length; 0 = no truncation. |
| `maxPages` | integer | none (built-in ceiling of 500 pages) | Cap on result pages per search/URL entry. Leave empty for the built-in ceiling of 500 pages per entry; the run also stops at Max products and the guards described above. |
| `maxItems` | integer | `20` | Hard cap on total products returned across every search/URL entry. `0` = unlimited (still bounded by Max pages). Values above 1,000,000 are clamped to 1,000,000. |
| `resumeFromRunId` | string | none | ID of a previous run or dataset of this actor. Already-collected products are skipped, so this run returns only the remaining delta. |
| `incrementalMode` | boolean | `false` | Recurring monitoring of the same search. See "Resume and recurring updates" above for exactly what is returned by default. |
| `stateKey` | string | none | Name for an incremental-mode monitoring campaign. Leave empty to derive one automatically from mode/postal code/search/filter settings. |
| `emitUnchanged` | boolean | `false` | Incremental mode only. Also return (and bill) products unchanged since the last run. |
| `emitExpired` | boolean | `false` | Incremental mode only. Also return (and bill) products no longer found, once a run fully scans the tracked search. |
| `ignoreFieldsForChanges` | array | none | Incremental mode only. Field names whose changes never make a product `UPDATED`. Image links and `scrapedAt` are always ignored. |
| `proxy` | object | automatic pool | Connection settings. The default uses the automatic proxy pool; residential access is used as a backup when your plan includes it. |
| `mcpConnectors` | array | none | Optional MCP connectors to export results into (Notion, Linear, Airtable, Apify). |
| `notionParentPageUrl` | string | none | Notion connector only: page under which item pages are created. |
| `maxNotifyListings` | integer | `50` | Cap on items exported to each connector per run. Does not affect the dataset. |

### Output Example

```json
{
  "productId": "677067",
  "name": "Sample Fresh Whole Milk 3.5% 1l",
  "brand": "Sample Dairy",
  "tags": ["Gekühlt", "Regional"],
  "url": "https://www.rewe.de/shop/p/sample-fresh-whole-milk-3-5-1l/677067",
  "hasVariants": false,
  "freeShipping": false,
  "categoryId": "175",
  "category": "Frischmilch",
  "categoryPath": ["Kühlregal", "Milchprodukte", "Frischmilch"],
  "image": "https://img.rewe-static.de/sample/digital-image.png",
  "images": ["https://img.rewe-static.de/sample/digital-image.png"],
  "price": 1.29,
  "currency": "EUR",
  "originalPrice": 1.59,
  "discountAmount": 0.3,
  "discountPercent": 19,
  "isOnSpecial": true,
  "pricePerUnit": "1.29 EUR / l",
  "grammage": "1l",
  "available": true,
  "offerValidTo": "2026-10-05T23:59:59Z",
  "merchant": "REWE",
  "gtin": "4008400205746"
}
```

> Sample shape: values above are illustrative placeholders, not from a live product.

#### Output views

The dataset has four views: **Products** (overview), **Product details** (description and ingredients, with Fetch product detail), **Changes (incremental)** (`changeType`, `changedFields`, first and last seen) and **Shopping list** (`quantity` and `lineTotal` when `productIds` is used). The run also stores a summary under the `OUTPUT` key.

### Plan Requirement

The default connection is the automatic Apify proxy pool, measured to serve REWE's product listings (the JSON catalog that search, category and listing links read), so no residential plan is needed for a normal run. If that connection is refused for your account, the run tries Germany residential access where your plan includes it, then a backup connection. Then the log shows:

> The selected connection is unavailable to this run. Apify free plans do not include residential proxies. Continuing on a backup connection; some pages may still be unavailable.

and, once the backup serves pages: "Served over the backup connection (the default connection was unavailable)." If nothing can be read, the run fails with an explicit message instead of an empty dataset: "The proxy connection was refused for this account, so no page could be read" (a paid plan or your own proxy URLs are needed), "No results: residential proxies are unavailable on this run ... and the backup connection could not load the requested REWE pages", or "Could not load any results page (the requests failed) ... This is a connection problem, not an empty search." (only when result pages were actually requested; a run with no keyword/category or no recognised link ends with a plain input message instead) You can also supply your own proxy URLs. See https://apify.com/pricing.

Product detail pages (`fetchDetails`) are read from REWE's product page. Not every product has a description, and some have no ingredient statement. If product pages stop loading during a run, the run logs one warning and skips detail enrichment for the remaining products.

### FAQ

#### How much does it cost?

You pay a small fixed amount per run start, plus a charge per product returned (dataset item), plus an optional detail-enrichment surcharge only for products where **Fetch product detail** is on and the product's page delivered a description or an ingredient statement for a product that reached the dataset. Products where REWE's own page has neither are not charged. The detail-enrichment event is charged one unit per product, immediately after that product is written to the dataset, so an interrupted run is only billed for products already delivered. A row whose delivered output carries no description or ingredients (for example with Compact output on) is not charged the event. The **Pricing** tab shows current rates. Use **Max products** to cap the cost of any run.

#### Is it legal to scrape REWE?

This actor collects only publicly available product and pricing data. You are responsible for how you use it: follow REWE's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Product names, prices and descriptions are generally facts, but images and brand names may be subject to third-party rights.

#### Can I get only new or changed products on a schedule?

Yes. Schedule the actor from the **Schedules** tab and turn on **Incremental mode**. By default a scheduled run then returns only `NEW` and `UPDATED` products; unchanged ones are suppressed and not billed. `EXPIRED` rows appear only on a run that has `emitExpired` on and fully scans the search; `REAPPEARED` only appears on a later run, after such a run has already marked a product expired; see "Resume and recurring updates" above.

#### Why did my run return fewer products than I expected?

Two independent reasons: **Max products** or **Max pages** capped the run intentionally, or a tight price band ran into the 5-consecutive-barren-page rule described above (REWE has no price filter of its own, so this actor's own narrowing can stop early rather than paging the whole catalog). Either way, more matches may exist; raise the caps or widen the price band to see more.

#### Why did my run fail instead of returning an empty dataset?

The most common cause is a postal code with no matching REWE market: the site's own postal-code service is checked up front, before anything is scraped or billed, and the run stops with a clear message rather than returning a dataset full of null prices. A genuinely empty search (no products match your keyword, category or filters) still succeeds, it just returns nothing to charge for.

#### Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the connector field to push results into Notion, Linear or Airtable.

### 🔗 Want more grocery data?

Pair this actor with these related scrapers from the same team:

<table>
<tr><td>🥦 <a href="https://apify.com/abotapi/kaufland-de-scraper"><b>Kaufland.de Scraper</b></a><br>Scrape Kaufland.de, Germany's hypermarket and online marketplace: keyword/category/brand...</td><td>🥦 <a href="https://apify.com/abotapi/mercadona-es-scraper"><b>Mercadona Scraper</b></a><br>Scrape Mercadona products by delivery-area postal code. Search by keyword or category, or...</td></tr>
<tr><td>🥦 <a href="https://apify.com/abotapi/auchan-fr-scraper"><b>Auchan France Scraper</b></a><br>Scrape Auchan.fr products: grocery, drinks, household and general merchandise. Real price...</td><td>🥦 <a href="https://apify.com/abotapi/leclerc-fr-scraper"><b>E.Leclerc Scraper</b></a><br>Scrape E.Leclerc France (e.leclerc) grocery and retail products. Search by keyword or...</td></tr>
<tr><td>🥦 <a href="https://apify.com/abotapi/coles-au-scraper"><b>Coles AU Scraper</b></a><br>Scrape Coles.com.au grocery products and customer reviews. Search by keyword, browse...</td><td>🥦 <a href="https://apify.com/abotapi/resq-club-surplus-food-scraper"><b>ResQ Club Scraper</b></a><br>Scrape ResQ Club surplus food offers in Finland, Sweden and Estonia: name, price, current...</td></tr>
</table>

👉 [Browse all abotapi scrapers](https://apify.com/abotapi)

### 💬 Support

- 🐞 **Found a bug or a missing field?** Open a ticket on the [Issues tab](https://apify.com/abotapi/rewe-de-scraper/issues/open). We usually reply within hours.
- 🛠️ **Need another site, extra fields or a private build?** Email <contact@abotapi.com>.
- ⭐ **Enjoying it?** A quick review on the actor page helps other users find it.

# Actor input Schema

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

'search' finds products by keyword and/or REWE's own category, brand, and attribute filters. 'url' scrapes any product or listing page URL you paste.

## `zipCode` (type: `string`):

German postal code REWE uses to match your local market. Prices, offers and availability are scoped to it, in every mode - and it also decides which products the market stocks, so it changes the result set, not just the prices. Five digits, e.g. 50667 for Cologne. A code with no REWE market stops the run rather than returning products with no prices.

## `searchTerm` (type: `string`):

Free-text keyword, e.g. 'milch' or 'kaffee'. Leave empty to browse a whole category with the field below.

## `categorySlug` (type: `string`):

Category name or REWE's own token, e.g. 'Milch' or 'milch'. Names are matched against REWE's live category list; a name that matches nothing stops the run with a clear error and suggestions. Combine with a keyword to search within a category, or leave the keyword empty to browse the whole category. Search mode only: URL mode reads this from the link you paste.

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

Paste REWE product pages (.../p/<slug>/<id>), category pages (.../shop/c/<slug>/), search pages (.../suche/produkte?search=<term>) or listing links (a URL with `search=` or `categorySlug=`, optionally `page`, `objectsPerPage` (1-100), `brand`, `attribute` and `sorting` = RELEVANCE_DESC, TOPSELLER_DESC, PRICE_ASC, PRICE_DESC or NAME_ASC). Left untouched, URL mode scrapes one example of each shape. Search-mode fields are ignored here. The fields in the Filters section still apply; a link's own `brand=` or `attribute=` wins over them.

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

Only return products from this brand, e.g. 'Lindt' (matched against REWE's live brand list, case-insensitive; a brand that matches nothing stops the run with a clear error). Leave empty for all brands. Applies in BOTH search mode and URL mode: the filter is also sent with a pasted category or search link.

## `attribute` (type: `string`):

REWE's own quick filter -- narrows to products carrying that tag. Applies in BOTH search mode and URL mode: the listing request is rebuilt for a pasted category or search URL, so the filter is sent with it.

## `minPrice` (type: `number`):

Only return products priced at or above this amount, in euros. Applies in BOTH search mode and URL mode: the listing request is rebuilt for a pasted category or search URL, so the filter is sent with it.

## `maxPrice` (type: `number`):

Only return products priced at or below this amount, in euros. Applies in BOTH search mode and URL mode: the listing request is rebuilt for a pasted category or search URL, so the filter is sent with it.

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

Order of the results. Relevance is the default. Price and name sorts are done by REWE itself, so they stay correct across pages. In URL mode a pasted link's own sorting wins.

## `campaignsOnly` (type: `boolean`):

Only return products currently on offer. REWE has no server-side filter for this, so the run reads the normal result pages and drops the rest (bounded by Max pages and the empty-page guard); combine it with a keyword or category.

## `inStockOnly` (type: `boolean`):

Only return products that are orderable at your market. REWE has no server-side filter for this, so the run reads the normal result pages and drops the rest (bounded by Max pages and the empty-page guard).

## `marketId` (type: `string`):

Exact REWE market id. When set it overrides the postal code for prices and availability.

## `searchTerms` (type: `array`):

Optional extra keywords searched one after another, in addition to the Search keyword. Search mode only.

## `productIds` (type: `array`):

Optional. REWE product ids (the number at the end of a product link) to price directly. When set, search fields are ignored in search mode. Rows get quantity and lineTotal.

## `quantities` (type: `array`):

Quantities parallel to the product ids (1 each if omitted). Adds lineTotal = price x quantity.

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

Adds the product description and ingredient statement from the product page, when REWE's own page carries them. Billed once per product only when the page delivers a description or ingredient statement; products where REWE's page has neither are not charged. Current price, was-price/discount, brand, category and images are already included without this toggle.

## `compact` (type: `boolean`):

Only the core fields (id, name, brand, url, prices, availability).

## `excludeEmptyFields` (type: `boolean`):

Drop fields that are empty (null, empty text or empty list) from each row.

## `descriptionMaxLength` (type: `integer`):

Truncate the description (Fetch product detail) to this many characters. 0 = no truncation.

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

Stop after this many result pages per search/URL entry. Leave empty for the built-in ceiling of 500 pages per search/URL entry -- the run also stops at Max products total.

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

Hard cap on total products returned across every search/URL entry. 0 = unlimited (still bounded by Max pages per search). Values above 1,000,000 are clamped to 1,000,000.

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

The default uses Apify's automatic proxy pool, which this site serves reliably. If your plan includes residential proxies they are used automatically as a backup; you can also pick a group or supply your own proxy URLs.

## `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. For recurring monitoring of the same search, use Incremental mode below instead.

## `incrementalMode` (type: `boolean`):

Turn this on for daily or recurring monitoring of the same search/URLs. The first run returns all matching products as NEW. Later runs normally return only NEW, UPDATED and REAPPEARED products. A product counts as UPDATED when its price, was-price, unit price, availability, name, brand, tags or category path change (reported in changedFields); image links, the scrapedAt timestamp and anything listed under "Ignore fields" below never count as a change. Turn on "Emit unchanged" or "Emit expired" only when you also want those products returned (and billed). State is kept separately per distinct search/filter/postal-code setup (or by State key below).

## `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 mode/postal code/search/filter/detail 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/Max pages capped it, a connection problem stopped it early, or Resume was used. This returns -- and bills -- extra synthetic rows.

## `ignoreFieldsForChanges` (type: `array`):

Optional. Field names (for example tags) whose changes should NOT turn a product into UPDATED. Image links and scrapedAt are always ignored. Ignored fields stay in the output; they are only left out of change detection and changedFields.

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

Optionally send the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize a connector once under Apify -> Settings -> Integrations, then select it here. The connector receives a condensed, human-readable summary per item (title + key fields), not the full JSON; the complete record stays in the dataset. Leave empty to skip. 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",
  "zipCode": "50667",
  "searchTerm": "milch",
  "urls": [
    "https://www.rewe.de/shop/api/products?search=milch&page=1",
    "https://www.rewe.de/shop/api/products?search=milch&page=2&objectsPerPage=30",
    "https://www.rewe.de/shop/api/products?categorySlug=milch&page=2&objectsPerPage=5",
    "https://www.rewe.de/shop/api/products?categorySlug=milch&search=hafer&objectsPerPage=3",
    "https://www.rewe.de/shop/api/products?search=*&page=1&objectsPerPage=3&sorting=PRICE_ASC",
    "https://www.rewe.de/shop/api/products?search=milch&brand=Alnatura&attribute=organic&objectsPerPage=5",
    "https://www.rewe.de/shop/p/weihenstephan-h-milch-3-5-1l/677067",
    "https://www.rewe.de/shop/c/milch/",
    "https://www.rewe.de/suche/produkte?search=milch"
  ],
  "attribute": "",
  "sortBy": "RELEVANCE_DESC",
  "campaignsOnly": false,
  "inStockOnly": false,
  "fetchDetails": false,
  "compact": false,
  "excludeEmptyFields": false,
  "descriptionMaxLength": 0,
  "maxItems": 20,
  "proxy": {
    "useApifyProxy": true
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": [],
  "maxNotifyListings": 50
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `detail` (type: `string`):

No description

## `changes` (type: `string`):

No description

## `shoppingList` (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",
    "zipCode": "50667",
    "searchTerm": "milch",
    "urls": [
        "https://www.rewe.de/shop/api/products?search=milch&page=1",
        "https://www.rewe.de/shop/api/products?search=milch&page=2&objectsPerPage=30",
        "https://www.rewe.de/shop/api/products?categorySlug=milch&page=2&objectsPerPage=5",
        "https://www.rewe.de/shop/api/products?categorySlug=milch&search=hafer&objectsPerPage=3",
        "https://www.rewe.de/shop/api/products?search=*&page=1&objectsPerPage=3&sorting=PRICE_ASC",
        "https://www.rewe.de/shop/api/products?search=milch&brand=Alnatura&attribute=organic&objectsPerPage=5",
        "https://www.rewe.de/shop/p/weihenstephan-h-milch-3-5-1l/677067",
        "https://www.rewe.de/shop/c/milch/",
        "https://www.rewe.de/suche/produkte?search=milch"
    ],
    "proxy": {
        "useApifyProxy": true
    },
    "ignoreFieldsForChanges": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/rewe-de-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",
    "zipCode": "50667",
    "searchTerm": "milch",
    "urls": [
        "https://www.rewe.de/shop/api/products?search=milch&page=1",
        "https://www.rewe.de/shop/api/products?search=milch&page=2&objectsPerPage=30",
        "https://www.rewe.de/shop/api/products?categorySlug=milch&page=2&objectsPerPage=5",
        "https://www.rewe.de/shop/api/products?categorySlug=milch&search=hafer&objectsPerPage=3",
        "https://www.rewe.de/shop/api/products?search=*&page=1&objectsPerPage=3&sorting=PRICE_ASC",
        "https://www.rewe.de/shop/api/products?search=milch&brand=Alnatura&attribute=organic&objectsPerPage=5",
        "https://www.rewe.de/shop/p/weihenstephan-h-milch-3-5-1l/677067",
        "https://www.rewe.de/shop/c/milch/",
        "https://www.rewe.de/suche/produkte?search=milch",
    ],
    "proxy": { "useApifyProxy": True },
    "ignoreFieldsForChanges": [],
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/rewe-de-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 '{
  "mode": "search",
  "zipCode": "50667",
  "searchTerm": "milch",
  "urls": [
    "https://www.rewe.de/shop/api/products?search=milch&page=1",
    "https://www.rewe.de/shop/api/products?search=milch&page=2&objectsPerPage=30",
    "https://www.rewe.de/shop/api/products?categorySlug=milch&page=2&objectsPerPage=5",
    "https://www.rewe.de/shop/api/products?categorySlug=milch&search=hafer&objectsPerPage=3",
    "https://www.rewe.de/shop/api/products?search=*&page=1&objectsPerPage=3&sorting=PRICE_ASC",
    "https://www.rewe.de/shop/api/products?search=milch&brand=Alnatura&attribute=organic&objectsPerPage=5",
    "https://www.rewe.de/shop/p/weihenstephan-h-milch-3-5-1l/677067",
    "https://www.rewe.de/shop/c/milch/",
    "https://www.rewe.de/suche/produkte?search=milch"
  ],
  "proxy": {
    "useApifyProxy": true
  },
  "ignoreFieldsForChanges": []
}' |
apify call abotapi/rewe-de-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,abotapi/rewe-de-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/aOOpMWyTgiwrt0iFG/builds/ur9BL7pgBKXssPXuW/openapi.json
