OTTO.de Scraper - Products, Prices, Variants & Reviews
Pricing
from $1.20 / 1,000 product results
OTTO.de Scraper - Products, Prices, Variants & Reviews
Scrape OTTO.de products by search or URL. Extract prices, discounts, variants, availability, brand, seller, EAN/GTIN, specifications, categories, and customer reviews across fashion, furniture, electronics, and homeware.
Pricing
from $1.20 / 1,000 product results
Rating
0.0
(0)
Developer
Abot API
Maintained by CommunityActor stats
1
Bookmarked
9
Total users
4
Monthly active users
5 days ago
Last modified
Categories
Share
OTTO Scraper: Prices, Discounts, Variants and Reviews
OTTO Scraper turns OTTO into structured product data. Search by keyword or category, or paste product and listing links directly, and get the current price, the strike-through original price with the site's discount percent when a product is marked down, colour variants with their own variant id and colour code, brand, seller identity when detail is fetched, and customer reviews for fashion, furniture, technik and household appliances. Export the results to JSON, CSV or Excel, or pull them straight into your app through the API.
Why This Scraper?
- Price and discount, already structured. When a product is marked down, the current price, the original price, the site's discount percent and its own promo label all come from one listing fetch, at no extra cost.
- Colour variant matrix. Every colour option a product offers is returned with its own variant id and colour code.
- Specials, first class.
specialsOnlynarrows any search to only products currently showing a reduced price, andsortBy: DISCOUNTsurfaces the biggest reductions first, both driven by the site's own live discount signal. - Seller transparency. With Fetch full product detail on, the seller is captured, distinguishing OTTO itself from third-party marketplace partners, with the partner's own identifier.
- Native review history. Overall rating, review count, and individual reviews (up to Max reviews per product, 10 by default, or every review the page renders with it set to 0) with author, location, date, star rating, verified-purchase flag, body text and helpful-vote counts, sourced directly from the product page.
- Rich detail on demand. Full breadcrumb category path, flattened technical specifications (these vary per product category), EAN/GTIN/SKU, the full image gallery and the return policy.
- Built for schedules. Incremental mode returns only new and changed products on recurring runs, Resume continues one interrupted run without double charges, and a long run survives an Apify platform interruption through its own checkpoint.
Use Cases
- Deal hunters and price trackers: monitor a category or brand for the current price, the original price and the discount percent so you can time a purchase or a repricing decision.
- Marketplace and assortment research: compare OTTO's own listings against third-party marketplace partners selling the same or similar products, and see who actually fulfils each one.
- Review and reputation analysis: build a dataset of native customer reviews per product or category for sentiment, quality, or complaint analysis.
- Catalogue monitoring: track new, updated and removed products in a search or category on a recurring schedule.
- Product data enrichment: pull structured specs, EAN/GTIN/SKU, images and return policy into your own catalogue or PIM system.
Data You Get
| Field | Example |
|---|---|
productId / variationId / articleNumber | "S0AB12CD" / "S0AB12CD4XYZ" / "S0AB12CDKIE" |
name | "Sample Brand Ecksofa, Sofa L-Form, mit Schlaffunktion" |
brand | "Sample Brand" |
category / breadcrumb | "Ecksofas" / [{"name": "Möbel", "url": "..."}, {"name": "Sofas", "url": "..."}, {"name": "Ecksofas", "url": "..."}] |
url | "https://www.otto.de/p/sample-ecksofa-S0AB12CD/" |
price / currency | 799.00 / "EUR" |
originalPrice / discountPercent / promoLabel | 1599.00 / 50 / "-50%" |
isOnSpecial | true |
availability / availabilityText | "AVAILABLE" / "lieferbar in 5 Wochen" |
rating / reviewCount | 4.5 / 26 |
link / priceCurrency / aggregateRating | Aliases of url / currency / {ratingValue, reviewCount}, for drop-in use with other OTTO scrapers |
variants[] | [{"variationId": "S0AB12CD4XYZ", "color": "Khaki", "hexCode": "BEB2A9", "selected": true}] |
image / images[] | "https://sample.example/img1.jpg" / ["https://sample.example/img1.jpg", "https://sample.example/img2.jpg"] |
ean / gtin / sku | "4068181820000" / "4068181820000" / "S0AB12CD4XYZ" |
specs | {"Höhe": "85 cm", "Breite": "289 cm", "Polsterung": "Schaumstoff", "Design": "Modern"} (flattened, fields vary per category) |
seller | {"name": "Sample Home Brands GmbH", "partnerId": "1000000", "sellerType": "MARKETPLACE_PARTNER"} |
returnPolicy | {"days": 30, "freeReturn": true} |
reviews.averageRating / reviews.reviewCount | 4.5 / 26 |
reviews.items[] | [{"reviewId": "sample-uuid", "author": "Sample R.", "location": "Sample City", "date": "11.04.2026", "rating": 5, "verifiedPurchase": true, "title": null, "body": "Sample review text.", "helpfulVotes": 5, "helpfulVotesTotal": 5, "variantPurchased": "Farbe: Khaki", "seller": "Sample Home Brands GmbH"}] |
changeType / changedFields / firstSeenAt / lastSeenAt | "UPDATED" / ["price"] / "2026-08-01T00:00:00Z" / "2026-08-17T00:00:00Z" (incremental mode only) |
Sample shape: values above are illustrative placeholders, not from a live product.
category, breadcrumb, specs, seller, ean, gtin, sku and returnPolicy are only populated when full product detail is fetched, either because you turned on Fetch full product detail, or because the record came from a pasted product URL, which always fetches its own detail page.
How to Use
- Pick a mode:
search(keyword or category name) orurl(paste product, search or category links). - Fill in the field for that mode, and switch on the narrowing filters or detail toggles you need.
- Set Max products total to control run size and cost, then click Start.
- Download the dataset as JSON, CSV or Excel, or read it through the API.
Keyword search, first page, with reviews (the defaults):
{"mode": "search","searchTerm": "ecksofa","maxItems": 20}
Category-style search, filtered to specials and sorted by biggest discount:
{"mode": "search","searchTerm": "sofas","specialsOnly": true,"sortBy": "DISCOUNT","maxItems": 50,"maxPages": 5}
Full product detail and reviews for a pasted product link:
{"mode": "url","urls": ["https://www.otto.de/p/sample-ecksofa-S0AB12CD/"],"fetchDetails": true,"fetchReviews": true,"maxReviewsPerProduct": 0}
Paste a listing link and keep paginating forward:
{"mode": "url","urls": ["https://www.otto.de/suche/ecksofa/"],"maxItems": 100,"maxPages": 10}
Run it from your code
Python:
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run = client.actor("abotapi/otto-de-scraper").call(run_input={"mode": "search", "searchTerm": "ecksofa", "maxItems": 20})for product in client.dataset(run["defaultDatasetId"]).iterate_items():print(product["name"], product["price"], product["currency"])
JavaScript:
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });const run = await client.actor('abotapi/otto-de-scraper').call({ mode: 'search', searchTerm: 'ecksofa', 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.
Full-catalogue walk and Max products
Max pages per search (maxPages) is 0 (no explicit limit, hard-capped at 1000 pages per entry) by default: a run walks every result page for a search, category or pasted listing URL until the site's own last page, Max products total (maxItems), or a page that adds no new products, whichever comes first. Set maxPages to a positive number only if you want to cap the number of page fetches directly.
Resume and recurring updates
- Resume (
resumeFromRunId) continues one interrupted run: paste its run or dataset id and the actor skips every product already collected there, so this run only returns new products, a delta, instead of a full re-scrape. - Incremental mode (
incrementalMode) is for scheduled runs over the same search or URL set. Each product is classifiedNEW,UPDATED(withchangedFields), orUNCHANGED(suppressed and not billed unlessemitUnchangedis on).EXPIREDdetection only runs at all whenemitExpiredis on, and only produces rows on a run that fully scanned the tracked search or URL set: not capped bymaxItemsormaxPages, not usingresumeFromRunId, no failed page along the way, no pasted URL that starts at an offset (&o=) or carries apreis=price range (those are partial scopes), and at least one product scanned; turn onemitExpiredto have those rows returned and billed.REAPPEAREDonly appears for a product that an earlier complete-scan run withemitExpiredon had already markedEXPIREDand that has now been found again.stateKeynames or shares the stored state; leave it empty and the actor derives one automatically from the search and detail settings. What is compared: every returned field of the product, including nested variants and reviews; an integral price (25 and 25.0) is one value. Upgrade note: a product tracked before this release is not re-reported asUPDATEDjust because it now carries thelink,priceCurrencyandaggregateRatingaliases, anonymous reviews now have their location and date filled in, ormaxReviewsPerProductabove 12 now appends more reviews after the ones already stored; a genuine review edit, a removed or reordered review, or any other change still counts asUPDATED. Nothing is excluded by default.ignoreFieldsForChangeslists top-level field names to leave out of change detection andchangedFields; they stay in the output, and editing the list never causes a one-time wave of UPDATED rows. With incremental mode off, output is exactly as before. - A long run also checkpoints its own progress: if it is interrupted by an Apify platform migration, or resumed with the Resurrect button, it picks up where it left off with no duplicate products and no double charges.
URL mode: supported URL shapes
| Shape | Example | Notes |
|---|---|---|
| Keyword search | https://www.otto.de/suche/ecksofa/ | Walks all pages. |
| Category (nested) | https://www.otto.de/moebel/sofas/ecksofas/ | |
| Category (parent) | https://www.otto.de/moebel/sofas/ | |
| Category (other department) | https://www.otto.de/technik/fernseher/ | The site's internal taxonomy differs from the path; resolved from the page itself. |
Search with suchbegriff | https://www.otto.de/suche/laptop/?suchbegriff=laptop | |
| Department hub | https://www.otto.de/moebel/ | Top-level department page. Probed live: it embeds the same product listing route as a category and returns products (120 per page), so it is scraped like a category. |
| Sorted search / category | .../suche/ecksofa/?sortiertnach=preis-aufsteigend | The pasted sort is used unless you set Sort by explicitly. |
| Filtered category | .../moebel/sofas/ecksofas/?marke=sit-more&preis=100-500 | marke is applied by the site; the price range is applied by the actor, because the site drops it. |
| Listing JSON route | https://www.otto.de/dundee/tilelist?rule=...&sortiertnach=topseller | Pasted as-is. &o=120 starts at that offset: a partial scope (a warning is logged). |
| Apex host | https://otto.de/suche/ecksofa | Normalised to www.otto.de. |
| Product page | https://www.otto.de/p/<slug>-<id>/ | Fetches full detail; accepted but not part of the default examples. |
/shoppages/ and /marken/ pages are not product listings: they are skipped with a warning per URL.
Behaviour change: with the URL field left untouched (or omitted), url mode now scrapes one example of each listing shape above instead of one search page. Pass your own urls to control this; maxItems (default 20) is shared across them.
Send results into your apps (MCP connectors)
Optionally pipe the scraped results into the apps you already use, through Model Context Protocol (MCP) connectors. This is an extra delivery step after the scrape: the Apify dataset itself 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.
- Authorize a connector once under Apify, Settings, Integrations (Notion, Linear, Airtable, or Apify).
- Select it in the "Pipe results into your apps" input field. If the picker is empty, you have not authorized a connector yet.
- For Notion, also set
notionParentPageUrlto the page where items should be created.
The connection is mediated by Apify's own 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 or category) or url (paste product, search, or category links). |
searchTerm | string | none (prefill: "ecksofa") | Keyword or category name to search (search mode only). |
urls | array | one example per supported URL shape (see "URL mode") | Product, search, or category page URLs to scrape (url mode only). Pagination continues forward automatically. startUrls is accepted as an alias and merged in. |
specialsOnly | boolean | false | Keep only currently-discounted products. Applies to a keyword search and a pasted search or category URL. Also evaluated on a single pasted product URL, but the detail page never exposes special-offer status, so it always excludes a pasted product link, on special or not; leave it off when pasting a single product URL. |
brands | array | none (prefill: []) | Exact brand names to keep. Applies to a keyword search, a pasted search or category URL, and a single pasted product URL. Leave empty for all brands. |
minRating | integer | none | 1 to 5, minimum rating to keep. Applies to a keyword search, a pasted search or category URL, and a single pasted product URL. |
minPrice | number | none | Minimum price in EUR to keep. Applies to a keyword search, a pasted search or category URL, and a single pasted product URL. |
maxPrice | number | none | Maximum price in EUR to keep. Applies to a keyword search, a pasted search or category URL, and a single pasted product URL. |
sortBy | string | RELEVANCE | RELEVANCE, PRICE_ASC, PRICE_DESC, DISCOUNT, NEWEST, or RATING. Applies to a keyword search and a pasted search or category URL; has no effect on a single pasted product URL, since there is nothing to reorder. |
fetchDetails | boolean | false | Fetch full product detail for a product found through search or a listing: breadcrumb, flattened specs, seller identity, EAN/GTIN/SKU, return policy and full image gallery. A pasted product URL always includes these fields regardless of this toggle. |
fetchReviews | boolean | true | Fetch native customer reviews. Products with no reviews return an empty list. |
maxReviewsPerProduct | integer | 10 | Cap on reviews fetched per product when fetchReviews is on. 0 returns all the reviews the product page renders. Values above 1000 are capped at 1000. The product page renders about a dozen reviews; when you ask for more (and the product has more), the actor reads the rest from OTTO's own review list, 30 per page, which uses the same residential connection and adds no extra charge beyond the once-per-product detail enrichment. 0 stays at what the product page renders. |
maxPages | integer | 0 | Stop after this many result-page fetches per search, category or URL entry. 0 means no explicit limit: the run walks the catalogue and stops at Max products total, with an internal hard cap of 1000 pages per entry. |
maxItems | integer | 20 | Hard cap on total products returned across every search, category, or URL entry. 0 means the maximum of 100000 (still bounded by Max pages per search). |
resumeFromRunId | string | none | Paste a previous run id (or its dataset id) to continue that run: products already collected there are skipped, so this run only returns new products. Leave empty for a normal run. |
incrementalMode | boolean | false | Turn on for recurring monitoring of the same search or URL set. See "Resume and recurring updates" above for exactly how NEW, UPDATED, UNCHANGED, REAPPEARED and EXPIRED are decided. |
stateKey | string | none | Optional, incremental mode only. 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. |
emitUnchanged | boolean | false | Incremental mode only. Also return, and bill, products that have not changed since the last run. |
emitExpired | boolean | false | Incremental mode only. Also return, and bill, products that were present in a previous run but are no longer found. Only produced once a run has fully scanned the tracked search or URL set. |
ignoreFieldsForChanges | array of strings | none | Incremental mode only. Top-level field names to exclude from UPDATED detection and changedFields (still returned in the output). |
proxy | object | Apify Proxy | Connection settings for the product listing and search fetch. Product detail and review fetches (on by default) always use a residential connection at extra cost, regardless of this setting. |
mcpConnectors | array | none | Optional: send a summary of each product to apps you authorized under Integrations. |
notionParentPageUrl | string | none | Notion connector only: page under which item pages are created. |
maxNotifyListings | integer | 50 | Cap on items exported to each connector per run. Does not affect the dataset. |
Output Example
{"productId": "S0AB12CD","variationId": "S0AB12CD4XYZ","articleNumber": "S0AB12CDKIE","name": "Sample Brand Ecksofa, Sofa L-Form, mit Schlaffunktion","brand": "Sample Brand","category": "Ecksofas","url": "https://www.otto.de/p/sample-ecksofa-S0AB12CD/","price": 799.00,"currency": "EUR","originalPrice": 1599.00,"discountPercent": 50,"isOnSpecial": true,"availability": "AVAILABLE","rating": 4.5,"reviewCount": 26,"variants": [{ "variationId": "S0AB12CD4XYZ", "color": "Khaki", "hexCode": "BEB2A9", "selected": true }],"image": "https://sample.example/img1.jpg","specs": { "Höhe": "85 cm", "Breite": "289 cm", "Polsterung": "Schaumstoff" },"seller": { "name": "Sample Home Brands GmbH", "partnerId": "1000000", "sellerType": "MARKETPLACE_PARTNER" },"returnPolicy": { "days": 30, "freeReturn": true },"reviews": {"averageRating": 4.5,"reviewCount": 26,"items": [{"reviewId": "sample-uuid","author": "Sample R.","location": "Sample City","date": "11.04.2026","rating": 5,"verifiedPurchase": true,"body": "Sample review text.","helpfulVotes": 5,"helpfulVotesTotal": 5,"variantPurchased": "Farbe: Khaki","seller": "Sample Home Brands GmbH"}]}}
Sample shape: values above are illustrative placeholders, not from a live product.
Plan Requirement
Searching and browsing listings (search, category and listing-route links) works on the default connection, including Apify datacenter proxies, on every plan. Product detail pages and reviews (reviews are on by default) are different: the product page answers a plain request with an anti-bot interstitial, so detail and reviews always use a residential German connection at extra cost, regardless of the connection setting you choose. Residential access needs a paid Apify plan or your own proxy URLs.
What you see when residential access is not available:
- If the selected connection is unavailable, the run logs "The selected connection is unavailable to this run. Apify free plans do not include residential proxies." and "Continuing on a backup connection; some pages may still be unavailable." Pages served that way log "Served over the backup connection (the default connection was unavailable)."
- If the proxy refuses every request, the run fails with "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".
- If no page loads and the backup connection also fails, the run fails with "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 Otto pages."
- If pages fail for another reason, the run fails with "Could not load any results page (the requests failed), so nothing was collected. This is a connection problem, not an empty search."
- A run that never requested a page (an empty search keyword, an empty URL list, or only unsupported links) is not a connection failure: it logs what to fix and ends without these messages, as does a run whose pages loaded but returned nothing.
FAQ
How much does it cost?
You pay per product returned, with the detail and review enrichment billed only once per product, only when it is actually fetched and kept. The Pricing tab shows the current rates. Use Max products total to cap the cost of any run.
Which dataset views are available?
The dataset has these views: Products (overview), Variants (nested), Reviews (one row per review), Product details (needs Fetch full product detail) and Changes (incremental mode).
What do the registered prices look like?
Pay per event, as registered on the Pricing tab (Free plan prices; higher plans pay less):
- Product result: $0.0018 for each product returned in the dataset.
- Detail & review enrichment: $0.0008 once per product when full detail and/or reviews are fetched and kept.
- Actor Start: $0.06 per GB of memory, one event minimum.
With the defaults (reviews on) that is roughly $2.66 per 1,000 products on the Free plan.
Is it legal to scrape OTTO?
This actor collects only publicly available product data. You are responsible for how you use it: follow OTTO's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Prices, availability and specifications are generally facts, 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 returned or billed unless you turn that on too.
Why did specialsOnly return nothing for my pasted product link?
The product detail page does not expose the same special-offer signal a listing card does, so Specials / offers only always excludes a single pasted product URL, whether or not that product is actually discounted. Leave it off when pasting one product link, and use it on a search or category URL or link instead.
Why did my run fail instead of returning an empty dataset?
If the site's response no longer matches any shape this actor recognizes, the run fails with a clear message instead of quietly returning zero products, so "no results for this search" is never confused with "the extraction broke". A genuinely empty search still completes normally with zero items. If the connection is repeatedly refused partway through, the run keeps everything already collected and stops cleanly rather than retrying forever.
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 results into Notion, Linear or Airtable.
🔗 Want more fashion data?
Pair this actor with these related scrapers from the same team:
| 🔌 Fnac Scraper Scrape Fnac (fnac.com) products: current price plus strike-through discount, colour/model... | 🔌 Boulanger.com Scrape Boulanger (boulanger.com) electronics and home-appliance products: current price... |
| 🥦 El Corte Inglés Scraper Scrape El Corte Inglés elcorteingles.es products across fashion, electronics, home... | 🔌 Darty Scraper Scrape Darty (darty.com) products: current price plus strike-through reference price and... |
| 🏷️ Kleinanzeigen.de Scraper From $1/1K. Scrape Kleinanzeigen.de ads by keyword, category, location, price, seller... | 🔌 Currys UK Scraper Scrape Currys UK electronics by keyword or URL. Extract prices, brands, stock status... |
💬 Support
- 🐞 Found a bug or a missing field? Open a ticket on the Issues tab. 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.