# Incollect Dealer & Listing Scraper (`artsiom_k/incollect-scraper`) Actor

Scrape Incollect.com fine-art, decorative-arts, furniture, and jewelry listings from vetted dealers (price, dimensions, images, dealer link) and dealer/professional profiles (bio, address, tracked inventory), with built-in delta mode. No login required.

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

## incollect-scraper

Scrapes both **listings** (fine art, furniture & lighting, decorative arts, and jewelry pieces from
vetted dealers — price, dimensions, images, and a dealer link) and **dealer/professional profiles**
from [Incollect.com](https://www.incollect.com), a US-based marketplace for fine art, antiques, and
high-end design sourced from vetted dealers. 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 `listings` or `dealers`.
- **Choose which categories to include.** Set `categories` to one or more of the site's real
  top-level categories (Fine Art, Furniture & Lighting, Decorative Arts, Jewelry) to filter the
  results, or leave it empty to include all 4. This is a real discovery-level filter — a
  non-matching category is never fetched at all.
- **A real per-dealer inventory rollup, fed as your listings runs go.** Dealer profiles report a
  `tracked_listing_count`/`tracked_categories` accumulated from every listings run over time —
  genuine numbers, honestly labeled as "tracked so far", not a capped or guessed total.
- **Real dealer bio, full postal address, and logo** — read from each dealer's own page, not
  approximated from listing text.
- **Delta mode built in for listings.** 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.
- **`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, or narrow `categories`
  instead.
- **No proxy needed.** A real burst test (up to 100 concurrent requests from a single IP) found no
  IP-based rate limiting at all — see the FAQ.

### 📋 Output

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

**Example listing record:**

```json
{
  "source": "incollect",
  "entity_type": "listings",
  "external_id": "177922",
  "url": "https://www.incollect.com/listings/fine-art/sculpture/alex-katz-maine-cow-177922",
  "category": "fine-art",
  "subcategory": "sculpture",
  "name": "Maine Cow",
  "maker_name": "Alex Katz",
  "width_value": 72.5,
  "width_unit": "INH",
  "height_value": 49,
  "height_unit": "INH",
  "depth_value": 10.25,
  "depth_unit": "INH",
  "currency": "USD",
  "price": null,
  "price_on_request": true,
  "dealer_name": "RoGallery",
  "dealer_url": "https://www.incollect.com/node/144054",
  "change_type": "new"
}
```

**Example dealer record:**

```json
{
  "source": "incollect",
  "entity_type": "dealers",
  "external_id": "rogallery",
  "url": "https://www.incollect.com/professionals/dealers/rogallery",
  "name": "RoGallery",
  "city": "Long Island City, NY",
  "region": "New York",
  "country": "United States",
  "tracked_listing_count": 14,
  "tracked_categories": ["fine-art"]
}
```

### 🔧 Input

| Field | Type | Default | Description |
|---|---|---|---|
| `entityType` | string | `listings` | `listings` or `dealers`. |
| `categories` | array | — | Listings only. Only crawl these categories (real discovery-level filter). Leave empty for all 4. |
| `startUrls` | array | — | Listings only. Specific listing URLs to scrape directly instead of the normal category-driven discovery. |
| `maxItems` | integer | `50` | Stop after pushing this many items. Set to `null` for a full crawl. |
| `mode` | string | `auto` | Listings only. `auto` / `full` / `incremental` — see [Incremental mode](#incremental-delta-mode). |
| `concurrency` | integer | `25` | How many pages to fetch in parallel. |
| `impersonate` | string | `chrome` | curl\_cffi TLS-impersonation target. |
| `proxyConfiguration` | object | off | Apify Proxy config — see FAQ for why this isn't needed by default. |

### 📥 Input examples

**Default preview (50 listings):**

```json
{ "entityType": "listings" }
```

**Every fine-art listing:**

```json
{ "entityType": "listings", "categories": ["fine-art"], "maxItems": null }
```

**Dealer profiles (with tracked inventory):**

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

### 🔁 Incremental (delta) mode

`auto` mode does a full scan the first time it runs for a given `categories` scope, then only pushes
new/changed items on later runs — including price changes (`price`/`price_on_request`/`currency` are
the watched fields). Only an uncapped run using the same `categories` selection each time (no
`maxItems` limit reached, no `startUrls`, no item errors) can detect delistings or update the
baseline. `entityType: "dealers"` has no delta mode — every run is a full live snapshot of the
currently-known dealer set.

### ❓ FAQ

**Why does `tracked_listing_count` grow over multiple runs instead of showing a fixed total?**
Incollect's own dealer directory only reveals about a dozen of its 560 real dealers without going
through the site's own AJAX-loaded pagination. Rather than block on that, dealer inventory counts
are accumulated honestly from your own listings runs over time — the more of the catalog you've
crawled, the more complete these numbers get. A brand-new account with no prior listings runs still
gets a real (if smaller) starting set from a static directory seed.

**Does this need a proxy?**
No. A real burst test (20 → 50 → 100 concurrent single-IP requests, no proxy) against live listing
pages came back 100% clean `200`/`404` responses — the site's declared `Crawl-delay: 10` in
`robots.txt` is the stock, unenforced Drupal boilerplate, not a real rate limit.

**How does discovery work?**
Via the site's own internal landing-page API (`/listing/ajax_get_landing_page_data`) — the same one
its own category browse pages use, confirmed unauthenticated and reliable. Each selected category is
paginated independently with a real item count, so results are both fast and accurate — no dead-link
guessing involved.

**Why is `price` sometimes `null` even though `price_on_request` is `false`... or the reverse?**
A listing with no visible price on the site reports `price: null, price_on_request: true` — this is
read directly from the page, never guessed. A genuine `$0` price does not occur on this site.

**What does `dealer_url` on a listing point to?**
The dealer's stable internal link as found directly on the listing page — it redirects to that
dealer's full public profile page. `entityType: "dealers"` resolves and reports the canonical,
human-readable profile URL instead.

# Actor input Schema

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

Scrape listings (fine-art, decorative-arts, furniture, and jewelry pieces from vetted dealers, with price/dimensions/images/dealer link) or dealer/professional profiles (bio, address, and a tracked inventory count/category footprint accumulated from listings runs). See dataset\_schema.json for the field set of each.

## `categories` (type: `array`):

Optional, listings only: only crawl listings in these categories. This is a real discovery-level filter — each selected category is queried independently via the site's own landing-page API, so a non-matching category is never fetched at all. Leave empty to include all 4 categories.

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

Optional, listings only: specific Incollect listing URLs (https://www.incollect.com/listings/{category}/{subcategory}/{slug}) to scrape directly, instead of the normal category-driven discovery. Overrides "Categories to include". A startUrls-scoped run cannot detect delistings, update the incremental baseline, or feed the dealer rollup as completely as a full crawl.

## `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. The site has ~139,763 real live listings across its 4 categories (Fine Art 22,079 / Furniture & Lighting 79,421 / Decorative Arts 34,150 / Jewelry 4,113) and ~560 dealers. Raise this or clear it (set to null) for a full run, or narrow "Categories to include" instead.

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

Listings only (dealers runs are always a live snapshot). "auto" (recommended): full scan on the first run for a given categories scope, incremental (new/changed only) afterwards. "full": always push every item and refresh the baseline — schedule this periodically to catch delistings and price changes. "incremental": always push only new/changed items. Only a plain, unscoped run (no startUrls, same "Categories to include" every time) can detect delistings or update the baseline.

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

How many pages to fetch in parallel. Defaults to 25 — a real burst test against live incollect.com (100 concurrent single-IP requests) came back with zero blocking of any kind (no 403/429/503, no CAPTCHA), so this is the portfolio's standard default, not a conservative override. Raise if you need faster runs at real scale; there is no known technical rate limit on this site.

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

curl\_cffi browser TLS-impersonation target. Defaults to "chrome" internally.

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

Apify Proxy configuration. Left off by default: incollect.com showed no IP-based rate limiting at all during investigation (up to 100 concurrent requests from one IP, zero blocks), so a proxy adds cost with no measured benefit here. Enable only if you start seeing blocks from your own IP reputation.

## Actor input object example

```json
{
  "entityType": "listings",
  "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/incollect-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/incollect-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/incollect-scraper --silent --output-dataset

```

## MCP server setup

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