# ROSSMANN.de Scraper - Prices, Reviews & Specials (`abotapi/rossmann-de-scraper`) Actor

Scrape ROSSMANN (rossmann.de) drugstore products: current + original strike-through price with discount, per-unit pricing (per 100ml/kg/piece), brand, stock and promo badges, plus full reviews with rating breakdown. Search by keyword, category or Angebote/online-only specials, or paste links.

- **URL**: https://apify.com/abotapi/rossmann-de-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** E-commerce, Developer tools, Automation
- **Stats:** 1 total users, 0 monthly users, 100.0% 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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## ROSSMANN Product Scraper

Pull rich product data from ROSSMANN (rossmann.de), the German drugstore retailer. Search by keyword, category, or the site's own current deals and online-only campaign listings, or paste product, search, category, or specials links directly. Every record includes current price, the manufacturer's original price with computed discount when a product is marked down, per-unit pricing (per litre, per kilogram, per piece) alongside the pack size, and a complete customer review history with a full rating breakdown.

### Why This Scraper?

- **Was-price and discount, structured, not scraped from a badge.** When a product carries a manufacturer's original price alongside ROSSMANN's own price, both are captured plus the computed discount amount and percentage.
- **Real per-unit pricing.** Every product returns its per-litre, per-kilogram, or per-piece price alongside the pack size, exactly as the site shows it -- with the unit clearly separated from the package size so the two are never confused.
- **Full review history.** Overall rating, review count, a full 1 to 5 star rating breakdown, and every individual review with author, date, rating, title, and body text.
- **Two real promotional listings.** Current deals ("Angebote") and the online-only campaign, each with thousands of live items -- not a single featured product.
- **Three ways in.** Keyword search, category browse, or paste any product/search/category/specials link and continue pagination automatically.
- **Optional export to your apps.** Send results into Notion, Linear, Airtable, or any Apify MCP connector alongside the dataset.

### Data You Get

| Field | Example value |
|---|---|
| productId / sku / ean | `123456`, `4000000000000` |
| name | `Sample Repair Shampoo` |
| brand | `Sample Brand` |
| url | `https://www.rossmann.de/de/sample-product-name/p/4000000000000` |
| price / currency | `2.29`, `EUR` |
| originalPrice / discountAmount / discountPercent | `2.99`, `0.70`, `23.4` |
| unitPrice / unitPriceCurrency / unitPriceUnit / unitPriceBasisSize | `9.16`, `EUR`, `L`, `1` |
| packagingAmount / packagingUnit | `250`, `ml` |
| rating / reviewCount | `4.8`, `261` |
| isOnSpecial / promotionLabel / badges | `true`, `Sample promo`, `["Sample badge"]` |
| stockLevelStatus / salesChannel | `IN_STOCK`, `BOTH` |
| categoryPath | `[{"name": "Care & Fragrance", "url": "..."}, {"name": "Hair Care", "url": "..."}]` |
| articleNumber | `154095` |
| description / ingredients / legalNotesText | full text fields |
| images | `["https://www.rossmann.de/media-neu/.../sample.png"]` |
| reviews.averageRating / reviews.totalReviewCount | `4.8`, `261` |
| reviews.ratingDistribution | `[{"rating": 5, "count": 218}, {"rating": 4, "count": 35}, ...]` |
| reviews.items\[] | `[{"rating": 5, "title": null, "body": "Sample review text.", "author": "Sample User", "date": "2026-03-23T12:27:40.000+00:00", "isRecommended": true}]` |

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

### How to Use

**Basic keyword search**

```json
{
  "mode": "search",
  "searchTerm": "shampoo",
  "maxItems": 20
}
```

**Search narrowed by category, brand and rating**

```json
{
  "mode": "search",
  "searchTerm": "shampoo",
  "category": "olcat3_4133099",
  "brands": ["Nivea"],
  "minRating": 4,
  "sortBy": "PRICE_ASC",
  "maxItems": 30
}
```

**Current deals, with full detail and review enrichment**

```json
{
  "mode": "search",
  "specialsCategory": "angebote",
  "fetchDetails": true,
  "fetchReviews": true,
  "maxReviewsPerProduct": 0,
  "maxItems": 50
}
```

**Paste product, search, category, or specials links**

```json
{
  "mode": "url",
  "urls": [
    "https://www.rossmann.de/de/sample-product-name/p/4000000000000",
    "https://www.rossmann.de/de/search?text=shampoo",
    "https://www.rossmann.de/de/angebote/m/angebote"
  ],
  "maxItems": 40
}
```

### Input Parameters

| Parameter | Type | Default | Description |
|---|---|---|---|
| `mode` | string | `search` | `search` (keyword/category/specials with filters) or `url` (paste links). |
| `searchTerm` | string | - | Free-text keyword for search mode. |
| `category` | string | - | A category code as shown in a category page link. Narrows a keyword search or browses a whole category alone. |
| `specialsCategory` | string | \`\` (none) | `angebote` (current deals) or `online-only` (online-only campaign). |
| `brands` | array | `[]` | Only these brands. |
| `minRating` | integer | - | Only products rated at or above this many stars (1-5). |
| `sortBy` | string | `RELEVANCE` | `RELEVANCE`, `PRICE_ASC`, `PRICE_DESC`, `NEWEST`, `RATING`. |
| `urls` | array | sample URL | Product, search, category, or specials links for URL mode. |
| `fetchDetails` | boolean | `false` | Adds full breadcrumb path, description, ingredients, legal/warning notes, media gallery, and article number. |
| `fetchReviews` | boolean | `true` | Fetches each product's full review history. |
| `maxReviewsPerProduct` | integer | `20` | Cap on reviews per product; `0` = all. |
| `maxPages` | integer | unlimited | Cap on result pages per search/URL entry. |
| `maxItems` | integer | `20` | Cap on total products; `0` = unlimited. |
| `proxy` | object | Apify default | Connection configuration. |
| `resumeFromRunId` | string | - | Continue ONE interrupted run: paste a previous run ID or dataset ID, and already-collected products are skipped (not returned, not charged again). |
| `incrementalMode` | boolean | `false` | Turn on for daily/recurring monitoring of the same search. Returns only NEW/UPDATED/REAPPEARED products by default; the actor remembers the previous run itself. |
| `stateKey` | string | - | Incremental mode only. Name a monitoring campaign to keep its state stable; leave empty to derive one automatically from the search/category/specials/URL and detail/review settings. |
| `emitUnchanged` | boolean | `false` | Incremental mode only. Also return (and bill for) products that did not change, marked `UNCHANGED`. |
| `emitExpired` | boolean | `false` | Incremental mode only. Also return (and bill for) products no longer found after a complete scan, marked `EXPIRED`. |
| `mcpConnectors` | array | `[]` | Optional MCP connectors to also receive results. |
| `notionParentPageUrl` | string | - | Required only when a Notion connector is selected. |
| `maxNotifyListings` | integer | `50` | Cap on items sent to each connector. |

### Resume & recurring updates

Two distinct features, both off by default so an ordinary run's output is unchanged:

- **Resume** (`resumeFromRunId`) continues ONE specific interrupted crawl. Paste the run ID (or dataset
  ID) of a previous run and this run returns only products it did not already collect there -- no
  duplicates, no double charges.
- **Incremental mode** (`incrementalMode`) is for scheduling this actor to run the SAME search again and
  again (e.g. daily). The actor remembers what it saw last time (keyed on the search/category/specials/
  URL and detail/review settings, or your own `stateKey`) and classifies every product:

  - `NEW` -- not seen before.
  - `UPDATED` -- a real field value changed since last time (`changedFields` lists which). A field that
    was simply not observed on one of the two runs (`null` on one side) is never treated as a change.
  - `UNCHANGED` -- nothing changed. Suppressed (not returned, not billed) unless `emitUnchanged` is on.
  - `REAPPEARED` -- back after previously being marked `EXPIRED`.
  - `EXPIRED` -- was tracked but is no longer found. Only produced once a run has scanned the FULL
    tracked search with no item cap hit and no Resume in effect; otherwise the previous state for those
    products is simply kept, since a partial scan can't tell "gone" apart from "not reached yet".
    Requires `emitExpired`.

  Every incremental-mode record adds `changeType`, `changedFields`, `firstSeenAt`, and `lastSeenAt`. These
  fields are absent in a normal (non-incremental) run.

### Output Example

```json
{
  "productId": "154095",
  "sku": "4000000000000",
  "ean": "4000000000000",
  "name": "Sample Repair Shampoo",
  "brand": "Sample Brand",
  "url": "https://www.rossmann.de/de/sample-product-name/p/4000000000000",
  "price": 2.29,
  "currency": "EUR",
  "originalPrice": null,
  "discountAmount": null,
  "discountPercent": null,
  "unitPrice": 9.16,
  "unitPriceCurrency": "EUR",
  "unitPriceUnit": "L",
  "unitPriceBasisSize": 1,
  "packagingAmount": 250,
  "packagingUnit": "ml",
  "rating": 4.8,
  "reviewCount": 261,
  "isOnSpecial": false,
  "badges": [],
  "stockLevelStatus": "IN_STOCK",
  "categoryPath": [
    { "name": "Care & Fragrance", "url": "https://www.rossmann.de/pflege-und-duft/c/olcat1_2325036" },
    { "name": "Hair Care", "url": "https://www.rossmann.de/pflege-und-duft/haarpflege/c/olcat2_4133009" }
  ],
  "images": [
    "https://www.rossmann.de/media-neu/sample/sample.png"
  ],
  "reviews": {
    "averageRating": 4.8,
    "totalReviewCount": 261,
    "ratingDistribution": [
      { "rating": 5, "count": 218 },
      { "rating": 4, "count": 35 },
      { "rating": 3, "count": 6 },
      { "rating": 2, "count": 2 },
      { "rating": 1, "count": 0 }
    ],
    "items": [
      {
        "rating": 5,
        "title": null,
        "body": "Sample review text describing the product experience.",
        "author": "Sample User",
        "date": "2026-03-23T12:27:40.000+00:00",
        "isRecommended": true
      }
    ]
  }
}
```

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

Optionally pipe results into an app you already use through a Model Context Protocol connector. Authorize a connector once under your account's Integrations settings, then select it in the input; for Notion, also set the parent page. Each connector receives a condensed, human-readable summary per item (a title plus key fields), not the full JSON record; the complete data always stays in the dataset.

### A Note on Access

Running this actor requires an active account with default proxy access, which is included on every plan. No further configuration is needed for typical use.

# Actor input Schema

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

'search' finds products by keyword, category, and/or the site's own Angebote/online-only specials, with real site filters. 'url' scrapes any product, search-result, category, or specials-listing page URL you paste, walking pagination forward automatically.

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

Free-text keyword, e.g. 'shampoo' or 'zahnpasta'. Combine with Category below to narrow a keyword search, or leave empty and set only Category or Specials category to browse a whole listing.

## `category` (type: `string`):

A ROSSMANN category code as it appears in a category page URL (.../c/<code>). Narrows a keyword search, or browses the whole category when Search keyword is left empty.

## `specialsCategory` (type: `string`):

Browse the site's own promotional listings instead of a keyword/category search: 'Angebote' (current weekly deals) or the online-only campaign. Leave as 'None' for a regular keyword/category search.

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

Only return products from these brands, e.g. 'Nivea', 'Balea', 'Isana'. Match the exact brand name as shown on the site. Leave empty for all brands.

## `minRating` (type: `integer`):

Only return products rated at or above this many stars, 1-5.

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

Order of the returned results.

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

Paste one or more ROSSMANN product pages (.../p/<ean>), search-result pages (/de/search?text=...), category pages (.../c/<code>), or specials-listing pages (/de/angebote/m/<slug>). Pagination continues forward automatically from any page index already in the URL. Filter fields above are ignored in this mode.

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

Adds the full breadcrumb category path, description, ingredients, legal/warning notes, media gallery, variant options, and article number from the product detail page. Current price, was-price/discount, unit price and rating are already included without this toggle.

## `fetchReviews` (type: `boolean`):

Fetch each product's full review history: overall rating, review count, a full 1-5 rating breakdown, and per-review author/date/rating/title/body. Products with no reviews simply return an empty list.

## `maxReviewsPerProduct` (type: `integer`):

Cap on reviews fetched per product when 'Fetch reviews' is on. 0 = all available reviews.

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

Stop after this many result pages per search/category/specials/URL entry. Leave empty for no page limit -- the run stops at Max products total.

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

Hard cap on total products returned across every search/category/specials/URL entry. 0 = unlimited (still bounded by Max pages per search).

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

The default connection works on every Apify plan, including the free tier. A residential connection is optional and only needed for very large or sustained runs.

## `resumeFromRunId` (type: `string`):

Paste a previous run ID or dataset ID to continue a large crawl of products without returning or charging for products already collected there. Use this after an interrupted run, or when continuing a product catalogue pull in another run. For recurring daily/weekly monitoring of the same search, use Incremental mode below instead.

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

Turn this on for daily or recurring monitoring. The first run returns all matching products as NEW. Later runs normally return only NEW, UPDATED, and REAPPEARED products. Turn on "Emit unchanged" or "Emit expired" only when you also want those products returned (and billed). State is kept separately for each search/category/specials/URL and detail/review setup; use State key when you want to name or deliberately share a monitoring campaign. To continue one specific interrupted run instead, use Resume from a previous run above.

## `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 the search/category/specials/URL and detail/review 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 capped it or when Resume was used. This returns -- and bills -- extra synthetic rows, so leave it off unless you need expiry tracking.

## `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",
  "searchTerm": "nivea duschgel",
  "specialsCategory": "",
  "brands": [],
  "sortBy": "RELEVANCE",
  "urls": [
    "https://www.rossmann.de/de/search?text=nivea%20duschgel"
  ],
  "fetchDetails": false,
  "fetchReviews": true,
  "maxReviewsPerProduct": 20,
  "maxItems": 20,
  "proxy": {
    "useApifyProxy": true
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "maxNotifyListings": 50
}
```

# Actor output Schema

## `overview` (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",
    "searchTerm": "nivea duschgel",
    "brands": [],
    "urls": [
        "https://www.rossmann.de/de/search?text=nivea%20duschgel"
    ],
    "proxy": {
        "useApifyProxy": true
    },
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/rossmann-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",
    "searchTerm": "nivea duschgel",
    "brands": [],
    "urls": ["https://www.rossmann.de/de/search?text=nivea%20duschgel"],
    "proxy": { "useApifyProxy": True },
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/rossmann-de-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).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",
  "searchTerm": "nivea duschgel",
  "brands": [],
  "urls": [
    "https://www.rossmann.de/de/search?text=nivea%20duschgel"
  ],
  "proxy": {
    "useApifyProxy": true
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false
}' |
apify call abotapi/rossmann-de-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=abotapi/rossmann-de-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/t8lAGQA0ug3Uu9l6K/builds/Y2jbUxDQX6cz6LuXB/openapi.json
