# Cian RU – Property Listings, Search Filters & URLs (`abotapi/cian-ru-scraper`) Actor

Collect property listings from Cian.ru by search filters or direct URLs. Returns structured rows with listing URL, title, price, address, rooms, area, floors, photos, visible contact fields, coordinates when available, and optional detail-page enrichment.

- **URL**: https://apify.com/abotapi/cian-ru-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** Real estate, Developer tools, Automation
- **Stats:** 17 total users, 5 monthly users, 99.5% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 dataset items

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
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

## Cian.ru Property Scraper

Collect property listings from Cian by search filters or direct URLs. Every row comes fully populated in a single fast pass: listing URL, title, price, address, rooms, area, floors, photos, coordinates, and contact phones.

### Input

- `mode`: choose `search` for filters or `url` for pasted Cian URLs.
- `urls`: Cian search-result URLs, used in URL mode. The filters below are ignored in URL mode, so encode filters in the URL itself.

The next group applies to **search mode only**:

- `location` / `regionId`: search area. Moscow is region `1`, Saint Petersburg is region `2`.
- `operationType`: `sale` or `rent`.
- `category`: flats, rooms, houses, new buildings, and commercial categories.
- `sort`: default, price low→high, price per m² (asc/desc), area, newest, or walking time.
- `rooms`, `includeStudio`: room-count filters (studio supported).
- `minPrice` / `maxPrice` (RUB), `minArea` / `maxArea`, `minFloor` / `maxFloor`, `minFloors` / `maxFloors`.
- `advancedFilters`: JSON object of raw Cian query fields for power users, e.g. `{"repair":{"type":"terms","value":[2,3]}}`.

Output and limits (both modes):

- `maxItems`: maximum rows to save. Fewer can be returned when results or filters match fewer live records.
- `maxPages`: leave `0` for no page limit. The run stops at Max items.
- `fetchDetails`: load each saved listing's own detail page for richer fields not on the search card — agent/agency name, total view count, BTI-verified build year, renovation type, window view, balcony count, bathroom counts, parking type, and amenities. Off by default. **Billed as a detail-enrichment surcharge**, charged only for listings whose detail page was actually fetched successfully.
- `proxy`: residential proxy is recommended for this site.

#### Input parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| mcpConnectors | array | (empty) | Pipe results into your apps via Model Context Protocol (MCP) connectors (Notion, Linear, Airtable, Apify). Items sent as condensed summaries; complete records stay in the dataset. Leave empty to skip. |
| notionParentPageUrl | string | (empty) | Notion connector only: URL or id of the Notion page under which item pages are created. Required for the Notion connector; ignored by other connectors. |
| maxNotifyListings | integer | 50 | Cap on items written to each connector per run. Does not affect the dataset. |

#### Resume & recurring updates

- `resumeFromRunId`: continue ONE interrupted crawl. Paste a previous run ID (or dataset ID) and this run skips listings it already saved, appending only new ones.
- `incrementalMode`: for recurring/scheduled monitoring of the SAME search instead — remembers the previous run's results itself (keyed by your filters/URLs) and reports each listing as `changeType` `NEW` / `UPDATED` / `REAPPEARED` / `EXPIRED` / `UNCHANGED`. Off by default.
- `stateKey`: optional manual key for the incremental baseline. Leave empty to derive one automatically from your mode/filters/URLs (excluding `maxItems`/`maxPages`), so identical searches share a baseline and different ones never mix.
- `emitUnchanged`: also save (and charge for) listings that did not change. Off by default, so a recurring run only pays for what actually changed.
- `emitExpired`: also save (and charge for) a row for previously-tracked listings no longer found, marked `changeType: "EXPIRED"`. Only fires after a run that scanned the full tracked search — a capped, resumed, or partially-blocked run leaves previous state untouched instead of guessing.

`resumeFromRunId` and `incrementalMode` can combine to bootstrap a monitoring baseline from an interrupted crawl's dataset, but not once that baseline already has tracked listings (set a different `stateKey` for a separate campaign instead).

### Output

Each dataset row includes:

- `id`, `url`, `sourceUrl`, `title`, `jkName`
- `price`, `priceValue`, `pricePerMeter`, `currency`
- `address`, `district`, `metro`, `metroTime`
- `roomsCount`, `totalArea`, `livingArea`, `kitchenArea`, `floorNumber`, `floorsCount`, `buildYear`, `materialType`
- `dealType`, `category`, `offerType`, `isFromDeveloper`, `isPremium`
- `description`, `phoneNumbers`
- `latitude`, `longitude`
- `imageUrls`, `creationDate`, `addedLabel`, `scrapedAt`

When `fetchDetails` is on, a successfully enriched row also carries (and sets `detailStatus` to `ok`; a failed detail fetch still ships the base row with `detailStatus: "skipped"` and no extra fields, at no extra charge):

- `agentName`, `agentCompanyName`, `agentType`, `agentIsPro`, `agentOffersCount`, `agentAvatarUrl`
- `viewsTotal`, `viewsToday`
- `houseBuildYear` (BTI-verified, often populated when the search card's own `buildYear` is null), `houseEntrances`, `houseFlatCount`, `houseLiftsTotal`, `houseHeatSupplyType`, `houseGasSupplyType`, `houseIsEmergency`
- `repairType`, `windowsViewType`, `balconiesCount`, `bathroomsCombined`, `bathroomsSeparate`, `parkingType`, `amenities`

Incremental mode adds, only when `incrementalMode` is on:

- `changeType`: `NEW` / `UPDATED` / `REAPPEARED` / `EXPIRED` / `UNCHANGED` (the last only when `emitUnchanged` is on).
- `changedFields`: which fields differ from the previous run (empty for `NEW`/`UNCHANGED`/`REAPPEARED`/`EXPIRED`).
- `firstSeenAt`, `lastSeenAt`: when this actor first and most recently observed the listing.

Incremental change detection ignores `scrapedAt` (this actor's own per-fetch timestamp) and `addedLabel` (cian's relative "added" label, e.g. `"вчера, 14:40"` — it shifts with scrape time and the day boundary independently of any real listing change). Both fields are still present in every row; they are just excluded from the change comparison itself. Every other field, including `imageUrls` and `phoneNumbers`, is measured live and stays part of the comparison.

### 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.

### Notes

Use broad filters first, then narrow by price, rooms, and area. Phones, coordinates, descriptions, and photos are included on every row for free — no detail step needed for those. `fetchDetails` is only for the extra fields listed above (agent identity, view count, verified build year, and building/unit registry details) and is billed as a detail-enrichment surcharge. Residential proxy is recommended.

### Verification note (2026-08-31)

URL and search modes re-verified against the live site on 2026-08-31 using the actor's own session warm-up plus search API path: a pasted `https://www.cian.ru/kupit-kvartiru/` URL had its embedded jsonQuery extracted and replayed through the search API, returning 28 real Moscow listings; filter facets narrow server-side (one-room flats: 361,680 site total, adding a max price of 10M RUB narrowed it to 287,140, and the API result set for one-room flats shared only 6 of 28 page-1 ids with the unfiltered set). No defect found; version bumped so the Store listing carries a freshly dated, re-verified build.

# Actor input Schema

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

Choose search filters or direct listing/search URLs.

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

Cian listing URLs or search-result URLs. Used only in URL mode. Encode any filters directly in the URL, because the search filters below are ignored in URL mode.

## `location` (type: `string`):

Search mode only. City, region, district, metro, or address. Known defaults include Moscow and Saint Petersburg.

## `regionId` (type: `integer`):

Search mode only. Optional Cian region ID. Moscow is 1, Saint Petersburg is 2. Leave empty to resolve from Location when possible.

## `operationType` (type: `string`):

Search mode only. Deal type for search mode.

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

Search mode only. Listing category. Choose Any to let operation and property type decide.

## `sort` (type: `string`):

Search mode only. Sort results when supported by the selected source.

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

Search mode only. Room counts for flats and rooms.

## `includeStudio` (type: `boolean`):

Search mode only. Include studio listings together with selected room counts.

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

Search mode only. Minimum price in rubles.

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

Search mode only. Maximum price in rubles.

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

Search mode only. Minimum total area in square meters.

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

Search mode only. Maximum total area in square meters.

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

Search mode only. Minimum floor.

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

Search mode only. Maximum floor.

## `minFloors` (type: `integer`):

Search mode only. Minimum floors in the building.

## `maxFloors` (type: `integer`):

Search mode only. Maximum floors in the building.

## `advancedFilters` (type: `object`):

Search mode only. Raw Cian query objects merged into the search, e.g. {"repair":{"type":"terms","value":\[2,3]}}. For power users who know Cian's query field names.

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

Maximum matching rows to save. Fewer rows can be returned when the selected source and filters match fewer live records.

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

No page limit when set to 0. The run stops at Max items.

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

Load each saved listing's own detail page for richer fields not on the search card: agent/agency name, total view count, BTI-verified build year, renovation type, window view, balcony count, bathroom counts, parking type, and amenities. Billed as a detail-enrichment surcharge.

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

Proxy configuration. The default works on every plan. Switch groups if you start seeing access checks.

## `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`):

Maximum saved records to export to selected MCP connectors.

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

Continue ONE interrupted crawl: paste a previous run ID (or dataset ID) and this run skips listings it already saved, appending only new ones. For recurring monitoring of the SAME search across scheduled runs instead, use Incremental mode below.

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

Remember this search's results between scheduled runs and report only what changed (NEW / UPDATED / REAPPEARED / EXPIRED) instead of re-saving everything every time. Distinct from Resume from run above, which continues one specific interrupted crawl rather than tracking a recurring search.

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

Incremental mode only. Optional manual key identifying this tracked search. Leave empty to derive one automatically from mode/filters/URLs, so identical searches share a baseline and different ones never mix.

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

Incremental mode only. Also save and CHARGE for listings that did not change since the last run. Off by default so recurring runs only pay for what actually changed.

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

Incremental mode only. Also save and CHARGE a row for previously-tracked listings no longer found, marked changeType EXPIRED. Only fires after a run that scanned the full tracked search with no cap, resume, or unreadable URL truncating it early.

## Actor input object example

```json
{
  "mode": "search",
  "urls": [
    "https://www.cian.ru/kupit-kvartiru/"
  ],
  "location": "Москва",
  "operationType": "sale",
  "category": "",
  "sort": "default",
  "includeStudio": false,
  "maxItems": 20,
  "maxPages": 0,
  "fetchDetails": false,
  "proxy": {
    "useApifyProxy": true
  },
  "maxNotifyListings": 50,
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": 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 = {
    "mode": "search",
    "urls": [
        "https://www.cian.ru/kupit-kvartiru/"
    ],
    "location": "Москва",
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/cian-ru-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",
    "urls": ["https://www.cian.ru/kupit-kvartiru/"],
    "location": "Москва",
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/cian-ru-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",
  "urls": [
    "https://www.cian.ru/kupit-kvartiru/"
  ],
  "location": "Москва",
  "maxItems": 20
}' |
apify call abotapi/cian-ru-scraper --silent --output-dataset

```

## MCP server setup

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