# LUMAS Artist & Artwork Scraper (`artsiom_k/lumas-scraper`) Actor

Scrape LUMAS.com limited-edition photography artworks (with per-size edition size, signature type, and remaining-stock data) and artist profiles, with built-in delta mode. No login required.

- **URL**: https://apify.com/artsiom\_k/lumas-scraper.md
- **Developed by:** [Artsiom Kunitsyn](https://apify.com/artsiom_k) (community)
- **Categories:** Other, E-commerce
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.00 / 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.

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

## lumas-scraper

Scrapes both **artworks** (limited-edition photography, one record per artwork with a per-size
breakdown) and **artist profiles** from [LUMAS.com](https://www.lumas.com), a global limited-edition
photography gallery network — via the site's own sitemap and real embedded structured data. No
login required.

### Contents

- [Key features](#key-features)
- [Output](#output)
- [Input](#input)
- [Input examples](#input-examples)
- [Incremental (delta) mode](#incremental-delta-mode)
- [FAQ](#faq)

### 🔑 Key features

- **Two entity types, one Actor.** Set `entityType` to `artworks` or `artists`.
- **Real per-size edition-size and remaining-stock tracking** — a genuinely different data shape
  from every other art-marketplace Actor in this collection. Each artwork is offered in 1-3+
  distinct sizes, each with its own price, availability, and edition size — some sizes are a
  numbered "Edition of: N" (with a signature type: Signed, Studio Stamp, ...), others are a genuine
  Open Edition with no cap at all, confirmed live on the same artwork.
- **A real, best-effort "stock left" count**, read from the page's own analytics data for whichever
  size is shown by default — present for most artworks, and honestly `null` (not guessed) when the
  site itself doesn't report it (confirmed: this correlates with Open Edition sizes, which have no
  capped stock to report).
- **A real three-state per-size availability signal** (`for_sale` / `limited_availability` /
  `sold`), aggregated honestly across a multi-size artwork's own sizes — some artworks have one
  size sold out while another is still in stock.
- **Delta mode built in.** Every run classifies each item as `new`, `changed`, `unchanged`, or
  `delisted` against a persisted baseline — pay for what changed, not a full re-scrape every time.
- **Confirmed-dead listings are never re-fetched.** A removed artwork or artist returns a plain
  HTTP 404 on this site — tracked in a persisted registry so a future run skips it without a
  wasted request.
- **`artistUrls` input.** Scope `entityType=artworks` to a specific artist's own catalog (e.g. from
  a prior `entityType=artists` run) — reuses that artist's own embedded artwork list, no separate
  discovery pass needed.
- **`maxItems` defaults to 50** — a fast, cheap preview, and what keeps an unconfigured run within
  Apify's automated 5-minute QA check. Clear it (`null`) for a full crawl (~5,046 artworks or ~604
  artists as of this build).

### 📋 Output

One dataset item per artwork or artist, depending on `entityType` — see
[`.actor/dataset_schema.json`](.actor/dataset_schema.json) for the full field list.

**Example artwork record:**

```json
{
  "source": "lumas",
  "entity_type": "artworks",
  "external_id": "pictures/3deluxe/bergen01_violet",
  "url": "https://www.lumas.com/pictures/3deluxe/bergen01_violet/",
  "title": "Bergen.01 (violet)",
  "artist_name": "3deluxe",
  "artist_url": "https://www.lumas.com/artist/3deluxe/",
  "date_created": "2009",
  "image_url": "https://img.lumas.com/showimg_dde01_desktop.jpg",
  "currency": "USD",
  "price_min": 760,
  "price_max": 1432,
  "availability_status": "for_sale",
  "sold": false,
  "has_open_edition_size": false,
  "sizes": [
    {
      "sku": "DDE02",
      "price": 760,
      "list_price": 950,
      "on_sale": true,
      "currency": "USD",
      "availability": "for_sale",
      "width_mm": 1080,
      "height_mm": 290,
      "edition_size": 100,
      "signature": "Studio Stamp",
      "mounting": "Mounted under acrylic glass",
      "framing_label": "depth 0.08\" glossy, 11.4 x 42.5\" (External dimensions)",
      "stock_left": 49
    },
    {
      "sku": "DDE01",
      "price": 1432,
      "list_price": 1790,
      "on_sale": true,
      "currency": "USD",
      "availability": "for_sale",
      "width_mm": 1800,
      "height_mm": 490,
      "edition_size": 100,
      "signature": "Studio Stamp",
      "mounting": "Mounted under acrylic glass",
      "framing_label": "depth 0.08\" glossy, 19.3 x 70.9\" (External dimensions)",
      "stock_left": null
    }
  ],
  "change_type": "new"
}
```

**Example artist record:**

```json
{
  "source": "lumas",
  "entity_type": "artists",
  "external_id": "660",
  "url": "https://www.lumas.com/artist/3deluxe/",
  "name": "3deluxe",
  "bio": "3DIMENSIONAL LUXURY Finding pictures in the clouds is one of the best exercises...",
  "image_url": "https://img-lumas.b-cdn.net/showimg_dde-header_full.jpg",
  "is_active": true,
  "artwork_urls": ["https://www.lumas.com/pictures/3deluxe/bergen01_violet/", "..."],
  "artwork_count": 10,
  "change_type": "new"
}
```

### 🔧 Input

| Field | Type | Default | Description |
|---|---|---|---|
| `entityType` | string | `artworks` | `artworks` or `artists`. |
| `startUrls` | array | — | Specific artwork/artist URLs to scrape directly instead of the full sitemap. |
| `artistUrls` | array | — | `artworks` only: scope to these artists' own catalogs (reuses each artist page's embedded artwork list). |
| `maxItems` | integer | `50` | Stop after pushing this many items. Set to `null` for a full crawl. |
| `mode` | string | `auto` | `auto` / `full` / `incremental` — see [Incremental mode](#incremental-delta-mode). |
| `concurrency` | integer | `20` | How many detail pages to fetch in parallel. |
| `impersonate` | string | `chrome` | curl\_cffi TLS-impersonation target. |
| `proxyConfiguration` | object | off | Apify Proxy config — not needed; no anti-bot friction found. |

### 📥 Input examples

**Default preview (50 artworks):**

```json
{ "entityType": "artworks" }
```

**Full artist directory:**

```json
{ "entityType": "artists", "maxItems": null }
```

**All artworks by specific artists:**

```json
{ "entityType": "artworks", "artistUrls": ["https://www.lumas.com/artist/3deluxe/"], "maxItems": null }
```

### 🔁 Incremental (delta) mode

`auto` mode does a full scan the first time it runs for a given `entityType`/scope, then only pushes
new/changed items on later runs — including price changes and sold-out transitions
(`price_min`/`price_max`/`availability_status` are the watched fields). Only an uncapped, unscoped
run (no `maxItems` limit reached, no `startUrls`/`artistUrls`, no item errors) can detect delistings
or update the baseline.

### ❓ FAQ

**Why is `stock_left` null for most sizes?**
This is a real, honest limitation, not a scraping gap: the site's own page only reports a
remaining-stock count for whichever single size is shown by default when the page loads — every
other size on the same artwork genuinely has no stock-left data available from the page itself.
It's also `null` for Open Edition sizes, since there's no capped stock to report.

**Why do some sizes have no `edition_size`?**
That's a real Open Edition — a size with no fixed cap at all, confirmed live to exist alongside
numbered "Edition of: N" sizes on the very same artwork. `has_open_edition_size` flags this at the
artwork level; check each entry in `sizes` for the per-size detail.

**Does this need a proxy?**
No — no anti-bot friction was found anywhere on this site while building this Actor (a real
150-request concurrent burst at concurrency 40 came back 100% clean).

**Does this cover LUMAS's other regional storefronts (lumas.de, lumas.co.uk, etc.)?**
Not in this build — it's scoped to `www.lumas.com` (USD pricing) only. The site has at least 9
other regional/currency storefronts sharing the same catalog; a future version could add those.

# Actor input Schema

## `entityType` (type: `string`):

Scrape artworks (limited-edition photography, one record per artwork with a per-size breakdown) or artist profiles (bio + complete artwork list, no extra fan-out cost). See dataset\_schema.json for the field set of each.

## `startUrls` (type: `array`):

Optional list of specific LUMAS artwork (https://www.lumas.com/pictures/{artist}/{artwork}/) or artist (https://www.lumas.com/artist/{artist}/) URLs to scrape directly, instead of discovering via the full site sitemap — use whichever kind matches entityType. A startUrls-scoped run cannot detect delistings or update the incremental baseline.

## `artistUrls` (type: `array`):

Optional: scope entityType="artworks" to only the artworks of these specific artists (https://www.lumas.com/artist/{artist}/ URLs, e.g. from a prior entityType="artists" run). Reuses each artist page's own complete artwork list — no separate discovery needed. Ignored when entityType is "artists" or startUrls is set. Gets its own incremental-mode scope, same as startUrls.

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

Stop after pushing this many dataset items. Defaults to 50 — a fast, cheap preview, and what keeps an unconfigured run within Apify's automated 5-minute QA check. A full, uncapped sitemap crawl covers the entire site (~5,046 artworks, or ~604 artists as of this build). Raise this or clear it (set to null) for that.

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

"auto" (recommended): full scan on the first run for a given entityType/scope, incremental (new/changed only) afterwards. "full": always push every item and refresh the baseline — schedule this periodically to catch delistings, price changes, and stock-left/sold-out transitions. "incremental": always push only new/changed items. Only a plain, unscoped sitemap crawl (no startUrls/artistUrls) can detect delistings or update the baseline.

## `concurrency` (type: `integer`):

How many artwork/artist detail pages to fetch in parallel. Defaults to 20 — validated live against lumas.com with zero blocking at concurrency up to 40 (~38 req/s). Lower it only if you start seeing errors.

## `impersonate` (type: `string`):

curl\_cffi browser TLS-impersonation target. Defaults to "chrome" internally, though no anti-bot friction was found anywhere on this site while building this actor.

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

Apify Proxy configuration. Leave off unless you start seeing blocks from your own IP reputation — no anti-bot friction was found while building this actor.

## Actor input object example

```json
{
  "entityType": "artworks",
  "maxItems": 50,
  "mode": "auto",
  "concurrency": 20,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("artsiom_k/lumas-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 = {}

# Run the Actor and wait for it to finish
run = client.actor("artsiom_k/lumas-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 '{}' |
apify call artsiom_k/lumas-scraper --silent --output-dataset

```

## MCP server setup

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