# Domclick RU Property Scraper - Listings, Prices & Agent Data (`abotapi/domclick-scraper`) Actor

Extract property listings from domclick.ru, one of Russia’s largest real estate portals. Search by city or use listing/search URLs. Returns 70+ fields including price, area, rooms, floor, address, GPS, metro info, photos, seller and agency details, developer data, discounts, and mortgage flags.

- **URL**: https://apify.com/abotapi/domclick-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** Real estate, Lead generation, Automation
- **Stats:** 11 total users, 1 monthly users, 87.8% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 listing results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Domclick Property Scraper

Pull structured property data from Domclick, one of the largest property portals in Russia, in seconds. Search by city with rich filters, or paste any Domclick search or listing link and the scraper walks results forward. Every listing comes back as a flat JSON record with 70+ fields: price, price per square meter, area, rooms, floor, build year, full address, GPS, metro stations with walking time, the full photo gallery, the seller description, seller and agency details, verification badges, new building and developer info, discounts, and mortgage flags. Turn on extra details for a market valuation estimate, nearby places, and agent deal stats.

### Why this scraper

- 70+ fields per listing, far more than most alternatives expose
- Two modes: build a search from filters, or paste search and listing URLs verbatim
- All of Russia: any city or region, resolved automatically from its name
- Sale and rent, residential, commercial, and garage categories
- Filter by rooms, price, area, floor, and sort seven ways, all applied server side so counts stay accurate
- Optional extra details: market valuation estimate (low / mid / high), nearby places, and agent deal history
- Fast and inexpensive: an optimized data path keeps runs quick and light on proxy usage

### Data you get

> Sample shape, values are illustrative placeholders, not from a live listing.

| Field | Example |
|---|---|
| id | `0000000000` |
| url | `https://domclick.ru/card/sale__flat__0000000000` |
| dealType | `sale` |
| offerType | `flat` |
| price | `34999000` |
| squarePrice | `466653` |
| currency | `RUB` |
| area | `75` |
| rooms | `2` |
| floor | `14` |
| totalFloors | `17` |
| buildYear | `2026` |
| isApartment | `false` |
| address | `Москва, набережная Примерная, 1 к1` |
| latitude | `55.0000` |
| longitude | `37.0000` |
| nearestSubway | `Технопарк` |
| subwayWalkMinutes | `12` |
| subways | `[{ "name": "Технопарк", "walkMinutes": 12, "lineColors": ["#3EA332"] }]` |
| description | `Full seller description text appears here.` |
| photoCount | `11` |
| photos | `["https://img.dmclk.ru/vitrina/owner/00/00/000.jpg"]` |
| complexName | `Sample Complex` |
| buildingEndYear | `2026` |
| buildingPath | `https://msk.domclick.ru/building/sample-building` |
| localityPath | `https://msk.domclick.ru/sample-district` |
| subdomain | `msk` |
| sellerName | `Agent Name` |
| sellerCasId | `0000000` |
| isAgency | `true` |
| companyName | `Sample Company` |
| companyFiasId | `00000000-0000-0000-0000-000000000000` |
| isRosreestrApproved | `true` |
| isSberCollateral | `false` |
| hasDiscount | `true` |
| discountValue | `0` |
| hasFamilyMortgage | `true` |
| publishedDate | `2026-01-01T00:00:00+00:00` |
| marketPrice | `30280000` |
| minMarketPrice | `25740000` |
| maxMarketPrice | `34820000` |
| agentDealsCompleted | `31` |
| poiCount | `50` |

The full seller and agency block is also passed through verbatim as `sellerRaw` so no upstream field is ever dropped.

### How to use

Search one city:

```json
{
  "mode": "search",
  "locations": ["Москва"],
  "dealType": "sale",
  "category": "living",
  "offerType": "flat",
  "maxListings": 100
}
```

Search with filters:

```json
{
  "mode": "search",
  "locations": ["Санкт-Петербург"],
  "dealType": "sale",
  "rooms": ["2", "3"],
  "minPrice": 10000000,
  "maxPrice": 25000000,
  "minArea": 50,
  "sortBy": "price_asc",
  "maxListings": 200
}
```

Multiple cities with extra details (valuation, nearby places, agent stats):

```json
{
  "mode": "search",
  "locations": ["Москва", "Сочи", "Казань"],
  "fetchDetails": true,
  "maxPages": 3
}
```

Paste URLs:

```json
{
  "mode": "url",
  "urls": [
    "https://domclick.ru/search?deal_type=rent&category=living&offer_type=flat&address=1d1463ae-c80f-4d19-9331-a1b68a85b553",
    "https://domclick.ru/card/sale__flat__0000000000"
  ],
  "maxListings": 50
}
```

### Input parameters

| Parameter | Type | Default | Description |
|---|---|---|---|
| mode | string | `search` | `search` builds a query from filters; `url` reads pasted links |
| locations | array | `["Москва"]` | City or region names, or address GUIDs. Search mode only |
| dealType | string | `sale` | `sale` or `rent` |
| category | string | `living` | `living`, `commercial`, or `garage` |
| offerType | string | `flat` | `flat` or `layout` (new building plan) |
| rooms | array | `[]` | Any of `studio`, `1`, `2`, `3`, `4+` |
| minPrice / maxPrice | integer | none | Price range in rubles |
| minArea / maxArea | integer | none | Area range in square meters |
| minFloor / maxFloor | integer | none | Floor range |
| sortBy | string | `relevance` | `relevance`, `newest`, `updated`, `price_asc`, `price_desc`, `price_per_m2_asc`, `price_per_m2_desc` |
| urls | array | none | Domclick search or listing links. URL mode only |
| fetchDetails | boolean | `false` | Add valuation estimate, nearby places, agent stats (loads each card page; slower) |
| maxPages | integer | (empty) | Result pages per city or URL, about 30 listings each. Leave empty to walk every result page (the item cap and end-of-catalogue detection still bound the run). |
| maxListings | integer | `20` | Overall cap across all targets. 0 means unlimited. |
| proxy | object | Residential RU | Proxy configuration |
| resumeFromRunId | string | (empty) | A previous run ID or dataset ID to continue a full-catalogue walk across separate runs. Listings already collected there (matched by `id`) are skipped, so this run only saves the new ones. |
| incrementalMode | boolean | `false` | Recurring monitoring: remembers the previous run of the same search and only returns changed listings on later runs. See "Incremental mode" below. |
| stateKey | string | (empty) | Only used with `incrementalMode`. Identifies the saved baseline. Leave empty to derive it automatically from your search filters. |
| emitUnchanged | boolean | `false` | Only used with `incrementalMode`. Also return (and bill) listings with no changes, marked `changeType: UNCHANGED`. |
| emitExpired | boolean | `false` | Only used with `incrementalMode`. Also return (and bill) a row for a listing that's no longer found, marked `changeType: EXPIRED`. Only fires after a complete, uncapped, non-resumed scan. |

#### Resuming a full-catalogue walk

For an unbounded pull (`maxPages` empty, `maxListings: 0`), two things protect a long run:

- **`resumeFromRunId`** lets you continue in a brand-new run: paste the ID of a previous run (or its dataset), and this run skips every listing `id` already saved there, appending only new listings.
- A **checkpoint** is saved automatically as the run progresses. If the run is interrupted by an Apify platform migration or you click **Resurrect** on a failed run, it picks back up from where it left off in the *same* run, without re-saving (or re-charging for) listings it already collected. No input is needed for this; it's automatic.

#### Incremental mode (recurring monitoring)

Turn on `incrementalMode` for a search you run on a schedule (e.g. daily) and only want what changed since last time. The actor remembers a baseline of every listing it has seen for that search (keyed by `stateKey`, or automatically by a hash of your filters — mode, deal type, category, property type, rooms, price/area/floor range, sort, locations/URLs, and whether extra details are on) and classifies every listing it sees against that baseline:

- **NEW** — never seen before
- **UPDATED** — a real field changed (price, area, floor, rooms, status, discounts, seller/verification info, market valuation, etc.) — the `changedFields` array lists which ones
- **UNCHANGED** — nothing changed since last time; **skipped from the output (and not billed) by default** — turn on `emitUnchanged` to include these rows too
- **REAPPEARED** — was previously marked expired and is back
- **EXPIRED** — was seen before but is no longer found; only emitted when `emitExpired` is on, and only after a run that scanned every result page for every location/URL with no page or item cap and no resume in effect (a capped or partial run can't tell "gone" apart from "not reached yet", so it's skipped rather than guessed)

Every emitted row also gets `firstSeenAt` and `lastSeenAt` timestamps.

**How the item cap interacts with incremental mode — read this before you schedule a run.** `maxListings` counts rows **returned**, not rows examined. So on a later run the actor skips the unchanged listings it already knows and keeps walking deeper until it has filled your quota with listings you have not seen before. A daily run with `maxListings: 20` therefore keeps returning (and billing for) up to 20 rows — but they are 20 listings that are genuinely new to you, never repeats of what you already received. If you would rather a quiet run return **nothing** and cost almost nothing, run uncapped (`maxListings: 0`, `maxPages` empty): then a run that finds no changes returns zero rows and prints its scanned-but-unchanged count instead — the normal, healthy outcome of monitoring a stable search, not an error.

**What counts as a change:** `description`, `photos`, and `subways` are tracked for presence but not compared field-by-field in the baseline (they're dropped from the stored snapshot to keep it small at scale — see below); every other field, including `price`, `area`, `floor`, `rooms`, `status`, discounts, and promo/placement flags, is compared and will trigger `UPDATED` on a genuine change. Measured against a live search (two fetches of the same listings 8 seconds apart): the only field that ever differs without a real change is the internal scrape timestamp, which is excluded from comparison.

`incrementalMode` is a separate mechanism from `resumeFromRunId` and the automatic migration checkpoint above — all three can be used together. Using `incrementalMode` together with `resumeFromRunId` on a search that already has a saved incremental baseline is rejected (the two would disagree about which listings are "new"); remove `resumeFromRunId` or pick a different `stateKey` to start a separate monitoring baseline.

### Send results into your apps (MCP connectors)

Optionally pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step **after** the scrape — the Apify dataset is never changed.

**What gets written to the connector:** a condensed, human-readable **summary** of each record — not the full JSON. Each item becomes one entry with a **title** and its key fields flattened to plain text. The **complete record always stays in the Apify dataset**.

1. Authorize a connector once under **Apify → Settings → Integrations** (Notion, Linear, Airtable, or Apify).
2. Select it in the **"Pipe results into your apps"** input field. (If the picker is empty, you haven't authorized a connector yet.)
3. For **Notion**, also set `notionParentPageUrl` to the page where items should be created.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.

### Output example

> Sample shape, values are illustrative placeholders, not from a live listing.

```json
{
  "id": "0000000000",
  "url": "https://domclick.ru/card/sale__flat__0000000000",
  "dealType": "sale",
  "offerType": "flat",
  "price": 34999000,
  "squarePrice": 466653,
  "currency": "RUB",
  "area": 75,
  "rooms": 2,
  "floor": 14,
  "totalFloors": 17,
  "buildYear": 2026,
  "address": "Москва, набережная Примерная, 1 к1",
  "latitude": 55.0000,
  "longitude": 37.0000,
  "nearestSubway": "Технопарк",
  "subwayWalkMinutes": 12,
  "complexName": "Sample Complex",
  "sellerName": "Agent Name",
  "sellerCasId": 0,
  "isAgency": true,
  "companyName": "Sample Company",
  "companyFiasId": "00000000-0000-0000-0000-000000000000",
  "isRosreestrApproved": true,
  "hasDiscount": true,
  "photoCount": 11,
  "photos": ["https://img.dmclk.ru/vitrina/owner/00/00/000.jpg"],
  "publishedDate": "2026-01-01T00:00:00+00:00",
  "marketPrice": 30280000,
  "minMarketPrice": 25740000,
  "maxMarketPrice": 34820000,
  "agentDealsCompleted": 31,
  "poiCount": 50,
  "query": "Москва",
  "scrapedAt": "2026-01-01T00:00:00.000Z"
}
```

### Plan requirement

Domclick only accepts traffic from Russian residential connections. Use Apify Residential proxy with country set to RU, available on the Starter plan and above. On the free plan, which does not include residential proxy, runs will likely return zero items. The actor prints a clear notice at the start of a run when residential proxy is not available.

### Run timeout

The first request of a run has to complete a one time connection check, which can take up to about 75 seconds on a slow connection, and the actor retries on a fresh connection if it does not pass. Give a run at least 5 minutes (300 seconds) in Input > Run options. The default of 1 hour is plenty.

The actor watches the clock you give it. If a run is close to its timeout it stops trying and ends with the message "Ran out of run time before any result page could be read", instead of being cut off with no explanation. A run that could not read any page at all ends as **failed** with the reason, so a connection problem is never reported as a successful run that simply found nothing. An empty search that really has no matches still finishes successfully with zero rows.

### Verification

Re-verified against the live source on 2026-08-31 (build 1.1): a Moscow sale search reports its server-side total in the run log, and room/price filters genuinely narrow that total (2-room listings capped at 15,000,000 RUB report a much smaller total than the unfiltered search, monotone). Pasted search and listing links both resolve to real records with the full field surface, including price per square meter, metro walking times, seller and agency details, and the photo gallery.

# Actor input Schema

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

Pick how to start: build a search from filters, or paste Domclick URLs.

## `locations` (type: `array`):

City or region names to search, for example "Москва", "Санкт-Петербург", "Сочи". Each name is resolved automatically. You can also paste an address GUID directly. Only applies in Search mode.

## `dealType` (type: `string`):

Sale or rent.

## `category` (type: `string`):

Property category.

## `offerType` (type: `string`):

Flat (secondary market) or layout (new building plan).

## `rooms` (type: `array`):

Filter by number of rooms. Choose any combination.

## `minPrice` (type: `integer`):

Minimum price in rubles.

## `maxPrice` (type: `integer`):

Maximum price in rubles.

## `minArea` (type: `integer`):

Minimum total area in square meters.

## `maxArea` (type: `integer`):

Maximum total area in square meters.

## `minFloor` (type: `integer`):

Minimum floor.

## `maxFloor` (type: `integer`):

Maximum floor.

## `sortBy` (type: `string`):

Result ordering.

## `urls` (type: `array`):

Paste Domclick search links (domclick.ru/search?...) or listing links (domclick.ru/card/...). Multi-URL supported. Filter fields above are ignored in URL mode. Only applies in URL mode.

## `fetchDetails` (type: `boolean`):

Adds a market valuation estimate, nearby points of interest count, and agent deal stats to each listing. This loads each listing's full card page, so it is significantly slower and bills a per-listing surcharge. Best-effort: a listing whose card does not load in time keeps its base fields and is not charged for enrichment.

## `maxPages` (type: `integer`):

How many result pages (about 30 listings each) to walk per city or URL. Leave empty to walk every result page (the item cap and end-of-catalogue detection still bound the run).

## `maxListings` (type: `integer`):

Overall cap across all targets. 0 means unlimited.

## `proxy` (type: `object`):

Domclick only accepts Russian residential connections. Apify Residential with country RU is strongly recommended (Starter plan or higher).

## `resumeFromRunId` (type: `string`):

Paste a previous run ID or dataset ID to continue a full-catalogue walk across separate runs. Listings already collected there (matched by listing id) are skipped, so this run only saves the new ones. Leave empty for a normal fresh run. See also Incremental mode below.

## `incrementalMode` (type: `boolean`):

For scheduled/recurring runs against the SAME search: remembers what this actor saw last time and only returns NEW, UPDATED, or (if enabled below) EXPIRED listings on later runs, instead of the full result set every time. Off by default so a one-off run behaves exactly as before. See also resumeFromRunId above, which continues one specific interrupted run instead.

## `stateKey` (type: `string`):

Only used when Incremental mode is on. Identifies which saved baseline this run compares against. Leave empty to derive it automatically from your search filters (so two different searches never share a baseline); set it manually to reuse or separate a baseline explicitly.

## `emitUnchanged` (type: `boolean`):

Only used when Incremental mode is on. By default, listings with no changes since the last run are skipped and not returned or billed. Turn this on to also return them (marked changeType: UNCHANGED) — this returns and bills every listing every run, not just the changes.

## `emitExpired` (type: `boolean`):

Only used when Incremental mode is on. Turn this on to also return a row for a previously-seen listing that is no longer found (marked changeType: EXPIRED) — this returns and bills an extra row per removed listing. Only fires after a run that scans every result page with no page/item cap and no resume in effect; otherwise it is safely skipped so a partial run never wrongly marks a listing as gone.

## `mcpConnectors` (type: `array`):

Optionally send the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize a connector once under Apify → Settings → Integrations, then select it here. The connector receives a condensed, human-readable summary per item (title + key fields), not the full JSON — the complete record stays in the dataset. Leave empty to skip. Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com).

## `notionParentPageUrl` (type: `string`):

URL (or id) of the Notion page under which item pages are created. Required to enable the Notion export; ignored by other connectors.

## `maxNotifyListings` (type: `integer`):

Cap on items written to each connector per run. Does not affect the dataset.

## Actor input object example

```json
{
  "mode": "search",
  "locations": [
    "Москва"
  ],
  "dealType": "sale",
  "category": "living",
  "offerType": "flat",
  "rooms": [],
  "sortBy": "relevance",
  "urls": [
    "https://domclick.ru/search?deal_type=sale&category=living&offer_type=flat&address=1d1463ae-c80f-4d19-9331-a1b68a85b553"
  ],
  "fetchDetails": false,
  "maxListings": 20,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "RU"
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "maxNotifyListings": 50
}
```

# Actor output Schema

## `overview` (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 = {
    "mode": "search",
    "locations": [
        "Москва"
    ],
    "urls": [
        "https://domclick.ru/search?deal_type=sale&category=living&offer_type=flat&address=1d1463ae-c80f-4d19-9331-a1b68a85b553"
    ],
    "proxy": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "RU"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/domclick-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 = {
    "mode": "search",
    "locations": ["Москва"],
    "urls": ["https://domclick.ru/search?deal_type=sale&category=living&offer_type=flat&address=1d1463ae-c80f-4d19-9331-a1b68a85b553"],
    "proxy": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "RU",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/domclick-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 '{
  "mode": "search",
  "locations": [
    "Москва"
  ],
  "urls": [
    "https://domclick.ru/search?deal_type=sale&category=living&offer_type=flat&address=1d1463ae-c80f-4d19-9331-a1b68a85b553"
  ],
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "RU"
  }
}' |
apify call abotapi/domclick-scraper --silent --output-dataset

```

## MCP server setup

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