# Artfinder.com Artist & Artwork Scraper (`artsiom_k/artfinder-scraper`) Actor

Artfinder.com artist and artwork scraper with real per-artist rollup stats, a sold-artworks filter, and built-in delta mode.

- **URL**: https://apify.com/artsiom\_k/artfinder-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 $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

## artfinder-scraper

Scrapes both **artworks** and **artist profiles** from [Artfinder.com](https://www.artfinder.com),
including per-artist rollup stats (total/sold artworks, average price, medium mix) computed from
the artist's full catalog — not a sample.

### Contents

- [Key features](#key-features)
- [Output](#output)
- [Input](#input)
- [Input examples](#input-examples)
- [Incremental (delta) mode](#incremental-delta-mode)
- [How to scrape Artfinder.com](#how-to-scrape-artfindercom)
- [Known gaps](#known-gaps)
- [You might also like](#you-might-also-like)
- [FAQ](#faq)

### 🔑 Key features

- **Two entity types, one Actor.** Set `entityType` to `artworks` or `artists` — same design as
  this Actor's siblings for SaatchiArt.com and Artmajeur.com.
- **Real per-artist rollup stats, not a sample.** Total/sold artwork counts and average price are
  computed from an artist's **entire** catalog, paginated to completion — confirmed to include sold
  items, not just currently-for-sale ones.
- **`minSoldArtworks` filter.** Only push artists with at least N sold artworks — skip the majority
  who've never sold anything, without paying for rows you'd throw away.
- **Delta mode built in.** Every run classifies each item as `new`, `changed`, `unchanged`, or
  `delisted` against a persisted baseline.
- **Exhaustive when you ask for it.** A plain run (no `startUrls`) covers the whole site — the full
  artist directory (~3,785 artists as of this build) or the full artwork catalog (~144,000 products
  across 3 sitemap shards). `maxItems` defaults to 50 (a fast preview) — clear it or raise it for a
  real full run.

### 📋 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, or the
Output tab's **Artworks** / **Artists** views for a readable table.

**Example artwork record:**

```json
{
  "source": "artfinder",
  "entity_type": "artworks",
  "external_id": "2312900",
  "url": "https://www.artfinder.com/product/east-meets-west-c3fc7/",
  "title": "East Meets West",
  "artist_name": "Teresa Pereira",
  "artist_url": "https://www.artfinder.com/artist/teresa-pereira/",
  "category": "Oil painting",
  "style": "Photorealistic",
  "materials": "oil",
  "price": 999.0,
  "currency": "USD",
  "availability_status": "avail",
  "width_cm": 71.12,
  "height_cm": 55.88,
  "change_type": "new"
}
```

**Example artist record:**

```json
{
  "source": "artfinder",
  "entity_type": "artists",
  "external_id": "97906",
  "url": "https://www.artfinder.com/artist/olgali/",
  "name": "Olga Li",
  "country": "Italy",
  "followers_total": 194,
  "total_artworks": 103,
  "sold_artworks": 5,
  "avg_price_eur": 287.4,
  "mediums": "Collage, Mixed media",
  "change_type": "new"
}
```

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

### ⚙️ Input

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

| Parameter | Type | Default | Description |
|---|---|---|---|
| `entityType` | String | `artworks` | `artworks` or `artists`. |
| `startUrls` | Array of strings | *(none)* | Specific artist or product URLs to scrape directly, instead of discovering via the artist-search API or the product sitemap. A `startUrls`-scoped run cannot detect delistings or update the incremental baseline, and for `entityType=artists` it skips the artist-search pass, so `country` isn't populated for that scope (see [Known gaps](#known-gaps)). |
| `maxItems` | Integer | `50` | Stop after pushing this many dataset items. Defaults to a fast, cheap preview. A full run covers the entire site — raise this or clear it for that. |
| `minSoldArtworks` | Integer | `0` | Artists only. Only push artist records with at least this many sold artworks (the `sold_artworks` rollup field) — set to `1` to skip artists who've never sold anything. Artists are still fully tracked internally either way (delta baseline, delisted-detection); this only filters what's pushed to the dataset. |
| `mode` | String | `auto` | `auto` (recommended): full scan on the first run, incremental after. `full`: always push every item and refresh the baseline. `incremental`: always push only new/changed items. Only a plain, unscoped run (no `startUrls`) can detect delistings or update the baseline. |
| `proxyConfiguration` | Object | `{"useApifyProxy": false}` | Apify Proxy config. Off by default — this Actor's default (a plain browser `User-Agent` header, no TLS impersonation) worked cleanly from every network tested while building it. |

### 🧪 Input examples

**Full artwork catalog scan** (exhaustive, slow — every artwork's own page is fetched;
`maxItems: null` explicitly overrides the 50-item default):

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

**Full artist directory, with rollup stats:**

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

**Artists with at least one sold artwork only** (skips the majority of artists who've never sold —
useful if you only care about active sellers):

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

**Scheduled tracking run** — only new/changed/delisted items since the last baseline:

```json
{ "entityType": "artists", "mode": "incremental" }
```

### 🔄 Incremental (delta) mode

Every run classifies each item as `new`, `changed` (price/status moved, for artworks; catalog
size/sold-count/avg-price moved, for artists), `unchanged`, or `delisted`, using a state baseline
persisted in a named Apify Key-Value Store scoped to `entityType`.

- `mode: auto` (default) — first run for a scope pushes everything (`full`); later runs push only
  `new`/`changed`/`delisted` (`incremental`).
- A `startUrls`-scoped run is always partial and never updates the baseline.

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

### 🚀 How to scrape Artfinder.com

1. Open the Artfinder Artist & Artwork Scraper in Apify Console and go to the **Input** tab.
2. Pick `entityType` (`artworks` or `artists`).
3. `maxItems` defaults to 50 (a quick preview) — raise it or clear it (set to `null`) for a full,
   uncapped run.
4. For `entityType: artists`, optionally set `minSoldArtworks` to only get artists who've sold
   something.
5. Click **Start**.
6. When the run finishes, browse results in the **Output** tab, or download as JSON/CSV/Excel, or
   fetch them via the API.
7. To track over time instead of scraping once: create a **Schedule** with `mode: auto` — the
   first run does a full scan, every run after only bills what actually changed.

### Known gaps

- **`country` isn't populated for a `startUrls`-scoped artists run.** The full field set (name,
  country, URL) comes from the artist-search discovery pass, which a `startUrls`-scoped run skips
  entirely — the artist's identity is instead recovered from their own artwork records (which do
  carry name, but not country). Not populated in that scope only, not a bug.
- **No `joined_date`, social links, or bio.** Those only exist on an artist's own profile page,
  a second per-artist request this Actor deliberately doesn't make (see `src/artfinder.py`'s module
  docstring) since they weren't needed for the fields this Actor was built to report. Worth
  revisiting if a customer needs them.
- **No `latest_artwork_*` fields**, unlike this Actor's SaatchiArt/Artmajeur siblings — the rollup
  data source has no reliable timestamp to determine "latest" from (only an `is_new` boolean, and
  an unconfirmed default sort order), so this Actor doesn't report an unverified guess.
- **`avg_price_eur`/`sold_avg_price_eur` are converted display prices**, not each artwork's real
  native currency — the per-artist rollup endpoint doesn't expose a native-currency marker the way
  the artwork detail page does (which is why `artworks` records report real `price` + `currency`
  instead).

### 🔗 You might also like

- **[SaatchiArt Artist & Artwork Scraper](https://apify.com/artsiom_k/saatchiart-scraper)** — the
  same entity-type/rollup-stats design pointed at a first, independent art marketplace.
- **[Artmajeur Artist & Artwork Scraper](https://apify.com/artsiom_k/artmajeur-scraper)** — a
  second independent art marketplace source, same design.
- **[Artsper Artist & Artwork Scraper](https://apify.com/artsiom_k/artsper-scraper)** — a third,
  independent art marketplace source, same design.

### ❓ FAQ

**Is it legal to scrape Artfinder.com?** It's legal to collect publicly available marketplace data
such as artwork listings, prices, and public artist profile information. Scrape it only with a
legitimate purpose under GDPR.

**How do I get only artists who've sold something?** Set `minSoldArtworks: 1` — see
[Input examples](#input-examples).

**How do I get only new/changed items?** Use `mode: auto` (or `incremental`) on a schedule — see
[Incremental mode](#incremental-delta-mode).

### Search keywords

artfinder scraper, artfinder.com scraper, art marketplace scraper, artist data scraper, artwork
data scraper, art price data, art market analytics, art collector data feed

# Actor input Schema

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

Scrape artworks or artist profiles. Each produces a different output shape (see dataset\_schema.json).

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

Optional list of specific Artfinder artist (https://www.artfinder.com/artist/{slug}/) or product (https://www.artfinder.com/product/{slug}/) URLs to scrape directly, instead of discovering via the artist-search API or the product sitemap — use whichever kind matches entityType. A start\_urls-scoped run cannot detect delistings or update the incremental baseline (see docs/incremental-mode.md), and for entityType=artists it skips the artist-search pass entirely, so the `country` field is not populated for that scope (see README known gaps).

## `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 run covers the entire site (~144,000 artworks, or the full ~3,785-artist directory as of this build — real-tested at ~1.8s/artist including rollup, so under 2 hours end to end) — raise this or clear it for that.

## `minSoldArtworks` (type: `integer`):

Only push artist records with at least this many sold artworks (rollup field sold\_artworks). Set to 1 to skip artists who have never sold anything. Artists are still fully tracked internally either way (delta baseline, delisted-detection), so this only filters what gets pushed to the dataset, not the incremental logic. Ignored when entityType is "artworks".

## `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. "incremental": always push only new/changed items. Only a plain, unscoped run (no startUrls) can detect delistings or update the baseline — see docs/incremental-mode.md.

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

Apify Proxy configuration. Leave off unless you start seeing blocks from your own IP reputation — this actor's default (plain browser User-Agent, no TLS impersonation) worked cleanly from every network tested while building it.

## Actor input object example

```json
{
  "entityType": "artworks",
  "maxItems": 50,
  "minSoldArtworks": 0,
  "mode": "auto",
  "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/artfinder-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/artfinder-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/artfinder-scraper --silent --output-dataset

```

## MCP server setup

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