# Artsy.net Artist, Artwork & Gallery Scraper (`artsiom_k/artsy-scraper`) Actor

Artsy.net artist, artwork, and gallery/dealer ("partner") scraper via Artsy's own public GraphQL API — real represented-artist and inventory data per gallery, real per-artist aggregate stats with no per-artwork fan-out, plus delta mode.

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

## artsy-scraper

Scrapes artists, artworks, and **galleries/dealers** ("partners") from [Artsy.net](https://www.artsy.net)
via Artsy's own public GraphQL API — real represented-artist lists and inventory data per gallery,
real per-artist aggregate stats with no per-artwork fan-out, and delta mode.

### Contents

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

### 🔑 Key features

- **Exhaustive, not keyword-search-only.** This is the difference that matters most: every other
  Artsy scraper on Apify requires a search query and returns whatever that query turns up. This one
  crawls Artsy's own sitemaps — 173,000+ artists, ~5,800 galleries/dealers, millions of artworks —
  so you get the *whole* catalog (or any slice of it via `startUrls`), not just what a keyword
  happens to surface.
- **The dealer/gallery angle is a first-class entity, not a bolted-on field.** `entityType:
  partners` returns each gallery's real, **fully-paginated represented-artist list AND inventory
  list** — not a page, not a sample, both walked to completion — plus real aggregate stats (total
  artists, total artworks). No other Artsy scraper on Apify offers this.
- **Delta (incremental) mode.** Every run classifies each item as new/changed/unchanged/delisted
  against a persisted baseline, so a repeat run only costs you for what actually changed. No other
  Artsy scraper on Apify has this either.
- **A real, documented GraphQL API under the hood, not HTML scraping.** Every field comes from
  Artsy's own public "Metaphysics" API, whose schema is open-source — richer, more reliable data
  than parsing rendered pages, and fast: validated at up to 190 requests/second with zero blocking.
- **Artist stats need no extra fetching at all.** `total_artworks`/`for_sale_artworks`/`follows` are
  real, server-computed numbers — one request per artist, not one request per artwork in their
  catalog.
- **Rich artwork data**: for-sale/sold status, hidden-price ("price on request") detection, listed
  price with currency, medium, dimensions, and the selling gallery/dealer.
- **Fast by default.** Concurrent fetching (tunable via `concurrency`, default 25) — 500 artworks in
  under 20 seconds in real testing.

### 📋 Output

One dataset item per artwork, artist, or partner, 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 artwork record:**

```json
{
  "source": "artsy",
  "entity_type": "artworks",
  "external_id": "stacy-daguiar-hybrid-plus-ladybug",
  "url": "https://www.artsy.net/artwork/stacy-daguiar-hybrid-plus-ladybug",
  "title": "Hybrid + Ladybug",
  "artist_name": "Stacy D'Aguiar",
  "partner_name": "Abend Gallery",
  "medium": "Acrylic on wood",
  "is_for_sale": false,
  "is_sold": true,
  "price_amount": 365,
  "price_currency": "USD",
  "price_display": "US$365",
  "sale_message": "Sold",
  "change_type": "new"
}
```

**Example artist record:**

```json
{
  "source": "artsy",
  "entity_type": "artists",
  "external_id": "andy-warhol",
  "url": "https://www.artsy.net/artist/andy-warhol",
  "name": "Andy Warhol",
  "nationality": "American",
  "gender": "male",
  "birthday": "1928",
  "deathday": "1987",
  "hometown": "Pittsburgh, PA, USA",
  "location": "New York, NY, USA",
  "bio": "Obsessed with celebrity, consumer culture, and mechanical reproduction, Pop artist Andy Warhol created some of the 20th century's most iconic images...",
  "total_artworks": 9651,
  "for_sale_artworks": 3294,
  "follows": 216033,
  "change_type": "new"
}
```

**Example partner (gallery) record:**

```json
{
  "source": "artsy",
  "entity_type": "partners",
  "external_id": "dc-moore-gallery",
  "url": "https://www.artsy.net/partner/dc-moore-gallery",
  "name": "DC Moore Gallery",
  "type": "Gallery",
  "city": "New York",
  "country": "US",
  "total_artists": 136,
  "total_artworks": 1412,
  "represented_artists": "Alicia Adamerovich, Eric Aho, ... Milton Avery",
  "represented_artist_urls": "https://www.artsy.net/artist/alicia-adamerovich, https://www.artsy.net/artist/eric-aho, ...",
  "artwork_titles": "Tile Roof (Gardenville), Heraldic Iris, ...",
  "artwork_urls": "https://www.artsy.net/artwork/..., https://www.artsy.net/artwork/..., ...",
  "change_type": "new"
}
```

`represented_artist_urls`/`artwork_urls` are real artsy.net URLs, same order as their `represented_
artists`/`artwork_titles` counterparts — feed them into a separate `entityType: artists` or
`entityType: artworks` run's `startUrls` to get full per-record detail (bio, price, dimensions,
etc.) for exactly the artists/artworks a given gallery carries. Both lists are paginated to
completion, not capped samples — but `artwork_titles`/`artwork_urls` can still come back shorter
than `total_artworks` for some galleries (confirmed live, sometimes even 0) — see
[FAQ](#faq) for why. A `partners` run itself still stays fast and reasonably cheap by design — it
doesn't fetch *full* per-record detail (bio, price, dimensions) for every represented artist or
every inventory item, only names/titles and URLs.

### 🔧 Input

| Field | Description |
|---|---|
| `entityType` | `artworks` (default), `artists`, or `partners`. |
| `startUrls` | Optional list of specific artsy.net artist/artwork/partner URLs to scrape directly instead of a full sitemap crawl. |
| `maxItems` | Stop after pushing this many items. Defaults to 50. Set to `null` for an uncapped run. |
| `concurrency` | How many items to fetch in parallel. Defaults to 25 — validated live with zero blocking well above this. |
| `mode` | `auto` (recommended), `full`, or `incremental` — see below. |
| `proxyConfiguration` | Off by default — no anti-bot blocking was found against this site. |

### 💡 Input examples

**Default preview** (50 artworks): leave input empty.

**All galleries/dealers, uncapped:**

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

**A specific artist:**

```json
{ "entityType": "artists", "startUrls": ["https://www.artsy.net/artist/andy-warhol"] }
```

### 🔄 Incremental (delta) mode

Every run classifies each item as `new`/`changed`/`unchanged`/`delisted` against a baseline
persisted between runs. `mode: auto` (the default) does a full scan on the first run for a given
`entityType`/scope, then only pushes new/changed items afterwards. Only a plain, unscoped sitemap
crawl (no `startUrls`) can detect delistings or update the baseline — see the linked doc below for
the full design.

### 🔗 You might also like

- **[SaatchiArt Artist & Artwork Scraper](https://apify.com/artsiom_k/saatchiart-scraper)** — a real
  per-artist rollup (avg price, medium mix, sold count) computed from an artist's full catalog, an
  independent art marketplace source.
- **[Artmajeur Artist & Artwork Scraper](https://apify.com/artsiom_k/artmajeur-scraper)** — same
  rollup-stats design, a second independent art marketplace.
- **[Artsper Artist & Artwork Scraper](https://apify.com/artsiom_k/artsper-scraper)** — same
  rollup-stats design, a third independent art marketplace.
- **[Artfinder Artist & Artwork Scraper](https://apify.com/artsiom_k/artfinder-scraper)** — same
  rollup-stats design, a fourth independent art marketplace.
- **[Bluethumb Artist & Artwork Scraper](https://apify.com/artsiom_k/bluethumb-scraper)** — a
  fifth independent art marketplace (Australia's largest), with real per-artist
  sold/available/total counts read from a single profile-page fetch, no rollup fan-out needed.
- **[Riseart Artist & Artwork Scraper](https://apify.com/artsiom_k/riseart-scraper)** — a sixth,
  independent art marketplace source, built entirely on the site's own public GraphQL search API
  (no HTML scraping, no per-item detail fetch), with a real per-artist rollup (total artworks, avg
  price, for-sale vs. enquire-only split).
- **[Kunsthaus ARTES Artist & Artwork Scraper](https://apify.com/artsiom_k/kunsthaus-artes-scraper)**
  — a seventh, independent art marketplace source (German gallery), with a real four-state
  availability signal (for sale, price-on-request, reserved, sold) read from the site's own
  schema.org microdata.
- **[Ars Mundi Artist & Artwork Scraper](https://apify.com/artsiom_k/ars-mundi-scraper)** — an
  eighth, independent art marketplace source (a sibling German gallery running the same underlying
  platform as Kunsthaus ARTES), also selling jewelry and decorative objects alongside
  paintings/sculptures.
- **[Artprice.com Artist & Artwork Scraper](https://apify.com/artsiom_k/artprice-scraper)** — a
  ninth, independent art marketplace source, built on Artprice.com's own public unauthenticated
  JSON APIs (artist directory + current marketplace listings), with real per-artist rollup stats
  and multi-currency pricing (EUR/USD/GBP/CNY/BTC/ETH) on every artwork.

### ❓ FAQ

**Why is `partner_name` sometimes empty on an artwork?** Not every artwork on Artsy is currently
listed by a gallery/dealer — some are archival records with no active seller attached.

**Why is `price_amount` null even though `is_for_sale` is true?** Many galleries mark high-value
work "Price on request" (`is_price_hidden: true`) rather than publishing a number — `sale_message`
will say so.

**Does a `partners` run give me full details for every artist/artwork a gallery carries?** No, and
deliberately so. A `partners` run gets you the *complete* represented-artist list and the *complete*
reachable inventory list — every one, paginated to completion, not a capped sample — but only
name/title + URL for each, not full per-record detail (bio, price, dimensions, etc.). Fetching full
detail for potentially hundreds of artists or thousands of artworks inline would turn a several-
second run into a much slower, much more expensive one, for data most customers don't need on every
single gallery in scope. Instead, use `represented_artist_urls`/`artwork_urls` from a `partners` run
as input to a follow-up `entityType: artists` or `entityType: artworks` run's `startUrls`, scoped to
exactly the artists/artworks you actually want full detail on.

**Why can `artwork_urls` be shorter than `total_artworks` — or even empty?** Confirmed live against
real galleries: Artsy's own aggregate artwork count for a partner (`total_artworks`) can be higher
than what's actually reachable through the public inventory listing this Actor uses — sometimes
substantially so (e.g. one real gallery reported 425 total artworks but 0 were reachable via the
public listing). This isn't a bug in this Actor: the gap looks to be private/unpublished inventory
that's only visible to the gallery's own Artsy account, not something available without a real
partner login. `total_artists`/`represented_artist_urls` don't have this gap in any case checked so
far — it's specific to the artwork inventory listing.

# Actor input Schema

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

Scrape artworks, artist profiles, or galleries/dealers ("partners" — real represented-artist lists and inventory samples). Each produces a different output shape (see dataset\_schema.json).

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

Optional list of specific artsy.net artist/artwork/partner URLs (e.g. https://www.artsy.net/artist/andy-warhol) to scrape directly, instead of discovering via the full site sitemap — use whichever kind matches entityType.

## `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 — 173K+ artists, millions of artworks, or the full ~5,800-partner directory — 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. "incremental": always push only new/changed items. Only a plain, unscoped sitemap crawl (no startUrls) can detect delistings or update the baseline.

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

How many artworks/artists/partners to fetch in parallel. Defaults to 25 — validated live against artsy.net with zero blocking well above this (up to 190 req/s in testing). Lower it only if you start seeing errors; there's no anti-bot reason to.

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

curl\_cffi browser TLS-impersonation target. No blocking was observed against artsy.net during scoping even without impersonation, but this defaults to "chrome" internally as a safety margin — override only if needed.

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

Apify Proxy configuration. Leave off — no anti-bot blocking was found against this site during scoping, so a proxy shouldn't be needed; only turn one on if you start seeing blocks from your own IP reputation.

## Actor input object example

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

```

## MCP server setup

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