Zalando Products Scraper avatar

Zalando Products Scraper

Pricing

from $1.05 / 1,000 results

Go to Apify Store
Zalando Products Scraper

Zalando Products Scraper

Product listings from Zalando catalogue and category pages across 12 country storefronts: name, brand, price and was-price, discount, silhouette and images. Relevance-checked, de-duplicated by SKU.

Pricing

from $1.05 / 1,000 results

Rating

0.0

(0)

Developer

Ibnu Adzim

Ibnu Adzim

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 days ago

Last modified

Categories

Share

Product listings from Zalando catalogue and category pages — name, brand, price and was-price, discount, silhouette and images — across 12 country storefronts.

HTTP only, no browser, no login.

Input

{
"mode": "search",
"market": "de",
"keywords": ["sneaker", "laufschuhe"],
"maxPages": 3,
"maxItems": 200
}

Or point it at catalogue/category URLs:

{ "mode": "urls", "market": "de",
"listingUrls": ["https://www.zalando.de/herrenschuhe/"] }

Markets

de at ch uk fr it es nl be pl se dk — each with its own currency and language. Every one was fetched and confirmed to return products; the market list is measured, not assumed.

This matters more than a locale toggle: each storefront has its own catalogue path, and the path is not derivable from the language. .de, .at, .ch, .se, .dk, .pl use /katalog/; .fr uses /catalogue/; .it, .es use /catalogo/; .nl, .be use /catalogus/; and the UK uses /catalog//catalogue/ 404s there.

What you get

recordTypeOne perCarries
SEARCH_SUMMARYkeyword / URLmarket, pages fetched, rows fetched vs returned, duplicates dropped, why paging stopped, and a relevance score
PRODUCTarticleSKU, URL, name, brand, price, was-price, discount %, silhouette, images — plus the raw upstream object
ERRORfailed inputwhy
{
"recordType": "PRODUCT",
"sku": "NI114D0HT-A11",
"productUrl": "https://www.zalando.de/NI114D0HT-A11.html",
"name": "AIR FORCE 1 LE UNISEX - Trainers - white",
"brand": "Nike Sportswear",
"currency": "EUR", "price": 69.95, "originalPrice": 99.95,
"isDiscounted": true, "discountPercent": 30,
"silhouette": "SNEAKER",
"imageUrls": ["https://img01.ztat.net/article/..."]
}

Known limits — read these before you rely on the output

Zalando has no "no results" page. An unmatched query returns HTTP 200 with unrelated products. So every query's relevance is measured and reported: titleMatchRate is the fraction of returned articles matching a query token across name, brand, silhouette and model, and lowRelevanceWarning flags a run below 20%. Measured: sneaker / nike / kleider / laufschuhe 1.00, jacke 0.96, stiefel 0.92, hemden 0.88, three different nonsense strings 0.00. Rows are never silently dropped — a low score is a fact about your query.

The relevance stemmer is German-aware (-e, -en, -er), because with English-only plural handling the perfectly valid query laufschuhe scored 0.00 and would have been wrongly flagged.

price is what you pay; originalPrice only exists when discounted. Undiscounted articles carry no promotional block at all (priceDisplayMode: "BLACK_PRICE") and their price lives in original. This actor resolves that, so price is always populated, and originalPrice is left null rather than echoing price and inventing a markdown that does not exist. isDiscounted says which case a row is.

Prices come from a mix of cents and floats. Upstream sends promotional.amount / original.amount as integer cents (9595) and trackingCurrentAmount as a float (95.95). The cents are converted explicitly; the float is passed through beside them as a cross-check.

Product detail pages are not supported. Zalando listing paths end in / and product pages end in .html; the latter are refused with an explanation.

24 articles per page. Page 1 is deterministic (fetched twice, identical 24/24), and page 2 overlaps page 1 by about one article, de-duplicated by SKU.

Technical

Server-rendered. The data is neither __NEXT_DATA__ nor JSON-LD nor an RSC flight stream — all three were checked and are absent. It is a single JSON object passed to window.__hydrationDataConsume({...}) containing a graphqlCache with the full article objects. Products are located structurally (any dict with both sku and name) because that cache is keyed by a per-deploy query hash.

No WAF: 5/5 TLS profiles returned 200 with near-identical sizes. Past the last page Zalando answers HTTP 400, treated as an honest end of results.

robots.txt was checked at path level: the catalogue path with ?q= is ALLOWED, no ClaudeBot / anthropic-ai group, no blanket disallow.

Full recon trail is in CRAWLING_METHOD.md.