# Douglas Beauty Products, Prices & Reviews Scraper (`abotapi/douglas-de`) Actor

Scrape douglas.de beauty and perfumery products. Returns name, brand, price with original/was price and discount, unit price, size variants, availability, gallery, ratings, reviews, ingredients and spec table. Keyword, category, brand browse plus URL input.

- **URL**: https://apify.com/abotapi/douglas-de.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** E-commerce, Automation, Developer tools
- **Stats:** 3 total users, 3 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

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

## Douglas DE Beauty & Perfumery Scraper

Scrape products from [douglas.de](https://www.douglas.de/), Douglas, Europe's leading
beauty, perfumery and cosmetics retailer (German flagship, EUR pricing). The actor returns
rich per-product records: identity, full price with the original/was price and discount,
unit price, size variants, availability, image gallery, ratings, reviews, and product
detail (ingredients, how-to-use, spec table).

### What you get (per product)

| Field | Example |
|---|---|
| `title` | TOM FORD Soleil Neige Eau de Parfum |
| `brand` | TOM FORD |
| `id` / `sku` / `ean` | 5011956034 / 1269078 / 0888066177962 |
| `url` | https://www.douglas.de/de/p/5011956034?variant=1269078 |
| `price` | `{current: 76.99, original: 115.0, currency: "EUR", discountPercent: 33.0}` |
| `unitPrice` | `{amount: 769.9, unit: "per 1 l"}` |
| `isOnSpecial` / `wasPrice` / `savingsAmount` | true / 115.0 / 38.01 |
| `availability` | `{code: "AVAILABLE", inStock: true}` |
| `variants` | `[{sku, label: "30 ml", price, unitPrice}, ...]` |
| `images` | `[{url, alt}, ...]` |
| `rating` / `reviewCount` | 4.8 / 86 |
| `ratingBreakdown` | `[{rating: 5, count: 77}, {rating: 4, count: 6}, ...]` |
| `reviews` | `[{id, author, date, rating, title, body}, ...]` |
| `ingredients` | ALCOHOL, PARFUM / FRAGRANCE, AQUA / WATER / EAU, ... |
| `howToUse` | Anwendungshinweise (how to use) |
| `specifications` | `{Duftrichtung: "frisch, blumig", Kopfnote: "Jasmin", ...}` |
| `breadcrumbs` | `[{name: "Parfum", url}, ...]` |

Full-price (non-discounted) products return `isOnSpecial: false`, `wasPrice: null` so they
are an explicit negative, never a missing key.

### Input modes

#### Search mode (default)

Keyword search and/or category and brand browse.

```json
{
  "mode": "search",
  "queries": ["vitamin c serum"],
  "category": "01",
  "brand": "b0690",
  "gender": "FEMALE",
  "sortBy": "rating"
}
```

- `queries`: free-text keywords, e.g. `"parfum"`, `"dior lipstift"`.
- `category`: a Douglas category code (`01` Parfum, `03` Make-up, `1205` Gesichtspflege) or
  a full category URL like `https://www.douglas.de/de/c/parfum/01`.
- `brand`: a brand **display name** (`Dior`) or a brand URL like
  `https://www.douglas.de/de/b/dior/b0690` for an exact filter. A bare brand
  code (`b0690`) cannot be resolved to a name and is skipped with a warning
  rather than risk matching the wrong brand — pass the name or URL instead.
- `gender`: any / women / men / unisex. Search mode only, it is sent as a store facet.
- `sortBy`: best match, price ascending, price descending, newest first, top rated.
  Search mode only, a pasted URL is walked in the store's own default order.
- `promoFlags`: extra store promotion facets. Search mode only.

#### URL mode

Paste one or more Douglas URLs. Product URLs return a single rich record; category, brand
and search URLs return products up to the configured item and page limits.

```json
{
  "mode": "url",
  "urls": ["https://www.douglas.de/de/c/parfum/01"],
  "priceMax": 80,
  "specialsOnly": true,
  "fetchDetails": true,
  "fetchReviews": true
}
```

#### Filters (both modes)

`priceMin`, `priceMax` and `specialsOnly` are **not** search-mode-only. They are applied to
every product the scraper collects, so a pasted category, brand or search URL is filtered
exactly like a keyword search.

- `priceMin` / `priceMax`: EUR price band. In search mode the band is also sent to the store
  as a price facet; in URL mode it narrows the products found behind the pasted URL.
- `specialsOnly`: keep only products that are currently reduced (see Specials below), in
  either mode.

The one exception is a pasted **product** URL: a single product you asked for by URL is
always returned, whatever its price and whether or not it is reduced.

### Specials and the was/original price

Douglas does **not** run a dedicated sale or outlet category. A product is on special when
its recommended retail price (UVP, "Empfohlener Verkaufspreis") is crossed out and a lower
current price is shown. The actor captures both prices on every product:

- `price.original` (the crossed-out UVP) and `price.discountPercent` come straight from the
  listing data.
- `isOnSpecial`, `wasPrice`, `savingsAmount`, `savingsPercent` are derived from those two
  prices.
- Turn on `specialsOnly` to keep only the reduced products. A real discounted product looks
  like: `price.current: 76.99`, `price.original: 115.0`, `isOnSpecial: true`,
  `wasPrice: 115.0`, `savingsAmount: 38.01`, `savingsPercent: 33.0`.

### Reviews

Turn on `fetchReviews` to pull the overall rating, the star-rating histogram, the EAN/GTIN,
and per-review text (author, date, rating, title, body) for each product. `maxReviewsPerProduct`
caps how many reviews come back (default 20). Reviews come from the retailer's own first-party
ratings endpoint, so no third-party host appears in the output. Products with no reviews
return an empty `reviews` list while the rating summary fields stay null.

### Product detail

Turn on `fetchDetails` to open each product page and pull the ingredient list (INCI), the
how-to-use text and the full spec table (scent notes, product type, article number, etc.),
flattened into `specifications`. Listing-only runs skip this and are faster and cheaper.
Detail and review enrichment open the individual product page, which is a separate, harder
route than listing data — on a given run it may return the base product without
enrichment; the listing fields themselves (price, variants, gallery, rating) are unaffected.

### Limits and cost

- `maxItems`: hard cap on the total number of products returned (default 100).
- `maxPages`: max pages to walk per query/category/URL (default 0, unlimited). One page holds
  48 products.
- A listing-only run returns fully-attributed products (price, variants, gallery, rating,
  description). Detail and review enrichment are optional, opt-in toggles.

### Proxy

A Germany exit is required, and the default is already set up for it. Changing the preset
can make a run return no results.

### Export to your apps

Optionally send a condensed summary of each result to Notion, Linear, Airtable or Apify via
MCP connectors. Authorize a connector under Apify Settings, Integrations, then select it in
`mcpConnectors`. The full record always stays in the dataset.

# Actor input Schema

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

Search mode = keyword + category + brand + filters. URL mode = paste Douglas product, category, brand or search URLs.

## `queries` (type: `array`):

Keywords to search, e.g. 'vitamin c serum', 'parfum', 'dior lipstift'. Each query runs as a separate search.

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

A Douglas category code (e.g. '01' Parfum, '03' Make-up, '1205' Gesichtspflege) or a full category URL like https://www.douglas.de/de/c/parfum/01. Leave empty for keyword-only search.

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

A Douglas brand code (e.g. 'b0690' Dior) or a full brand URL like https://www.douglas.de/de/b/dior/b0690. Optional.

## `gender` (type: `string`):

Restrict to women's, men's or unisex products. Search mode only: this is sent to the store as a search facet, so it has no effect in URL mode, where a pasted URL carries its own facets.

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

How to order the search results. Search mode only: a pasted URL is walked in the store's own default order, so this has no effect in URL mode.

## `promoFlags` (type: `array`):

Site promotion facets to apply (e.g. 'travelSize' travel size, 'computedNewFlag' new, 'computedLimited' limited edition). Optional. Search mode only: these are sent to the store as search facets, so they have no effect in URL mode.

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

Full Douglas URLs: product (https://www.douglas.de/de/p/...), category (https://www.douglas.de/de/c/...), brand (https://www.douglas.de/de/b/...) or search (https://www.douglas.de/de/search?q=...).

## `priceMin` (type: `number`):

Only return products at or above this price. Applies in BOTH search mode and URL mode: in search mode it is also sent to the store as a price facet, and in URL mode it filters the products found behind a pasted category, brand or search URL. A pasted product URL is always returned, whatever its price.

## `priceMax` (type: `number`):

Only return products at or below this price. Applies in BOTH search mode and URL mode: in search mode it is also sent to the store as a price facet, and in URL mode it filters the products found behind a pasted category, brand or search URL. A pasted product URL is always returned, whatever its price.

## `specialsOnly` (type: `boolean`):

Keep only products that are currently reduced (have a strike-through original price). Applies in BOTH search mode and URL mode, including the products found behind a pasted category, brand or search URL; a pasted product URL is always returned whether or not it is reduced. Douglas does not run a dedicated sale category; a 'special' here is any product whose original/list price is crossed out.

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

Open each product page for the full spec table, ingredients (INCI) and how-to-use text. Off = listing fields only (faster, cheaper).

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

Pull the rating histogram, EAN/GTIN and per-review text (author, date, rating, title, body) for each product.

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

Cap on reviews returned per product (1-200). Ignored unless 'Fetch reviews' is on.

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

Maximum result pages to walk per query/category/URL (0 = unlimited, walk until results are exhausted). One page holds 48 products.

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

Hard cap on the total number of products returned across all queries/URLs.

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

A Germany exit is required. The preset default is the configuration the store serves; changing it can make a run return no results.

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

Optionally send scraped results to apps you already use, via MCP connectors. Authorize a connector once under Apify Settings Integrations, then select it here. A condensed human-readable summary per item is sent; the full record stays in the dataset. Leave empty to skip.

## `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",
  "queries": [
    "parfum"
  ],
  "category": "01",
  "gender": "any",
  "sortBy": "relevance",
  "promoFlags": [],
  "urls": [
    "https://www.douglas.de/de/p/5010207032"
  ],
  "specialsOnly": false,
  "fetchDetails": false,
  "fetchReviews": false,
  "maxReviewsPerProduct": 20,
  "maxPages": 0,
  "maxItems": 100,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "DE"
  },
  "maxNotifyListings": 50
}
```

# 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",
    "queries": [
        "parfum"
    ],
    "category": "01",
    "promoFlags": [],
    "urls": [
        "https://www.douglas.de/de/p/5010207032"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "DE"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/douglas-de").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",
    "queries": ["parfum"],
    "category": "01",
    "promoFlags": [],
    "urls": ["https://www.douglas.de/de/p/5010207032"],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "DE",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/douglas-de").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",
  "queries": [
    "parfum"
  ],
  "category": "01",
  "promoFlags": [],
  "urls": [
    "https://www.douglas.de/de/p/5010207032"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "DE"
  }
}' |
apify call abotapi/douglas-de --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,abotapi/douglas-de"
        }
    }
}

```

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/adF4wdzFYbIUcCDTs/builds/WYGqgqYJuti24WzKi/openapi.json
