# Thalia.de Scraper - Books, Prices, ISBNs & Reviews (`abotapi/thalia-de-scraper`) Actor

Scrape Thalia.de books, eBooks, audiobooks, toys and stationery. Search by keyword or category, browse Schnäppchen deals, or paste product and listing URLs. Get prices, original prices, discounts, ISBN/EAN, author, publisher, format, ratings and full reviews.

- **URL**: https://apify.com/abotapi/thalia-de-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** E-commerce, Automation, Developer tools
- **Features:** Limited permissions
- **Stats:** 6 total users, 3 monthly users, 86.5% runs succeeded, 1 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/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

## Thalia Scraper: Books, eBooks, Audiobooks & Media with Prices, Discounts & Reviews

Thalia Scraper turns Thalia (thalia.de), Germany's books and media retailer, into structured product data. Every record carries the current price and, on a discounted item, the exact strike through original price with the computed discount amount and percentage, plus ISBN and EAN, author and publisher, format, the product's own technical specifications, and Thalia's own customer review history with the star rating breakdown. Search by keyword and category with the site's real filters, browse the Schnäppchen sale collection on its own, or paste product, category, search, or sale page links directly, then export the results to JSON, CSV, or Excel, or pull them straight into your app through the API.

### Why This Scraper?

- **Real was-price and discount, never fabricated.** On a Schnäppchen (sale) item, both the current price and the strike through original price come back, plus the discount amount and percentage computed from them; a full price item simply carries no discount fields.
- **A dedicated Specials filter.** `specialsOnly` maps to Thalia's own price reduction facet, store wide or scoped to a keyword and/or category, the same collection shown at thalia.de/themenwelten/sale/.
- **Rich bibliographic identity.** ISBN and EAN, the full author list, publisher, and the exact format/binding (Taschenbuch, gebundene Ausgabe, ePUB, MP3, Hoerbuch-Download, and more).
- **Full technical specifications as clean key value pairs.** Each product's own "Produktdetails" table (pages, language, release date, dimensions, weight; varies per category) comes back structured, plus the full breadcrumb category path.
- **Format variant matrix.** Other editions of the same title, for example the Hardcover and eBook of one novel, come back linked to each other.
- **Complete native review history.** Overall rating, review count, a full 1 to 5 star rating breakdown, and individual reviews (20 per product by default; set Max reviews per product up to 1000, or 0 for the 1000 maximum) with author, date, rating, format, title, and body text, all from Thalia's own review system.
- **Two ways in.** Keyword and/or category search with the site's real filters (format, price range, availability, specials, sort), or paste any product, category, search, or sale link and let pagination continue automatically.

### Use Cases

- **Price and discount tracking:** monitor a title, category, or the whole Schnäppchen collection for price drops and build a was-price history over time.
- **Bibliographic data feeds:** pull ISBN, EAN, author, publisher, and format details into a catalog, metadata store, or library system.
- **Review and rating research:** collect star distributions and full review text to study reader sentiment on specific titles or formats.
- **Competitive pricing analysis:** compare current and discounted prices, formats, and availability against other book and media retailers.
- **Content and merchandising tools:** build bestseller trackers, format comparison pages, or category based reading lists from search and category browses.

### Data You Get

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

| Field | Example |
| --- | --- |
| `productId` | `A2048591` |
| `title` | `Sample Title` |
| `authors` | `["Sample Author"]` |
| `url` | `https://www.thalia.de/shop/home/artikeldetails/A2048591` |
| `price` / `currency` | `9.99`, `EUR` |
| `originalPrice` | `14.99` |
| `discountAmount` / `discountPercent` | `5.0`, `33.4` |
| `isOnSpecial` | `true` |
| `promoLabel` | `- 33%` |
| `format` | `Buch (Taschenbuch)` |
| `categoryPath` | `["Buch (dtsch)", "Buecher", "Romane & Erzaehlungen"]` |
| `availabilityText` / `availabilityStatus` | `Sofort lieferbar`, `gruen` |
| `rating` / `reviewCount` | `4.3`, `128` |
| `image` | `https://images.thalia.media/-/BF2000-2000/sample/sample-cover.jpeg` |
| `isbn` / `ean` | `978-3-000-00000-0`, `9783000000000` |
| `specifications` *(fetchDetails)* | `{"Verlag": "Sample Verlag", "Seitenzahl": "288", "Erscheinungsdatum": "12.03.2024"}` |
| `variants` *(fetchDetails)* | `[{"format": "Hardcover", "url": "https://www.thalia.de/shop/home/artikeldetails/A2048592"}]` |
| `images` *(fetchDetails)* | `["https://images.thalia.media/-/BF2000-2000/sample/sample-1.jpeg"]` |
| `description` *(fetchDetails)* | `Sample blurb text describing the book.` |
| `reviews.averageRating` / `reviews.totalReviewCount` | `4.3`, `128` |
| `reviews.ratingDistribution` | `[{"rating": 5, "count": 75}, {"rating": 4, "count": 30}, {"rating": 3, "count": 13}]` |
| `reviews.items[]` | `[{"reviewId": "9000001", "author": "Sample User", "authorLocation": "aus Wien", "rating": 5.0, "date": "03.02.2025", "title": "Sample title", "body": "Sample review text."}]` |

Fields marked *(fetchDetails)* only appear when Fetch full product detail is on for a search or category result; a pasted single product link always returns the full detail fields, and carries the detail-enrichment surcharge regardless of that toggle (except in incremental mode, where an unchanged product link is skipped, or returned with Emit unchanged products, without the enrichment charge). The review breakdown and per review list appear whenever Fetch reviews is on, which is the default; a product with no reviews simply returns an empty list. On a full price item, `originalPrice`, `discountAmount`, `discountPercent`, and `promoLabel` are `null`, derived only from the site's own strike through price and sale badge, never estimated.

### How to Use

1. Pick a **mode**: `search` (keyword and/or category with real filters) or `url` (paste product, category, search, or sale links).
2. For search mode, set a keyword and/or category, and switch on **Specials only** to browse just the Schnäppchen sale facet.
3. Turn on **Fetch full product detail** and/or **Fetch reviews** for the enrichment 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": "krimi",
  "maxItems": 20
}
```

**Search narrowed by category, format, and price, with specials:**

```json
{
  "mode": "search",
  "searchTerm": "krimi",
  "category": "3",
  "einband": ["Taschenbuch"],
  "minPrice": 5,
  "maxPrice": 20,
  "specialsOnly": true,
  "sortBy": "sfpa",
  "maxItems": 30
}
```

**Category browse with full detail and review enrichment:**

```json
{
  "mode": "search",
  "category": "3",
  "fetchDetails": true,
  "fetchReviews": true,
  "maxReviewsPerProduct": 50,
  "maxItems": 50
}
```

**Paste product, category, search, or sale links:**

```json
{
  "mode": "url",
  "urls": [
    "https://www.thalia.de/shop/home/artikeldetails/A2048591",
    "https://www.thalia.de/kategorie/romane-erzaehlungen-3/",
    "https://www.thalia.de/themenwelten/sale/"
  ],
  "maxItems": 30
}
```

#### Output tabs

The run's Output tab offers **Products** (overview), **Reviews** (rating breakdown and review items), **Product details** (ISBN, publisher, description, specifications, variants, media) and **Changes (incremental)** (`changeType` and `changedFields`, for incremental runs).

#### Incremental mode (recurring monitoring)

Turn on `incrementalMode` to run the same search or links repeatedly (for example daily) and receive only what changed. Each returned row gets `changeType` (`NEW`, `UPDATED`, `REAPPEARED` or `EXPIRED`), `changedFields` (empty for `NEW`), `firstSeenAt` and `lastSeenAt`. Products are tracked by `productId`.

- Compared: title, authors, price, original price and discount, sale flag, badges and promo label, format, category path, availability.
- Never compared (still returned): rating, review count, image link, product link, reviews. Use `ignoreFieldsForChanges` to exclude more.
- For search, category and sale links the comparison uses the search-result data of each product; fields that only exist after product details (ISBN, publisher, description) do not trigger `UPDATED`. A pasted product link always returns the full product record, and that whole record (details included) is compared, so a changed description or ISBN there can produce `UPDATED`.
- Unchanged products are skipped before enrichment: they are not pushed, so they are not billed as results and never trigger the detail enrichment event. Billing is otherwise unchanged: every returned row is a result, and the enrichment event applies once per returned row whose detail or review fetch completed, and it is charged one unit per product immediately after that row is saved, so an interrupted run is only billed for rows already delivered.
- A product that was in an earlier run but is no longer listed is returned once with `changeType` `EXPIRED` (switch `emitExpired` off to skip these). A product that comes back later is returned as `REAPPEARED`. `EXPIRED` rows are only produced when a run walked its whole search to the end; capped, partial or resumed runs never produce them.
- Every `UNCHANGED` row (`emitUnchanged`) and every `EXPIRED` row that is returned counts as a returned result and is billed under the per-result event like any other row; neither carries the enrichment charge.

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/thalia-de-scraper").call(run_input={"mode": "search", "searchTerm": "krimi", "maxItems": 20})
for product in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(product["title"], 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/thalia-de-scraper').call({ mode: 'search', searchTerm: 'krimi', 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.

#### Caps and filter scope

With the URLs field left untouched, URL mode scrapes one example link per shape (a category, a search, the sale page and a product), split under your Max products cap; replace or clear them to scrape your own links.

**Max products total** (`maxItems`) is the hard ceiling across every search or every pasted link in one run; in URL mode it is split evenly across the pasted links. **Max pages per search** (`maxPages`) caps pages before that (the actor never fetches more than 1000 pages; empty means as many as needed up to Max products). Max products defaults to 20 when left empty; a value of 0 means the most the actor allows, 100000 products per run, which is also the largest accepted value. Max reviews per product accepts up to 1000 (0 means 1000). Larger Max pages per search values (limit 1000) are lowered to 1000 with a warning in the run log. In URL mode, Specials only, Format/binding, price range, availability, and Sort order are not search only filters: pasting a category, search, or sale link rebuilds that listing request through your filter values, so the same filtered result comes back; filters set in the pasted link's own query string are not kept, set them in the input instead. The one exception is a pasted product link, which always returns that single product in full, with the enrichment surcharge applied regardless of any filter or the detail/review toggles (except in incremental mode, where an unchanged product link is skipped, or returned with Emit unchanged products, without that surcharge).

### 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 record, not the full JSON. Each item becomes one entry with a title and its key fields flattened to plain text. The complete record always stays in the Apify dataset.

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

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

### Input Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `mode` | string | `search` | `search` (keyword/category with filters) or `url` (paste links). |
| `searchTerm` | string | (none; prefill `krimi`) | Free text keyword, title, author, or ISBN for search mode. |
| `category` | string | (none) | A category id, e.g. `3`. Narrows a keyword search or browses a whole category alone. |
| `urls` | array | one example per shape (category, search, sale, product) | Product, category, search, or sale links for URL mode. |
| `specialsOnly` | boolean | `false` | Only Schnäppchen (sale) items, a real strike through price and discount. |
| `einband` | array | (none; prefill empty) | Only these exact binding/format values, e.g. `Taschenbuch`, `ePUB 3`. |
| `minPrice` | integer | (none) | Minimum price in EUR. |
| `maxPrice` | integer | (none) | Maximum price in EUR. |
| `availability` | string | (empty: any) | `GL` (lieferbar), `SL` (sofort lieferbar), or `VB` (vorbestellbar). |
| `sortBy` | string | `sfmd` | `sfmd` (best match), `sfpa`/`sfpd` (price asc/desc), `sfea`/`sfed` (release date asc/desc), `sfsd` (top rated). |
| `fetchDetails` | boolean | `false` | Adds ISBN/EAN, full author list, publisher, description, flattened specifications, breadcrumb path, media gallery, and the format variant matrix. |
| `fetchReviews` | boolean | `true` | Fetches each product's full review history. |
| `maxReviewsPerProduct` | integer | `20` | Cap on reviews per product; empty uses 20, 0 means the limit of 1000; maximum 1000. |
| `maxPages` | integer | (none) | Cap on result pages per search/URL entry; empty or 0 means the limit of 1000; larger values are lowered to 1000 with a warning. |
| `maxItems` | integer | `20` | Cap on total products; empty uses 20, 0 means the limit of 100000; maximum 100000. |
| `proxy` | object | Apify default connection | Proxy & connection configuration. Your own proxy URLs are used as given. |
| `resumeFromRunId` | string | (none) | Run ID of an earlier unfinished run of this actor; products it already returned are not returned (or billed) again. |
| `incrementalMode` | boolean | `false` | Recurring monitoring: the first run returns every match as `NEW`; later runs return only `NEW` and `UPDATED` products. Unchanged products are not returned, not enriched and not billed. |
| `stateKey` | string | (derived) | Optional name for the monitoring campaign; by default derived from the search/URL and filter setup. |
| `emitUnchanged` | boolean | `false` | Incremental mode only: also return unchanged products as plain rows (no reviews fetch, no enrichment charge). Each returned `UNCHANGED` row counts as a returned result and is billed under the per-result event. |
| `emitExpired` | boolean | `true` | Incremental mode only: when a run walks its whole search to the end, return products seen earlier that are no longer listed, with `changeType` `EXPIRED` (no enrichment charge; each returned `EXPIRED` row counts as a returned result under the per-result event). Never emitted for capped, partial or resumed runs. |
| `ignoreFieldsForChanges` | array | (none) | Field names (dotted paths for nested ones) excluded from change detection, still returned in the row. `rating`, `reviewCount`, `image` and `url` are always excluded. |
| `mcpConnectors` | array | (none) | Optional MCP connectors to also receive results. |
| `notionParentPageUrl` | string | (none) | Required only when a Notion connector is selected. |
| `maxNotifyListings` | integer | `50` | Cap on items sent to each connector. |

### Output Example

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

```json
{
  "productId": "A2048591",
  "title": "Sample Title",
  "authors": ["Sample Author"],
  "url": "https://www.thalia.de/shop/home/artikeldetails/A2048591",
  "price": 4.99,
  "currency": "EUR",
  "originalPrice": 9.99,
  "discountAmount": 5.0,
  "discountPercent": 50.1,
  "isOnSpecial": true,
  "promoLabel": "- 50%",
  "format": "eBook (ePUB 3)",
  "categoryPath": ["Ebooks", "eBooks", "Krimis & Thriller"],
  "availabilityText": "Sofort per Download lieferbar",
  "availabilityStatus": "gruen",
  "rating": 4.3,
  "reviewCount": 128,
  "isbn": "978-3-000-00000-0",
  "specifications": {
    "Format": "ePUB 3",
    "Verlag": "Sample Verlag",
    "Seitenzahl": "288 (Printausgabe)",
    "Erscheinungsdatum": "12.03.2024"
  },
  "variants": [
    { "format": "Hardcover", "url": "https://www.thalia.de/shop/home/artikeldetails/A2048592" },
    { "format": "Taschenbuch", "url": "https://www.thalia.de/shop/home/artikeldetails/A2048593" }
  ],
  "images": ["https://images.thalia.media/-/BF2000-2000/sample/sample-cover.jpeg"],
  "reviews": {
    "averageRating": 4.3,
    "totalReviewCount": 128,
    "ratingDistribution": [
      { "rating": 5, "count": 75 },
      { "rating": 4, "count": 30 },
      { "rating": 3, "count": 13 },
      { "rating": 2, "count": 4 },
      { "rating": 1, "count": 6 }
    ],
    "items": [
      {
        "reviewId": "9000001",
        "author": "Sample User",
        "authorLocation": "aus Wien",
        "rating": 5.0,
        "date": "03.02.2025",
        "format": "eBook (ePUB 3)",
        "title": "Sample review title",
        "body": "Sample review text describing the reading experience."
      }
    ]
  }
}
```

### Plan Requirement

Thalia refuses most datacenter connections, and residential exits are accepted only on some attempts, so a run starts on the default Apify connection, moves to residential when your plan includes it, and falls back to a backup connection when the others are refused. No connection is guaranteed to be accepted: expect occasional slow or failed runs, and retry a failed run a few minutes later. Apify free plans do not include residential proxies. If a run logs "The selected connection is unavailable to this run", it continues on the backup connection and some pages may still be unavailable. To use your own proxies, add custom proxy URLs under **Proxy & connection**.

### FAQ

#### How much does it cost?

You pay a small start fee per run, plus a fee per product returned, plus a second, smaller surcharge for detail and review enrichment billed once per product whenever full product detail and/or the review history was actually fetched for it. A search or category result carries this by default because Fetch reviews defaults to on; turn it off if you only need the base catalog fields. A pasted single product link always carries the full detail fields and this surcharge, whatever the toggles say, except that in incremental mode an unchanged product link is skipped (or returned with Emit unchanged products) without the surcharge. The **Pricing** tab shows the current rates, and **Max products total** caps the size, and therefore the cost, of any run.

#### Is it legal to scrape Thalia?

This actor collects only publicly available product and review data. You are responsible for how you use it: follow Thalia's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Prices, availability, and bibliographic facts like ISBN are generally facts, but review text and product images may carry their own rights.

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

Yes. Turn on `incrementalMode` (see Incremental mode above) and schedule the actor from the **Schedules** tab. The first run returns every match as `NEW`; later runs return only `NEW` and `UPDATED` products with `changedFields`, and unchanged products are neither returned nor billed. `ignoreFieldsForChanges` excludes fields you do not care about.

#### What's the most common point of confusion?

That the filters (Specials only, Format/binding, price range, availability, sort) are not search only. In URL mode they still apply: pasting a category, search, or sale link rebuilds that listing request through your filter values, so you get the same filtered result. The one exception is a pasted product link, which always returns that single product regardless of any filter you set.

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

If the site refuses every result or product page in a run, the run stops with a clear error instead of quietly finishing with zero items, so an empty result from a real search is never confused with a run the site blocked outright. Run it again in a few minutes, or supply your own proxy URLs under **Proxy & connection**. The error text says whether the proxy connection was refused for your account (a paid plan is required), whether residential access was unavailable, or whether the pages simply failed to load. If only some pages were blocked, you still get what was found.

#### 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 a summary of each result 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/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/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/rossmann-de-scraper"><b>ROSSMANN.de Scraper</b></a><br>Scrape ROSSMANN (rossmann.de) drugstore products: current + original strike-through price...</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/thalia-de-scraper/issues/open). We usually reply within hours.
- 🛠️ **Need another site, extra fields or a private build?** Email <contact@abotapi.com>.
- ⭐ **Enjoying it?** A quick review on the actor page helps other users find it.

# Actor input Schema

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

'search' finds products by keyword and/or category with Thalia's own filters, including Schnäppchen (sale) items. 'url' scrapes any product, category, search-result, or sale page URL you paste, walking pagination forward automatically.

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

Free-text keyword, title, author, or stichwort, e.g. 'krimi' or 'fantasy roman'. Combine with Category below to narrow, or leave empty and set only Category (and/or Specials only) to browse a whole department.

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

A Thalia category id, e.g. '3' (Romane & Erzählungen) or '4893' (eBooks). Find it as the trailing number in a category page URL: thalia.de/kategorie/<name>-<id>/. Narrows a keyword search, or browses the whole category when Search keyword is left empty. Search mode only: URL mode reads this from the link you paste.

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

Paste one or more Thalia product pages (/shop/home/artikeldetails/<id>), category pages (/kategorie/<name>-<id>/), search-result pages (/suche?sq=<term>), or the sale page (/themenwelten/sale/). Pagination continues forward automatically from any page number already in the URL. The keyword and category fields are ignored here. The fields in the Filters and sort section still apply to a pasted listing, category, search or specials URL.

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

Only return items currently on sale (Thalia's own 'Schnäppchen' price-reduction facet): a real strike-through original price + discount. Combine with a keyword and/or category, or leave those empty to browse all current sale items (mirrors thalia.de/themenwelten/sale/). Applies in BOTH search mode and URL mode: the listing request is rebuilt for a pasted listing, category, search or specials URL, so the filter is sent with it. It does not narrow a pasted product URL, which returns that one product.

## `einband` (type: `array`):

Only return this exact binding/format, e.g. 'Taschenbuch', 'gebundene Ausgabe', 'ePUB 3', 'MP3', 'Hörbuch-Download'. Match the exact value as shown on the site (visible in each result's 'format' field). Leave empty for every format. Applies in BOTH search mode and URL mode: the listing request is rebuilt for a pasted listing, category, search or specials URL, so the filter is sent with it. It does not narrow a pasted product URL, which returns that one product.

## `minPrice` (type: `integer`):

Only return products priced at or above this amount. Applies in BOTH search mode and URL mode: the listing request is rebuilt for a pasted listing, category, search or specials URL, so the filter is sent with it. It does not narrow a pasted product URL, which returns that one product.

## `maxPrice` (type: `integer`):

Only return products priced at or below this amount. Applies in BOTH search mode and URL mode: the listing request is rebuilt for a pasted listing, category, search or specials URL, so the filter is sent with it. It does not narrow a pasted product URL, which returns that one product.

## `availability` (type: `string`):

Narrow by delivery/availability status. Applies in BOTH search mode and URL mode: the listing request is rebuilt for a pasted listing, category, search or specials URL, so the filter is sent with it. It does not narrow a pasted product URL, which returns that one product.

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

Order of the returned results. Applies in BOTH search mode and URL mode: the listing request is rebuilt for a pasted listing, category, search or specials URL, so the filter is sent with it. It does not narrow a pasted product URL, which returns that one product.

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

Adds ISBN/EAN, full author list, publisher, description, flattened technical specifications (pages, language, release date, dimensions — varies per product), full breadcrumb category path, media gallery, and the format-variant matrix (e.g. Hardcover/Taschenbuch/eBook siblings of the same title). Current price, was-price/discount and rating are already included without this toggle on search/category results.

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

Fetch each product's full review history: overall rating, review count, 1-5 star 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. Leave empty for 20. A value of 0 means the maximum the actor allows, 1000 reviews per product; the largest accepted value is 1000.

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

Stop after this many result pages per search/category/URL entry. Leave empty for as many pages as needed (bounded by Max products total). A value of 0 also means as many as needed. The actor never fetches more than 1000 pages per entry; larger values are lowered to 1000 with a warning in the run log.

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

Hard cap on total products returned across every search/category/URL entry. Leave empty for 20. A value of 0 means the maximum the actor allows, 100000 products per run; the largest accepted value is 100000.

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

Thalia refuses most datacenter connections, so runs start on the default Apify connection and move to residential when your plan includes it. No connection is guaranteed; failed runs can be retried later.

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

Optional. Run ID of an earlier run of this actor that did not finish. Products it already returned are not returned (or billed) again. Leave empty for a normal run.

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

Turn this on for daily or recurring monitoring. The first run returns all matching products as NEW. Later runs compare every product against the last run (title, authors, price, original price and discount, sale flag, badges and promo label, format, category path, availability) and return only NEW and UPDATED products; an UPDATED row's changedFields names exactly what moved. Ratings, review counts, the image link and the product link are never compared, because they change without a real product change. Unchanged products are not returned, not enriched and not billed. For search, category and sale links the comparison uses the search result data, so fields only present after the optional product details (ISBN, publisher, description) do not trigger UPDATED; a pasted product link returns the full product record and that whole record (details included) is compared. State is kept separately for each search/URL/filter setup; use State key to name or share a campaign. Products that disappear are returned once as EXPIRED (see 'Return products that disappeared') and a product that comes back is returned as REAPPEARED.

## `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 derive a key automatically from the search/URL and filter settings.

## `emitUnchanged` (type: `boolean`):

Incremental mode only. Return products that did not change, as plain rows (no reviews fetch, no enrichment charge). Each returned UNCHANGED row counts as a returned result and is billed under the per-result event. Off by default: unchanged products are skipped.

## `emitExpired` (type: `boolean`):

Incremental mode only. When a run walks its whole search to the end, return products seen earlier that are no longer listed, with changeType EXPIRED. Each returned EXPIRED row counts as a returned result and is billed under the per-result event (no enrichment charge). Never emitted for capped, partial or resumed runs.

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

Optional. Field names to exclude from UPDATED change detection, in addition to the built-in handling (rating, reviewCount, image and url never trigger UPDATED). Nested fields can be given as dotted paths. Ignored fields are still returned in the row.

## `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": "krimi",
  "urls": [
    "https://www.thalia.de/kategorie/romane-erzaehlungen-3/",
    "https://www.thalia.de/suche?sq=krimi",
    "https://www.thalia.de/themenwelten/sale/",
    "https://www.thalia.de/shop/home/artikeldetails/A2048591"
  ],
  "specialsOnly": false,
  "einband": [],
  "availability": "",
  "sortBy": "sfmd",
  "fetchDetails": false,
  "fetchReviews": true,
  "maxReviewsPerProduct": 20,
  "maxItems": 20,
  "proxy": {
    "useApifyProxy": true
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": true,
  "ignoreFieldsForChanges": [],
  "maxNotifyListings": 50
}
```

# Actor output Schema

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

No description

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

No description

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

No description

## `changes` (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": "krimi",
    "urls": [
        "https://www.thalia.de/kategorie/romane-erzaehlungen-3/",
        "https://www.thalia.de/suche?sq=krimi",
        "https://www.thalia.de/themenwelten/sale/",
        "https://www.thalia.de/shop/home/artikeldetails/A2048591"
    ],
    "einband": [],
    "proxy": {
        "useApifyProxy": true
    },
    "incrementalMode": false,
    "ignoreFieldsForChanges": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/thalia-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": "krimi",
    "urls": [
        "https://www.thalia.de/kategorie/romane-erzaehlungen-3/",
        "https://www.thalia.de/suche?sq=krimi",
        "https://www.thalia.de/themenwelten/sale/",
        "https://www.thalia.de/shop/home/artikeldetails/A2048591",
    ],
    "einband": [],
    "proxy": { "useApifyProxy": True },
    "incrementalMode": False,
    "ignoreFieldsForChanges": [],
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/thalia-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": "krimi",
  "urls": [
    "https://www.thalia.de/kategorie/romane-erzaehlungen-3/",
    "https://www.thalia.de/suche?sq=krimi",
    "https://www.thalia.de/themenwelten/sale/",
    "https://www.thalia.de/shop/home/artikeldetails/A2048591"
  ],
  "einband": [],
  "proxy": {
    "useApifyProxy": true
  },
  "incrementalMode": false,
  "ignoreFieldsForChanges": []
}' |
apify call abotapi/thalia-de-scraper --silent --output-dataset

```

## MCP server setup

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