# Bruun Rasmussen Auction Results & Artist Scraper (`artsiom_k/bruun-rasmussen-scraper`) Actor

Bruun Rasmussen (Denmark) auction scraper — real realized prices, upcoming estimates, and a cross-run artist rollup, with built-in delta mode.

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

## Pricing

from $7.50 / 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/platform/actors/running/actors-in-store#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

## bruun-rasmussen-scraper

Scrapes auction lots and a cross-run artist rollup from
[Bruun Rasmussen](https://bruun-rasmussen.dk) — Denmark's oldest and largest auction house. Real
realized prices, upcoming-lot estimates, and the cleanest price representation of any comparable
actor — every price on the site is carried in a custom HTML element's numeric attribute, never a
formatted string to parse.

### Contents

- [Key features](#key-features)
- [Output](#output)
- [Input](#input)
- [Input examples](#input-examples)
- [Incremental (delta) mode](#incremental-delta-mode)
- [How to scrape Bruun Rasmussen](#how-to-scrape-bruun-rasmussen)
- [You might also like](#you-might-also-like)
- [FAQ](#faq)

### 🔑 Key features

- **No text-parsing of prices, anywhere.** Every estimate/realized-price figure on the site is
  carried in a custom `<currency-amount amount="950.0" currency="DKK">`/
  `<currency-range from="..." to="...">` HTML element — this Actor reads the raw attribute directly,
  never a locale-formatted string.
- **Three entity types, one Actor.** `entityType: currentAuctions` (not-yet-resolved lots),
  `auctionResults` (resolved lots with real realized prices — the default), or `artists` (a
  cross-run rollup — see below).
- **No anti-bot friction at normal usage.** No TLS-impersonation workaround needed. A large uncapped
  crawl can hit a real site-side rate limit — Apify Residential proxy (the default) meaningfully
  mitigates it; see Output's known gaps.
- **Delta mode, tuned per entity type.** `auctionResults` defaults to the usual auto-incremental
  behavior (full scan first run, changes only after). `currentAuctions` always fully refreshes every
  run instead.
- **A real artist rollup**, accumulated from `auctionResults` lots as they're crawled.

### 📋 Output

One dataset item per lot or artist, depending on `entityType` — see
[`.actor/dataset_schema.json`](.actor/dataset_schema.json) for the full field list, or the Output
tab's per-entity-type views for a readable table.

**Example lot record** (resolved, sold):

```json
{
  "source": "bruunrasmussen",
  "entity_type": "auctionResults",
  "external_id": "bruunrasmussen_BDE49E1A9E7F",
  "url": "https://bruun-rasmussen.dk/m/lots/BDE49E1A9E7F",
  "title": "Joakim Skovgaard",
  "artist_name": "Joakim Skovgaard",
  "artist_id": "365",
  "auction_number": "2633",
  "auction_title": "Paintings, furniture and varia, 10 August 2026",
  "category_name": "Paintings",
  "estimate_low": 4000.0,
  "estimate_high": 5000.0,
  "currency": "DKK",
  "sold": true,
  "price": 4600.0,
  "change_type": "new"
}
```

**Example artist rollup record:**

```json
{
  "source": "bruunrasmussen",
  "entity_type": "artists",
  "external_id": "bruunrasmussen_artist_365",
  "name": "Joakim Skovgaard",
  "tracked_lot_count": 1,
  "tracked_total_price": 4600.0,
  "tracked_avg_price": 4600.0,
  "latest_lot_title": "Joakim Skovgaard",
  "latest_auction_number": "2633"
}
```

Results can be downloaded as JSON, CSV, or Excel from the Console's Output tab, or pulled via the
Apify API/dataset endpoint.

**Known gaps:**

- **A large uncapped run can hit a real site-side rate limit — mitigated by default, not eliminated.**
  Confirmed live: sustained crawling on a static/datacenter IP triggers a block lasting over an hour.
  Apify's Residential proxy (the default here) was confirmed live across ~1,300 real requests to avoid
  that permanent block — instead producing occasional (~once per 90-300 items) short, self-healing
  stalls (a 30s timeout, ~5 min retry, then resumes), with zero errors and zero data loss in every
  real test so far. Real cost impact: roughly $0.73-0.85 per 1,000 items at this account's rates,
  still well under this actor's own per-item Store price. Not yet run at true full-archive scale —
  keep an eye on real usage over time.
- **`auctionResults` is a rolling ~2-week recent-results window, not a deep historical archive.**
  Confirmed live across multiple categories: resolved lots only stay queryable for roughly the
  current and immediately-past auction cycle. Great for tracking prices as auctions conclude
  (especially with `mode: auto` on a schedule), not for looking up decades-old results.
- The site's own `status` filter (used for discovery) is a coarse, occasionally-stale signal in
  *both* directions — confirmed live that it can include a lot that turns out not actually sold, or
  (rarer) a lot that's already resolved when discovered as "not yet resolved". This never affects
  what's in the output: `sold`/`price` always come from the lot's own page, never from the filter
  that discovered it.
- `provenance`/`exhibitions` are best-effort — parsed from free-text paragraphs prefixed
  "Provenance:"/"Exhibited:", not a guaranteed structured field. Anything that doesn't match lands in
  `remarks` instead of being dropped.
- `entityType: artists` **does not crawl anything itself** — it reads a rollup that `auctionResults`
  runs build up over time. Running `artists` before `auctionResults` has ever run pushes nothing
  (with a clear log message saying so). Given `auctionResults` is itself a rolling recent window, the
  rollup's numbers reflect recent activity accumulated over time — never a full sales history for an
  artist.
- Not every lot carries an artist attribution (e.g. books, coins, furniture without a named maker) —
  `artist_name`/`artist_id` are `null` in that case.

### ⚙️ Input

See [`.actor/input_schema.json`](.actor/input_schema.json) for the full JSON schema.

| Parameter | Type | Default | Description |
|---|---|---|---|
| `entityType` | String | `auctionResults` | `currentAuctions`, `auctionResults`, or `artists`. |
| `startUrls` | Array of strings | *(none)* | Specific Bruun Rasmussen lot URLs or bare 12-character lot ids to scrape directly. Ignored for `entityType=artists`. Scope is always `"custom"` — no delisting-detection, no persisted baseline. Leave empty for the full crawl instead. |
| `maxItems` | Integer | `50` | Stop after pushing this many dataset items (lots or artists). Defaults to a fast, cheap preview (also what keeps an unconfigured run within Apify's automated 5-minute QA check). |
| `mode` | String | `auto` | `auto` (recommended): the usual full-then-incremental behavior for `auctionResults`; always `full` for `currentAuctions`. `full`/`incremental` override this per run. Has no effect for `entityType=artists`. |
| `impersonate` | String | `chrome` (internal) | curl\_cffi TLS-impersonation target. No bot-management signal was observed anywhere on bruun-rasmussen.dk while building this Actor, so this is set internally by default. |
| `proxyConfiguration` | Object | `{"useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"]}` | Apify Proxy config. Residential by default — confirmed live to meaningfully mitigate a real site-side rate limit that a static/datacenter IP hits on a large sustained crawl (see Known gaps). |

### 🧪 Input examples

**Quick preview of recent results** (the default):

```json
{ "entityType": "auctionResults" }
```

**Every currently resolved lot across the full category tree** (uncapped):

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

**What's currently up for bidding:**

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

**Artist rollup** (run `auctionResults` at least once first):

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

**A specific lot:**

```json
{ "entityType": "auctionResults", "startUrls": ["https://bruun-rasmussen.dk/m/lots/BDE49E1A9E7F"] }
```

**Scheduled tracking run** — full, uncapped run (`maxItems` cleared — required for the baseline to
save and delistings to be detected):

```json
{ "entityType": "auctionResults", "mode": "incremental", "maxItems": null }
```

### 🔄 Incremental (delta) mode

Every `currentAuctions`/`auctionResults` run classifies each lot as `new`, `changed` (sold/price
moved), `unchanged`, or `delisted`, using a state baseline persisted in a named Apify Key-Value
Store scoped to `entityType`. `entityType: artists` doesn't use delta mode at all — every run is a
full snapshot of the current rollup.

- `auctionResults`: `mode: auto` (default) — first run for a scope pushes everything (`full`); later
  runs push only `new`/`changed`/`delisted` (`incremental`). Given the ~2-week rolling window, a
  scheduled `mode: auto` run is a genuinely good way to track newly-resolved lots as they land.
- `currentAuctions`: `mode: auto` always behaves as `full`, every run.
- A `startUrls`-scoped run is always partial and never updates the baseline or reports delistings.

Full design: [`docs/incremental-mode.md`](../../docs/incremental-mode.md).

### 🚀 How to scrape Bruun Rasmussen

1. Open the Bruun Rasmussen Auction Results & Artist Scraper in Apify Console and go to the
   **Input** tab.
2. Pick `entityType` (`currentAuctions`, `auctionResults`, or `artists`).
3. `maxItems` defaults to 50 (a quick preview) — clear it (set to `null`) for a full, uncapped run.
4. Click **Start**.
5. When the run finishes, browse results in the **Output** tab, or download as JSON/CSV/Excel, or
   fetch them via the API.
6. To track over time instead of scraping once: create a **Schedule** with `mode: auto`.

### 🔗 You might also like

- **[Dorotheum Auction Results & Artist Scraper](https://apify.com/artsiom_k/dorotheum-scraper)**
- **[Artcurial Auction Results & Artist Scraper](https://apify.com/artsiom_k/artcurial-scraper)**
- **[Lempertz Auction Results & Artist Scraper](https://apify.com/artsiom_k/lempertz-scraper)**

### ❓ FAQ

**Is it legal to scrape Bruun Rasmussen?** It's legal to collect publicly available auction-result
data such as lot descriptions, prices, and sale information. Scrape it only with a legitimate
purpose under GDPR.

**How far back does `auctionResults` reach?** Roughly the current and immediately-past auction
cycle (~2 weeks), confirmed live — not a deep historical archive. Use a schedule with
`mode: auto` to build up a longer record over time instead.

**Why is `price` null for a resolved lot?** It wasn't sold — check `sold`/`state` for the exact
outcome.

**Why does `entityType: artists` push nothing?** It reads a rollup built up by
`entityType: auctionResults` runs — it doesn't crawl anything on its own. Run `auctionResults`
(ideally a full, unbounded run) at least once first.

### Search keywords

bruun rasmussen scraper, auction house scraper, auction results scraper, art price data, art market
analytics, realized price data, hammer price data, auction price index, art collector data feed

# Actor input Schema

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

"currentAuctions": lots not yet resolved — a mostly-reliable site filter, occasionally including a just-resolved lot (each item's own sold/price fields are always authoritative regardless). "auctionResults": resolved lots with real realized prices — the default. Note this is a rolling ~2-week recent-results window on this site, not a deep historical archive (confirmed live) — see the README. "artists": a cross-run artist rollup, accumulated from auctionResults lots as they're crawled (not a fresh crawl of its own — run auctionResults at least once first, or this will push nothing). Each produces a different output shape (see dataset\_schema.json).

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

Optional list of specific Bruun Rasmussen lot URLs (e.g. https://bruun-rasmussen.dk/m/lots/01D6FC033326) or bare 12-character lot ids to scrape directly, instead of the full category-tree crawl. Ignored for entityType=artists. Scope is always "custom", with no delisting-detection or persisted incremental baseline (a hand-picked list is necessarily partial) — leave empty for the full crawl instead.

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

Stop after pushing this many dataset items (lots or artists). Defaults to 50 — a fast, cheap preview, and what keeps an unconfigured run within Apify's automated 5-minute QA check. A full auctionResults crawl covers every category's current ~2-week results window — raise this or clear it (set to null) for that; note a capped run never updates the incremental baseline (see mode below) or feeds the artist rollup for lots it never reaches.

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

"auto" (recommended): for auctionResults, full scan on the first run for a scope, incremental (new/changed only) afterwards. For currentAuctions specifically, "auto" always behaves as a full refresh every run regardless of an existing baseline — upcoming lots are a small, frequently-changing dataset where a full refresh is more useful than a delta. "full": always push every item and refresh the baseline. "incremental": always push only new/changed items. Only a plain, unscoped crawl (no startUrls) can detect delistings or update the baseline. Has no effect for entityType=artists (every run there just reads the current rollup state).

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

curl\_cffi browser TLS-impersonation target. No Cloudflare or other bot-management signal was observed anywhere on bruun-rasmussen.dk while building this Actor, so this defaults to "chrome" internally — override only if that stops working.

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

Apify Proxy configuration — defaults to the Residential group. A real site-side rate limit exists on this site (not Cloudflare/bot-management — a plain IP-level limit); a static datacenter IP (Apify's own datacenter group and a BrightData static-zone proxy) was confirmed live to hit it and never recover within an hour. Apify's Residential proxy group was confirmed live across ~1,300 real requests to avoid that permanent block entirely — occasional short (~5 min), self-healing stalls still occur (roughly once per 90-300 items), but with zero errors and zero data loss in every real test so far. See the README/docs for the full investigation before turning this off.

## Actor input object example

```json
{
  "entityType": "auctionResults",
  "maxItems": 50,
  "mode": "auto",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# 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/bruun-rasmussen-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/bruun-rasmussen-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/bruun-rasmussen-scraper --silent --output-dataset

```

## MCP server setup

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