# OTTO.de Scraper \[~$0.76/1K💰] | Prices | Products | Repricing (`ahmed_jasarevic/otto-scraper`) Actor

The cheapest verified OTTO.de scraper — ~$0.76 per 1,000 products. Extract prices, compare-at UVP, discount %, ratings, availability, variants and product IDs from Germany's 2nd largest marketplace. Reads OTTO's tilelist JSON (no browser). Built for repricing, Preisüberwachung & market research.

- **URL**: https://apify.com/ahmed\_jasarevic/otto-scraper.md
- **Developed by:** [Ahmed Jasarevic](https://apify.com/ahmed_jasarevic) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.76 / 1,000 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?

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

## Otto Scraper

Scrape **OTTO.de** (Germany's largest online department store) — search results, category listings and product pages — and extract **product title, brand, price + compare-at price, discount %, rating + review count, availability, variants, images and product URL** as clean structured JSON.

Built for **DACH repricers, marketplace sellers, e-commerce analysts and catalog-matching pipelines**. Runs on the Apify platform, so you get API access, scheduling, monitoring and proxy rotation out of the box.

### Why use Otto Scraper?

- **Cheap and fast** — listings are pulled from OTTO's own internal JSON API (`/dundee/tilelist`), not rendered in a browser. No Cloudflare/KPSDK challenge on the listing surface, no wasted compute units.
- **Full pricing picture** — current price, strike-through compare-at price (UVP) and the site's own discount percentage, straight from OTTO's data.
- **Repricer-ready identifiers** — productId, variationId and articleNumber.
- **Variant awareness** — colour/size variant matrix with per-variant IDs and availability.
- **Pagination built in** — walks every result page of a search or category up to your caps, deduplicated by product/variation ID.

### How to use Otto Scraper

1. Open the actor's **Input** tab.
2. Enter one or more **search terms** (e.g. `fernseher`, `kühlschrank`, `ecksofa`), **category URLs** (e.g. `https://www.otto.de/technik/fernseher/`), and/or **product URLs**.
3. Set `maxItems` and `maxPagesPerListing` to bound the run.
4. Click **Start** and download the dataset as JSON, CSV, HTML or Excel.

#### Example input

```json
{
  "searchTerms": ["fernseher"],
  "categoryUrls": ["https://www.otto.de/technik/fernseher/"],
  "maxItems": 100,
  "maxPagesPerListing": 10,
  "sortBy": "topseller",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "proxyType": "ApifyProxy",
    "apifyProxyGroups": []
  }
}
```

### Input

| Field | Type | Description |
| --- | --- | --- |
| `searchTerms` | array | Free-text keywords searched on OTTO.de, paginated automatically. |
| `categoryUrls` | array | OTTO.de category/listing URLs (e.g. `https://www.otto.de/technik/fernseher/`). |
| `productUrls` | array | OTTO.de product page URLs (e.g. `https://www.otto.de/p/<slug>-<id>/`). |
| `maxItems` | integer | Max products returned across all inputs (default 100). Free users are capped at 10. |
| `maxPagesPerListing` | integer | Max result pages per search/category (default 10). Each page holds ~158 tile entries, but only a subset carries the full product payload, so consecutive pages yield fewer new products. |
| `sortBy` | enum | `topseller`, `preisaufsteigend`, `preisabsteigend`, `rabatt`, `neuheit`, `bewertung`. |
| `proxyConfiguration` | object | Native Apify proxy editor. `AUTO` (datacenter) is fine for search/category listings. |

### Output

Each dataset item is a flat product record. Example:

```json
{
  "productId": "C2092091215",
  "variationId": "2092091217",
  "articleNumber": "88622148",
  "name": "Philips 40PFS6050/12 LED-Fernseher (101 cm/40 Zoll, Full HD, Smart-TV)",
  "brand": "Philips",
  "price": 199.99,
  "compareAtPrice": 379.0,
  "discountPercent": 47,
  "isOnSale": true,
  "currency": "EUR",
  "rating": 4.5,
  "reviewCount": 15,
  "availability": "AVAILABLE",
  "availabilityText": "lieferbar - in 1-2 Werktagen bei dir",
  "images": ["https://i.otto.de/i/otto/8baef01a-93f0-52ea-8656-3d122a16d13b?$responsive_ft2$"],
  "productUrl": "https://www.otto.de/p/philips-40pfs6050-12-led-fernseher-101-cm-40-zoll-full-hd-smart-tv-C2092091215/",
  "variants": [{ "variationId": "2092091217", "color": "schwarz", "name": "unknown", "selected": true }],
  "topInfos": [{ "label": "Diagonale", "value": "101 cm/40 Zoll" }],
  "ean": null,
  "gtin": null,
  "sku": null,
  "source": "search:fernseher",
  "scrapedAt": "2026-09-12T08:00:00.000Z"
}
```

You can download the dataset in various formats such as JSON, HTML, CSV, or Excel.

### Data table

| Field | Description |
| --- | --- |
| `productId` / `variationId` / `articleNumber` | OTTO's internal product identifiers |
| `name` / `brand` | Product title and brand |
| `price` / `compareAtPrice` / `discountPercent` / `isOnSale` | Current price, UVP compare-at price, discount % and sale flag (EUR) |
| `rating` / `reviewCount` | Average rating and number of reviews |
| `availability` / `availabilityText` | Stock state (`AVAILABLE`, …) and human-readable text |
| `images` | Array of image URLs |
| `productUrl` | Canonical product page URL |
| `variants` | Colour/size variant matrix with variation IDs |
| `topInfos` | Key product attributes (e.g. screen diagonal, resolution) |
| `ean` / `gtin` / `sku` | Barcode identifiers — best-effort via `productUrls` input (see known limitation) |
| `source` / `scrapedAt` | Which input produced the record and when |

### How much does it cost to scrape OTTO.de?

The actor is a lightweight CheerioCrawler (plain HTTP, no browser) sized at 256 MB, so compute-unit usage is minimal. A typical run of 100 products across a few search/category pages finishes in well under a minute. Product detail-page fetching is not offered — see the known limitation below.

### Tips and advanced options

- **Repricing / price monitoring**: run a search term on a schedule and diff `price` / `compareAtPrice` / `discountPercent` between runs.
- **Bigger crawls**: raise `maxItems` and `maxPagesPerListing`; the actor dedupes by product/variation ID so sponsored repeats never inflate the dataset.

### Technical notes — which data source is used

This actor does **not** render pages in a browser. It uses OTTO's own internal JSON API:

- **Listings (search + category)**: `GET https://www.otto.de/dundee/tilelist?rule=<rule>&sortiertnach=<sort>&o=<offset>` returns the full tile payload as clean JSON — title, brand, price, compare-at price, discount %, rating, review count, availability, images, variants and product URL. The `rule` for a search term is built as `(und.(suchbegriff.<urlencoded-term>).(~.(v.1)))`; for category pages it is extracted from the served HTML (the page embeds the tilelist route). Pagination uses the raw `o` offset and steps by the number of tiles OTTO returns per page (~158). Note that OTTO only fully hydrates a subset of the ~158 tile entries per page — the rest are lightweight product cards without price/rating data and are filtered out, which is why a page can show "158 tiles" while yielding fewer products.
- **Product detail pages (only via the `productUrls` input)**: fetched as HTML and parsed from embedded JSON-LD / devalue state to best-effort EAN/GTIN/SKU. These pages sit behind a KPSDK JS challenge, so they are fetched through the Apify proxy.

### FAQ, disclaimers and support

- **Is this legal?** Scraping publicly available data for your own analysis is generally permitted, but you are responsible for complying with OTTO's Terms of Service and applicable law (including the German `UrhG`/`BDSG` where relevant). This actor is provided as-is.
- **Known limitation**: OTTO serves a JavaScript challenge (KPSDK) on product pages (`/p/...`) that blocks plain-HTTP fetches even through residential proxies. The listing surface (search + category) is unaffected and always returns the full tile payload. This is why the `fetchDetails` option was dropped — `ean`/`gtin`/`sku` fields therefore stay `null` except for the rare `productUrls` page that serves without the challenge. If EAN/GTIN is a hard requirement, a browser-based (Playwright + stealth) detail fetcher is the recommended enhancement — contact us for a custom build.
- **Feedback**: open an issue on the actor's Issues tab, or contact us for custom modifications (e.g. review scraping, incremental price-change tracking, custom output formats).

# Actor input Schema

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

Free-text keywords to search on OTTO.de, e.g. "fernseher", "kühlschrank", "ecksofa". Each term is resolved through OTTO's own search and paginated automatically.

## `categoryUrls` (type: `array`):

OTTO.de category/listing page URLs, e.g. https://www.otto.de/technik/fernseher/ . The actor extracts the listing rule from the served HTML and paginates through OTTO's internal JSON API.

## `productUrls` (type: `array`):

OTTO.de product page URLs, e.g. https://www.otto.de/p/<slug>-<id>/ . Product pages sit behind a JS challenge, so they are fetched through the Apify proxy.

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

Maximum number of products to return across all inputs. Free users are capped at 10.

## `maxPagesPerListing` (type: `integer`):

Maximum number of result pages to walk per search term / category URL (each page holds ~158 tile entries; only a subset carries the full product payload, so consecutive pages yield fewer new products).

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

OTTO's own server-side sort order, applied before pagination.

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

Select proxies to be used by your crawler so the client IP is never exposed. AUTO (datacenter) is fine for search/category listings.

## Actor input object example

```json
{
  "searchTerms": [
    "fernseher"
  ],
  "categoryUrls": [],
  "productUrls": [],
  "maxItems": 100,
  "maxPagesPerListing": 10,
  "sortBy": "topseller",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "proxyType": "ApifyProxy",
    "apifyProxyGroups": []
  }
}
```

# Actor output Schema

## `results` (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 = {
    "searchTerms": [
        "fernseher"
    ],
    "categoryUrls": [],
    "productUrls": [],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "proxyType": "ApifyProxy",
        "apifyProxyGroups": []
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("ahmed_jasarevic/otto-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 = {
    "searchTerms": ["fernseher"],
    "categoryUrls": [],
    "productUrls": [],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "proxyType": "ApifyProxy",
        "apifyProxyGroups": [],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("ahmed_jasarevic/otto-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 '{
  "searchTerms": [
    "fernseher"
  ],
  "categoryUrls": [],
  "productUrls": [],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "proxyType": "ApifyProxy",
    "apifyProxyGroups": []
  }
}' |
apify call ahmed_jasarevic/otto-scraper --silent --output-dataset

```

## MCP server setup

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