# Harrods Scraper — Luxury Product Prices, Stock & Images (`studio-amba/harrods-scraper`) Actor

Scrape harrods.com, the UK's flagship luxury department store: product names, brands, prices, per-size stock, images, descriptions and breadcrumb categories from category browsing or site search, unblocked with Bright Data Web Unlocker.

- **URL**: https://apify.com/studio-amba/harrods-scraper.md
- **Developed by:** [Studio Amba](https://apify.com/studio-amba) (community)
- **Categories:** E-commerce
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.20 / 1,000 result scrapeds

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

## Harrods Scraper

Pull product data from harrods.com, the UK's flagship luxury department store. This scraper reads Harrods' own server-rendered category pages and product pages, so it returns clean, structured data — name, brand, price, per-size stock, images, description — for real catalogue listings, not a scraped screenshot of a page.

### How to scrape Harrods data

Point the actor at a Harrods category URL (e.g. `https://www.harrods.com/en-gb/women-clothing` or `https://www.harrods.com/en-gb/designers/gucci`) and it pages through that category's own listing, 60 products per page, until it hits `maxResults`. Or give it a free-text `searchQuery` (e.g. `"gucci bag"`) to run against Harrods' own site search instead.

Harrods sits behind Akamai Bot Manager: a plain request to any `harrods.com` path — including `/robots.txt` — comes back as a direct `403 Access Denied` from Akamai's edge. This actor routes every request through Bright Data's Web Unlocker, which clears the block, so you don't need your own proxy or browser-automation setup.

Harrods runs on the SCAYLE commerce platform (Nuxt 3 frontend) with Algolia for on-site search. Category pages are fully server-rendered and embed a complete `application/ld+json` `ItemList` block — one entry per listed product, with name, SKU, URL, image, brand, price, currency and stock status already structured — so category-mode results come straight out of the page HTML with no extra requests. Harrods' `/en-gb/search` page, by contrast, is client-rendered (Algolia has no public key exposed in the bundle), so search mode collects the product links Harrods still renders into that page and fetches each product's own page to get its data — one extra Bright Data request per result, see the cost note below.

Every product page also carries a small embedded data blob (Nuxt's SSR payload) that includes SCAYLE's real per-size stock ladder — the actual variant IDs and stock quantities feeding the "Add to Bag" size picker, not just a size list. Turn on `fetchProductDetails` (or use search mode, which always fetches details) to get that per-size breakdown, plus the full description, image gallery, colour and breadcrumb category.

If you don't provide any input, it defaults to scraping Harrods' Women's Clothing category as a demo.

### Input

| Field | Type | Description |
|-------|------|-------------|
| `categoryUrl` | string | A Harrods category/browse page URL. Paged automatically. Ignored when `searchQuery` is set (default: `https://www.harrods.com/en-gb/women-clothing`) |
| `searchQuery` | string | A free-text search term run against Harrods' own site search. When set, replaces `categoryUrl` entirely and forces per-product detail fetching |
| `maxResults` | integer | Cap on number of products returned (default: 20, max: 500) |
| `fetchProductDetails` | boolean | OFF by default. ON adds description, full image gallery, colour, breadcrumb category and per-size stock — at the cost of one extra Bright Data request per product. Always on in search mode. |
| `brightDataApiKey` | string | Your own Bright Data API key, if you want to use your own Web Unlocker zone instead of the actor's built-in one |
| `proxyConfiguration` | object | Not used — this actor reaches harrods.com exclusively through Bright Data, which handles unblocking on its own. Kept for input-shape compatibility. |

### What you get back

| Field | Type | Example |
|-------|------|---------|
| `name` | string | `"Agnona Cashmere Lipstick Sweater"` |
| `brand` | string | `"Agnona"` — falls back to `"Harrods"` for own-label items |
| `price` | number | null | `810` — current selling price in GBP |
| `currency` | string | `"GBP"` — harrods.com is a single UK storefront |
| `sku` | string | null | `"000000000000199415"` — Harrods' internal SKU |
| `productId` | string | null | Same value as `sku` |
| `inStock` | boolean | null | `true` — real 3-state signal from Harrods' own availability field, never coerced to `false` |
| `url` | string | Full product page URL |
| `imageUrl` | string | null | Primary product image |
| `imageUrls` | array | All product images (with `fetchProductDetails` on) |
| `colour` | string | null | `"BURGUNDY"` — only with `fetchProductDetails` on |
| `description` | string | null | Editorial product copy — only with `fetchProductDetails` on |
| `category` | string | null | Deepest breadcrumb category — only with `fetchProductDetails` on |
| `categories` | array | Full breadcrumb path — only with `fetchProductDetails` on |
| `sizes` | array | Per-size stock ladder (`size`, `sku`, `inStock`, `stockQuantity`) — only with `fetchProductDetails` on, and only for products with size variants |
| `scrapedAt` | string | ISO timestamp |

### Sample output

```json
{
  "name": "Agnona Cashmere Lipstick Sweater",
  "brand": "Agnona",
  "price": 810,
  "currency": "GBP",
  "sku": "000000000000199415",
  "productId": "000000000000199415",
  "inStock": true,
  "url": "https://www.harrods.com/en-gb/p/agnona-cashmere-lipstick-sweater-000000000000199415",
  "imageUrl": "https://hrd-live.cdn.scayle.cloud/images/70af0731b110863c4a4cd665de0d3579.jpg?quality=75",
  "imageUrls": [
    "https://hrd-live.cdn.scayle.cloud/images/70af0731b110863c4a4cd665de0d3579.jpg?quality=75",
    "https://hrd-live.cdn.scayle.cloud/images/98c28c1ba7eeb341c4fd9df3d3e1f79d.jpg?quality=75"
  ],
  "colour": "L77PURPLE - BURGUNDY",
  "description": "Elevate your wardrobe with Agnona's Lipstick sweater, where exquisite softness meets function...",
  "category": "Womens Clothing",
  "categories": ["Women New In", "Womens Clothing"],
  "sizes": [
    { "size": "XS", "sku": "000000000199415001", "inStock": false, "stockQuantity": 0 },
    { "size": "S", "sku": "000000000199415002", "inStock": true, "stockQuantity": 2 }
  ],
  "scrapedAt": "2026-09-08T09:16:13.016Z"
}
```

### Use cases

- **Price monitoring** — track Harrods' own pricing across designer brands and categories over time.
- **Assortment research** — see which designers and products a category is actually carrying, and in what depth of size runs.
- **Stock intelligence** — `sizes` exposes the real per-size stock quantity behind the "Add to Bag" picker, not just a size list.
- **Market comparison** — Harrods is a standard UK luxury reference point alongside NET-A-PORTER, Mytheresa and Farfetch.

### Coverage

harrods.com is a single UK storefront, priced in GBP throughout. Harrods ships internationally, but there is no separate country-specific storefront the way some luxury e-tailers run — this actor covers the one live site.

### Performance and cost

Category mode's listing data (name, brand, price, SKU, image, stock) comes from a single Bright Data request per 60 products — a small fraction of a cent per result. Turning on `fetchProductDetails`, or using search mode (which always fetches details), adds one Bright Data request per product for the richer fields (description, full gallery, colour, breadcrumb, sizes) — budget for that when sizing a run. Your run's usage cost only settles after the run reports SUCCEEDED — checking cost mid-run will undercount it.

### Limitations

- Search mode depends on Harrods still rendering product links into `/en-gb/search`'s initial HTML even though the result data itself is client-side (Algolia); if Harrods moves to a fully client-only search page, search mode would need a different data source.
- `sizes` is only populated when the product's own page carries a discoverable per-size `variants` structure in its embedded data — a handful of product types (e.g. some beauty/home items) may only ever offer a single "Default" variant.
- Ratings and review counts aren't exposed on either the listing or detail pages, so they're not included in the output.
- If Harrods restructures its category/product JSON-LD or its Nuxt payload shape, the actor will need updating.

### Related scrapers

If you're covering European luxury fashion more broadly, also check out:

- [NET-A-PORTER Scraper](https://apify.com/studio-amba/net-a-porter-scraper) — luxury fashion, per-size stock, 20+ markets
- [Mytheresa Scraper](https://apify.com/studio-amba/mytheresa-scraper) — luxury fashion e-tail, full per-size stock
- [Farfetch Scraper](https://apify.com/studio-amba/farfetch-scraper) — luxury fashion marketplace

### Support

Found an issue or need a category that isn't resolving correctly? Open an issue on this actor's Apify Store page and we'll take a look.

### Why Bright Data instead of a standard proxy

Harrods' Akamai Bot Manager layer returns a direct `403 Access Denied` on every path from a plain datacenter connection, including `/robots.txt` itself — confirmed live. Bright Data's Web Unlocker handles that fingerprinting and challenge-solving so this actor can focus on parsing real product data instead of maintaining an anti-bot bypass.

# Actor input Schema

## `categoryUrl` (type: `string`):

A harrods.com category/browse page URL, e.g. "https://www.harrods.com/en-gb/women-clothing" or "https://www.harrods.com/en-gb/designers/gucci". Paged automatically (60 products/page). Ignored when searchQuery is set.

## `searchQuery` (type: `string`):

A free-text search term run against harrods.com's own site search, e.g. "gucci bag". When set, this replaces categoryUrl entirely (not combined) and forces per-product detail fetching -- see README cost note.

## `maxResults` (type: `integer`):

Maximum number of products to scrape. Hard-capped at 500 to keep a single run well under an hour even in the most expensive mode (search, or category with fetchProductDetails on).

## `fetchProductDetails` (type: `boolean`):

OFF by default: category-mode results come straight from the category page's own listing data (name, brand, price, currency, SKU, image, stock) -- fast and cheap, one Bright Data request per 60 products. ON: fetches each product's own page too, adding description, full image gallery, colour, breadcrumb category and the per-size stock ladder (sizes) -- at the cost of one extra Bright Data request PER PRODUCT. Search mode always fetches detail pages regardless of this setting, since /en-gb/search carries no listing data server-side.

## `brightDataApiKey` (type: `string`):

Bright Data API key (Web Unlocker zone) used to reach harrods.com behind its Akamai Bot Manager protection. Falls back to the actor's own BRIGHT\_DATA\_API\_KEY environment variable if left empty.

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

Not used — this actor reaches harrods.com exclusively through Bright Data Web Unlocker (Akamai Bot Manager-protected, confirmed live), which handles unblocking on its own. Kept for input-shape compatibility.

## Actor input object example

```json
{
  "categoryUrl": "https://www.harrods.com/en-gb/women-clothing",
  "maxResults": 20,
  "fetchProductDetails": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# 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 = {
    "categoryUrl": "https://www.harrods.com/en-gb/women-clothing",
    "maxResults": 20,
    "fetchProductDetails": false,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("studio-amba/harrods-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 = {
    "categoryUrl": "https://www.harrods.com/en-gb/women-clothing",
    "maxResults": 20,
    "fetchProductDetails": False,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("studio-amba/harrods-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 '{
  "categoryUrl": "https://www.harrods.com/en-gb/women-clothing",
  "maxResults": 20,
  "fetchProductDetails": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call studio-amba/harrods-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,studio-amba/harrods-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/0OOIFmFCNGD4KKI7a/builds/ZNAp4d5xCqYB7h8St/openapi.json
