# 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
- **Features:** Limited permissions
- **Stats:** 12 total users, 4 monthly users, 95.5% runs succeeded, 1 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

## ROSSMANN Scraper: Prices, Discounts, Unit Prices & Reviews

ROSSMANN Scraper turns rossmann.de, the German drugstore retailer, into a clean product data feed. Get the current price, the manufacturer's original price with the computed discount when a product is marked down, real per-unit pricing (per litre, per kilogram, or per piece) alongside the pack size, stock and promo badges, and review history (up to 1000 reviews per product) with a complete rating breakdown. Search by keyword, browse a category, or pull the site's own current deals and online-only campaign listings, or paste product, search, category, or specials links directly, then export to JSON, CSV or Excel, or pull results straight into your app through the API.

### 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.** Search, category and specials results return a per-litre, per-kilogram, or per-piece price alongside the pack size where the site shows one, with the unit kept separate from the package size so the two are never confused; a pasted product link has no unit price.
- **Review history.** Overall rating, review count, a complete 1 to 5 star rating breakdown, and individual reviews (20 per product by default, up to 1000 with Max reviews per product) 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, or specials link and pagination continues forward automatically.
- **Built for schedules.** Incremental mode returns only new and changed products on repeat runs against the same search, and Resume continues one interrupted run without paying twice.
- **Optional export to your apps.** Send results into Notion, Linear, Airtable, or any Apify MCP connector alongside the dataset.

### Use Cases

- **Price monitoring and repricing:** track ROSSMANN's current price, was-price, and discount for your own catalogue over time.
- **Deal and promotion tracking:** pull every item in Angebote or the online-only campaign to catch markdowns as they go live.
- **Review and reputation research:** analyze rating distributions and individual review text across a brand or product line.
- **Assortment and category research:** browse a whole category or brand for range and pricing analysis.

### Data You Get

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

| Field | Example |
| --- | --- |
| `productId` | `654321` |
| `ean` / `sku` | `4000000000001` |
| `name` | `Sample Repair Shampoo 250ml` |
| `brand` | `Sample Brand` |
| `url` | `https://www.rossmann.de/de/sample-repair-shampoo/p/4000000000001` |
| `price` / `currency` | `2.29`, `EUR` |
| `originalPrice` / `discountAmount` / `discountPercent` | `2.99`, `0.7`, `23.4` |
| `promotionLabel` / `isOnSpecial` | `"Sample promo"`, `true` |
| `unitPrice` / `unitPriceUnit` / `unitPriceBasisSize` | `9.16`, `"L"`, `1` |
| `packagingAmount` / `packagingUnit` | `250`, `"ml"` |
| `rating` / `reviewCount` | `4.8`, `261` |
| `badges` | `["Sample badge"]` |
| `stockLevelStatus` / `salesChannel` | `"IN_STOCK"`, `"BOTH"` |
| `categoryPath` | `[{"name": "Care & Fragrance", "url": "..."}]` (with Fetch full product detail on) |
| `articleNumber` | `654321` (with Fetch full product detail on) |
| `images` | `["https://www.rossmann.de/media-neu/.../sample.png"]` (with Fetch full product detail on) |
| `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}]` |
| `changeType` / `changedFields` | `"UPDATED"`, `["price"]` (incremental mode only) |
| `firstSeenAt` / `lastSeenAt` | `"2026-03-01T00:00:00Z"` (incremental mode only) |

With Fetch full product detail on, each product record also carries a full text description, an ingredients list, legal/warning notes, and, for products sold in several variants (for example colours), the sibling variants straight from the product page. Every search, category or specials result additionally carries a thumbnail image and alt text, per-item order limits (`minOrderAmount`, `maxOrderAmount`, `orderIncrement`), an `isRateable` flag, and the raw `disruptors` array that badges and the special flag are derived from, plus the raw `legalNotes` array. A pasted product link's record starts from the site's own search card for that article number (price, unit price, discount, stock, order limits, `disruptors`, `legalNotes`, rating) when the site's search finds it, and is completed with the product-page fields (`brand`, `categoryPath`, `badges`, `articleNumber`, `description`, `ingredients`, `legalNotesText`, `images`, `variants`, `currency`, `availability`) and `reviews`. If the search does not return the article, the record carries only `productId`, `ean`, `sku`, `url`, `name` and the product-page fields, with the price read from the product page.

### How to Use

1. Pick a **mode**: `search` (keyword, category, and/or ROSSMANN's own Angebote/online-only specials) or `url` (paste links).
2. For search mode, set a keyword and/or category, and narrow it with brand, rating, and sort filters as needed.
3. Turn on **Fetch full product detail** and/or **Fetch reviews** for the extra fields you need, and set **Max products total** to control run size and cost.
4. Click **Start**, then download the dataset as JSON, CSV, or Excel, or read it through the API.

**Basic keyword search:**

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

**Search narrowed by category, brand, rating, and sort order:**

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

**Current deals, with full detail and reviews (up to 1000 per product):**

```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/4000000000001",
    "https://www.rossmann.de/de/search?text=shampoo",
    "https://www.rossmann.de/de/angebote/m/angebote"
  ],
  "maxItems": 40
}
```

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/rossmann-de-scraper").call(run_input={"mode": "search", "searchTerm": "shampoo", "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/rossmann-de-scraper').call({ mode: 'search', searchTerm: 'shampoo', 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.

#### Max items and max pages

**URL mode accepts these link shapes:**

| Shape | Example path |
|---|---|
| Keyword search | `/de/search?text=nivea%20duschgel` |
| Paginated search | `/de/search?text=nivea%20duschgel&pageIndex=1` |
| Search with sort | `/de/search?text=nivea&sort=sale_price%20asc` (the friendly `sort=PRICE_ASC`, `PRICE_DESC`, `NEWEST`, `RATING` are also accepted) |
| EAN search | `/de/search?text=4006000317779` |
| Category (any slug) | `/de/pflege-und-duft/c/olcat1_2325036`, `/de/kategorie/c/olcat1_2325036` |
| Paginated category | `/de/pflege-und-duft/c/olcat1_2325036?pageIndex=1` |
| Specials | `/de/angebote/m/angebote`, `/de/angebote/m/neu-im-sortiment`, bare `/de/angebote`, paginated with `?pageIndex=1` |
| Product detail (any slug) | `/de/<slug>/p/4006000317779`, `/de/x/p/4006000317779` |
| Brands index | `/de/marken/c/brands` is not supported: it is skipped with a warning per link |

Note: in URL mode the **URLs** field is pre-filled with one example per shape, so running URL mode without editing it scrapes all of them (still capped by **Max products total**).

**Max products total** (`maxItems`) caps the number of products actually returned, and billed, across every search/category/specials/URL entry in one run; `0` means unlimited. **Max pages per search** (`maxPages`) caps how many result pages are walked per entry before moving to the next one; leave it empty for no page limit, in which case a run simply stops once **Max products total** is reached. A run that stops before the natural end of its result set, whether from either cap, also disables Incremental mode's `EXPIRED` detection for that run.

#### Resume and recurring updates

Two distinct features, both off by default:

- **Resume** (`resumeFromRunId`) continues ONE specific interrupted run. 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 (for example 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 as `NEW`, `UPDATED` (with `changedFields` listing what changed; a field simply not observed on one of the two runs is never treated as a change), `UNCHANGED` (suppressed, not returned or billed, unless `emitUnchanged` is on), `REAPPEARED`, or `EXPIRED`.

  `EXPIRED` is only produced once a run has scanned the FULL tracked search with no item cap hit and Resume not in effect; a partial scan cannot tell "gone" apart from "not reached yet", so those products simply keep their previous state instead. This also requires `emitExpired`. `REAPPEARED` can only happen to a product that an earlier complete scan already marked `EXPIRED` this way; a product does not turn up as REAPPEARED just because one run happened to miss it. Every incremental-mode record adds `changeType`, `changedFields`, `firstSeenAt`, and `lastSeenAt`; these fields are absent from a normal (non-incremental) run.
  **What is compared.** `UPDATED` means price, original price, discount, promotion, unit price, stock status, name, brand, images, descriptions, badges or other product data moved. Ratings, review counts and reviews never make a product `UPDATED` (they stay in the output), and the order of badges is ignored. **Change scope** (`changeScope`) set to `priceAndStock` narrows this to price, discount, unit price, stock status and variant price/stock only. **Ignore fields when detecting changes** (`ignoreFieldsForChanges`) removes further fields (a dotted path such as `variants.label` removes that key inside nested lists). **Compact UPDATED rows** (`compactUpdates`) return only identifiers, the current price and stock status and the old/new values of what moved (`priceChanges`, `variantChanges`); compact rows carry no detail or review data and the detail-enrichment charge is not applied to them. **Expire after N missed scans** (`expireAfterMissedScans`, default 1) tombstones a product only after it was missing from that many consecutive complete scans; `EXPIRED` means the product was no longer found in the search results, which a listing change and a delisting both cause, so use the stock status field for availability. A pasted URL that starts at a later `pageIndex` is a partial scan and never produces `EXPIRED`.

### 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 haven't authorized a connector yet.)
3. For **Notion**, also set `notionParentPageUrl` to the page where product pages should be created.
4. `maxNotifyListings` caps how many items are written to each connector per run; it never affects the dataset.

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

#### Aliases for users of other ROSSMANN scrapers

Every product row also carries `productName`, `inStock` (true/false from the stock status), `imageUrl`, `category` (the last breadcrumb name, when product details are fetched; the full path is `categoryPath`), `productDescription` (when details are fetched), `discount` (percent) and `scrapedAt`. The inputs `searchQuery`, `categoryUrl`, `maxResults` and `proxyConfiguration` are accepted as aliases of Search term, Category, Max products total and Proxy. The `country` input accepts only `de`; any other value stops the run with a clear message. If the site visibly ignores a brand, minimum-rating or price-sort setting on the first page, the run log says so. Price sorting follows the site's own ordering, so a product with several price points can sit slightly out of order. A keyword that is a brand name (for example `nivea`) lists that brand's whole range, exactly as the site's own search does.

### Input Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `mode` | string | `search` | `search` (keyword/category/specials with filters) or `url` (paste links). |
| `searchTerm` | string | no default (prefill: `nivea duschgel`) | Free-text keyword for search mode. |
| `category` | string | no default | A category code (`olcat...`), the category page URL, or the category name as shown in the site navigation. A name that is not found stops the run with a clear error. Narrows a keyword search, or browses a whole category alone. |
| `specialsCategory` | string | \`\` (none) | `angebote` (current deals) or `online-only` (online-only campaign). |
| `brands` | array | no default (prefill: `[]`) | Only return products from these exact brand names. |
| `minRating` | integer | no default | Only products rated at or above this many stars (1-5). |
| `sortBy` | string | `RELEVANCE` | `RELEVANCE`, `PRICE_ASC`, `PRICE_DESC`, `NEWEST`, or `RATING`. |
| `urls` | array | 11 example links (one per URL shape, see the URL mode table) | Product, search, category, or specials links for URL mode. Filter fields above are ignored in this mode. If you leave the field untouched, URL mode scrapes one example per shape; clear the list and add your own links to scrape only those. |
| `fetchDetails` | boolean | `false` | Adds the breadcrumb category path, description, ingredients, legal/warning notes, media gallery, sibling variants (colours and similar, when the product has any), and article number from the product page for search/category/specials results. A pasted product link in URL mode always fetches the product page regardless of this toggle; it is billed for detail-enrichment when that page was read or its reviews were fetched. |
| `fetchReviews` | boolean | `true` | Fetches each product's reviews, up to Max reviews per product (default 20, at most 1000). |
| `maxReviewsPerProduct` | integer | `20` | Cap on reviews per product when Fetch reviews is on; `0` = up to the maximum of 1000 reviews per product; higher values are also limited to 1000. |
| `maxPages` | integer | no default (unlimited when empty) | Stop after this many result pages per search/category/specials/URL entry. |
| `maxItems` | integer | `20` | Hard cap on total products returned across the whole run; `0` = unlimited. |
| `proxy` | object | Apify Proxy (auto) | Connection configuration. |
| `resumeFromRunId` | string | no default | Continue ONE interrupted run: paste its run 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; classifies each product against the previous run's state. |
| `stateKey` | string | no default | Name or deliberately share an incremental-mode monitoring campaign; auto-derived from the search/detail/review settings when left empty. |
| `changeScope` | string | `all` | Incremental mode: `priceAndStock` makes only price, discount and stock changes count as UPDATED. |
| `compactUpdates` | boolean | `false` | Incremental mode: UPDATED and EXPIRED rows carry only identifiers, current price/stock and old/new values. |
| `expireAfterMissedScans` | integer | `1` | Incremental mode: consecutive complete scans a product must be missing before it is EXPIRED. |
| `ignoreFieldsForChanges` | array | empty | Incremental mode: fields (or dotted paths) that never make a product UPDATED. |
| `emitUnchanged` | boolean | `false` | Also return (and bill for) products classified `UNCHANGED`. |
| `emitExpired` | boolean | `false` | Also return (and bill for) products classified `EXPIRED` after a complete scan. |
| `mcpConnectors` | array | no default | Optional: send a summary of each product to apps you authorized under Integrations. |
| `notionParentPageUrl` | string | no default | Notion connector only: page under which product pages are created. |
| `maxNotifyListings` | integer | `50` | Cap on items written to each connector per run. |
| `searchQuery` | string | no default | Alias of `searchTerm`. |
| `categoryUrl` | string | no default | Alias of `category`. |
| `maxResults` | integer | no default | Alias of `maxItems`. |
| `proxyConfiguration` | object | no default | Alias of `proxy`. |
| `country` | string | `de` | Only `de` is accepted; any other value stops the run with a clear message. |

### Output Example

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

```json
{
  "productId": "654321",
  "ean": "4000000000001",
  "sku": "4000000000001",
  "name": "Sample Repair Shampoo 250ml",
  "brand": "Sample Brand",
  "url": "https://www.rossmann.de/de/sample-repair-shampoo/p/4000000000001",
  "price": 2.29,
  "currency": "EUR",
  "originalPrice": 2.99,
  "originalPriceCurrency": "EUR",
  "discountAmount": 0.7,
  "discountPercent": 23.4,
  "promotionLabel": "Sample promo",
  "isOnSpecial": true,
  "badges": ["Sample badge"],
  "unitPrice": 9.16,
  "unitPriceCurrency": "EUR",
  "unitPriceUnit": "L",
  "unitPriceBasisSize": 1,
  "packagingAmount": 250,
  "packagingUnit": "ml",
  "rating": 4.8,
  "reviewCount": 261,
  "stockLevelStatus": "IN_STOCK",
  "salesChannel": "BOTH",
  "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
      }
    ]
  }
}
```

### Plan Requirement

ROSSMANN protects every page (search, category, specials, product) with a JavaScript access check ("Client Challenge"). Plain HTTP requests, including through a datacenter proxy, only receive that check page and no product data, so this actor opens a real browser session to clear the check and then reads the pages. The first clearance can take a few minutes, so allow a generous run timeout (at least 5 minutes) for small runs.

The connection starts on your selected Apify proxy and escalates through further connections automatically. Residential proxies are not included on Apify free plans: there the run logs "The selected connection is unavailable to this run." and continues on a backup connection where possible, and some pages may still be unavailable. If you supply your own proxy URLs the backup connections are disabled. For large or sustained runs, select the residential proxy group under **Connection** on a paid plan.

Running this actor bills the pay-per-event charges shown on the actor's **Pricing** tab: one charge per product returned, plus the detail-enrichment charge, applied once per product as that product is delivered, when full detail and/or reviews were fetched for it. Compact UPDATED rows and unchanged rows that are not returned are not charged.

### FAQ

#### How much does it cost?

You pay per product returned, plus a one-time-per-product surcharge, applied as each product is delivered, whenever full detail and/or the review history was actually fetched for it. `fetchReviews` defaults to on, so most runs already trigger that surcharge unless you turn it off. The **Pricing** tab shows the current per-event rates. Use **Max products total** to cap the cost of any run.

#### Is it legal to scrape ROSSMANN?

This actor collects only publicly available product data. You are responsible for how you use it: follow ROSSMANN's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Prices and product facts are generally not protected, but product photos and review text 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**. Each run then returns only new and updated products, and unchanged ones are not billed unless you turn on **Emit unchanged**.

#### Why did my run fail with a connection message?

If ROSSMANN's access check cannot be cleared on any available connection, or no results page could be read, the run ends as **Failed** with a clear status message instead of reporting an empty success. The messages are: "The proxy connection was refused for this account, so no page could be read. Your Apify plan does not include proxy access; a paid plan is required", "No results: residential proxies are unavailable on this run (Apify free plans do not include them), and the backup connection could not load the requested Rossmann pages", and "Could not load any results page (the requests failed), so nothing was collected. This is a connection problem, not an empty search." Items collected before a mid-run block are kept. When the backup connection serves the run, the log says "Served over the backup connection (the default connection was unavailable)."

#### Why did my run return an empty dataset instead of failing?

Only when the pages loaded normally and nothing matched your search, category or filters: that is a genuinely empty result and the run succeeds with an empty dataset. A blocked or unreadable connection never ends as an empty success; it fails with one of the messages above. Two other situations also stop the run: pasting a Resume run or dataset ID that cannot be read, and turning on Resume together with Incremental mode when that same search already has saved incremental state.

#### Does "Fetch full product detail" apply to a pasted product link?

No. A pasted product link is first looked up by its article number in the site's search to get the listing card (price, stock and so on), at the cost of one extra search request per link, and the product page is then always fetched, whatever the **Fetch full product detail** toggle says. The detail-enrichment charge applies once to that product when its product page was read or its reviews were fetched. That toggle only controls whether search, category, and specials results also get an extra detail-page fetch on top of their listing-card data.

#### What's the difference between Resume and Incremental mode?

**Resume** continues ONE specific interrupted run: paste its run or dataset ID and already-collected products are skipped, no duplicates, no double charges. **Incremental mode** is for running the SAME search again and again on a schedule; the actor remembers state itself and classifies every product as new, changed, unchanged, reappeared, or expired. Turning on Resume together with Incremental mode for a search that already has saved incremental state is rejected; use one or the other for that search.

#### 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 product summaries into Notion, Linear, or Airtable.

### 🔗 Want more e-commerce data?

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

<table>
<tr><td>🛒 <a href="https://apify.com/abotapi/hornbach-de-scraper"><b>HORNBACH Products, Prices &amp; Reviews Scraper</b></a><br>Scrape HORNBACH (hornbach.de) DIY, building &amp; garden products: current + strike-through...</td><td>🛒 <a href="https://apify.com/abotapi/dm-de-scraper"><b>dm.de</b></a><br>Scrape dm-drogerie markt (dm.de) products: current + strike-through Ausverkauf price with...</td></tr>
<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/obi-de-scraper"><b>OBI.de Scraper</b></a><br>Scrape OBI (obi.de) DIY/home-improvement products: current + strike-through original...</td></tr>
<tr><td>🛒 <a href="https://apify.com/abotapi/thalia-de-scraper"><b>Thalia.de Scraper</b></a><br>Scrape Thalia.de books, eBooks, audiobooks, toys and stationery. Search by keyword or...</td><td>🛒 <a href="https://apify.com/abotapi/douglas-de"><b>Douglas Germany Scraper</b></a><br>Scrape Douglas.de beauty and fragrance products by keyword, category, brand, or URL...</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/rossmann-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, 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: the code from a category page URL (olcat...), the category page URL itself, or the category name as shown in the site navigation (e.g. 'Pflege & Duft'). Narrows a keyword search, or browses the whole category when Search keyword is left empty. A name that is not found in the navigation stops the run with a clear error.

## `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 ROSSMANN links: product pages (.../p/<ean>), keyword or EAN search pages (/de/search?text=...), category pages (.../c/<code>), or specials listings (/de/angebote/m/<slug>). Pagination continues forward from any pageIndex already in the URL. Brand index pages (/de/marken/c/brands) are not supported and are skipped with a warning. Filter fields above are ignored in this mode. If you leave this list untouched, URL mode scrapes the 11 built-in example links (one per URL shape); clear it and add your own links to scrape only those.

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

Adds the full breadcrumb category path, description, ingredients, legal/warning notes, media gallery, sibling variants (colours and similar, when the product has any), 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 reviews, up to Max reviews per product (default 20, at most 1000): 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 = up to 1000 reviews per product; larger values are also limited to 1000.

## `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. UPDATED means a product's data moved: price, original price, discount, promotion, unit price, stock status, name, brand, images, descriptions, badges or other product fields (Change scope "priceAndStock" narrows this to price and stock only). Ratings, review counts and reviews never make a product UPDATED; they stay in the output. 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.

## `changeScope` (type: `string`):

Which changes make a product UPDATED. "all" (default): any change to price, original price, discount, unit price, stock status, name, brand, images, descriptions, badges and other product data. "priceAndStock": only price, original price, discount, promotion, unit price and stock/availability changes (and variant stock/price); descriptions, images, badges and labels never create an UPDATED row. Ratings and review counts never decide UPDATED in either scope; they stay in the output. Switching scope under an automatic state key starts a fresh baseline; set a State key to keep one.

## `compactUpdates` (type: `boolean`):

Off by default. When on, UPDATED and EXPIRED rows contain only identifiers, the current price and stock status and the old/new values of what moved (priceChanges, variantChanges). NEW rows stay full. Reviews and detail data are not repeated on compact rows and are not charged for them.

## `expireAfterMissedScans` (type: `integer`):

With "Emit expired" on, a product is marked EXPIRED only after it was missing from this many CONSECUTIVE complete scans (default 1). EXPIRED means the product was no longer found in the search results; without opening each product it cannot tell a listing change from a delisting, so use the stock status field for availability.

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

Field names that never make a product UPDATED or appear in changedFields (they stay in the output). Use a dotted path to ignore a key inside a nested list, e.g. variants.label. Ratings, review counts and reviews are already ignored. Leave empty to compare everything.

## `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.

## `searchQuery` (type: `string`):

Alias of Search term, for users migrating from other ROSSMANN scrapers. Used only when Search term is empty.

## `categoryUrl` (type: `string`):

Alias of Category: a category page link or code. Used only when Category is empty.

## `maxResults` (type: `integer`):

Alias of Max products total. Used only when that field is not sent.

## `proxyConfiguration` (type: `object`):

Alias of Proxy configuration.

## `country` (type: `string`):

Only 'de' (rossmann.de) is supported; other values stop the run with a clear error.

## Actor input object example

```json
{
  "mode": "search",
  "searchTerm": "nivea duschgel",
  "specialsCategory": "",
  "brands": [],
  "sortBy": "RELEVANCE",
  "urls": [
    "https://www.rossmann.de/de/search?text=nivea%20duschgel",
    "https://www.rossmann.de/de/search?text=nivea%20duschgel&pageIndex=1",
    "https://www.rossmann.de/de/search?text=nivea&sort=sale_price%20asc",
    "https://www.rossmann.de/de/search?text=4006000317779",
    "https://www.rossmann.de/de/pflege-und-duft/c/olcat1_2325036",
    "https://www.rossmann.de/de/pflege-und-duft/c/olcat1_2325036?pageIndex=1",
    "https://www.rossmann.de/de/angebote/m/angebote",
    "https://www.rossmann.de/de/angebote/m/angebote?pageIndex=1",
    "https://www.rossmann.de/de/angebote/m/neu-im-sortiment",
    "https://www.rossmann.de/de/angebote",
    "https://www.rossmann.de/de/pflege-und-duft-nivea-duschgel-vanilla-pecan-praline/p/4006000317779"
  ],
  "fetchDetails": false,
  "fetchReviews": true,
  "maxReviewsPerProduct": 20,
  "maxItems": 20,
  "proxy": {
    "useApifyProxy": true
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "changeScope": "all",
  "compactUpdates": false,
  "expireAfterMissedScans": 1,
  "maxNotifyListings": 50,
  "country": "de"
}
```

# Actor output Schema

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

No description

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

No description

## `reviews` (type: `string`):

No description

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

No description

## `summary` (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",
        "https://www.rossmann.de/de/search?text=nivea%20duschgel&pageIndex=1",
        "https://www.rossmann.de/de/search?text=nivea&sort=sale_price%20asc",
        "https://www.rossmann.de/de/search?text=4006000317779",
        "https://www.rossmann.de/de/pflege-und-duft/c/olcat1_2325036",
        "https://www.rossmann.de/de/pflege-und-duft/c/olcat1_2325036?pageIndex=1",
        "https://www.rossmann.de/de/angebote/m/angebote",
        "https://www.rossmann.de/de/angebote/m/angebote?pageIndex=1",
        "https://www.rossmann.de/de/angebote/m/neu-im-sortiment",
        "https://www.rossmann.de/de/angebote",
        "https://www.rossmann.de/de/pflege-und-duft-nivea-duschgel-vanilla-pecan-praline/p/4006000317779"
    ],
    "proxy": {
        "useApifyProxy": true
    },
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false,
    "compactUpdates": 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",
        "https://www.rossmann.de/de/search?text=nivea%20duschgel&pageIndex=1",
        "https://www.rossmann.de/de/search?text=nivea&sort=sale_price%20asc",
        "https://www.rossmann.de/de/search?text=4006000317779",
        "https://www.rossmann.de/de/pflege-und-duft/c/olcat1_2325036",
        "https://www.rossmann.de/de/pflege-und-duft/c/olcat1_2325036?pageIndex=1",
        "https://www.rossmann.de/de/angebote/m/angebote",
        "https://www.rossmann.de/de/angebote/m/angebote?pageIndex=1",
        "https://www.rossmann.de/de/angebote/m/neu-im-sortiment",
        "https://www.rossmann.de/de/angebote",
        "https://www.rossmann.de/de/pflege-und-duft-nivea-duschgel-vanilla-pecan-praline/p/4006000317779",
    ],
    "proxy": { "useApifyProxy": True },
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
    "compactUpdates": 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(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",
  "searchTerm": "nivea duschgel",
  "brands": [],
  "urls": [
    "https://www.rossmann.de/de/search?text=nivea%20duschgel",
    "https://www.rossmann.de/de/search?text=nivea%20duschgel&pageIndex=1",
    "https://www.rossmann.de/de/search?text=nivea&sort=sale_price%20asc",
    "https://www.rossmann.de/de/search?text=4006000317779",
    "https://www.rossmann.de/de/pflege-und-duft/c/olcat1_2325036",
    "https://www.rossmann.de/de/pflege-und-duft/c/olcat1_2325036?pageIndex=1",
    "https://www.rossmann.de/de/angebote/m/angebote",
    "https://www.rossmann.de/de/angebote/m/angebote?pageIndex=1",
    "https://www.rossmann.de/de/angebote/m/neu-im-sortiment",
    "https://www.rossmann.de/de/angebote",
    "https://www.rossmann.de/de/pflege-und-duft-nivea-duschgel-vanilla-pecan-praline/p/4006000317779"
  ],
  "proxy": {
    "useApifyProxy": true
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "compactUpdates": false
}' |
apify call abotapi/rossmann-de-scraper --silent --output-dataset

```

## MCP server setup

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