# Yandex Realty Scraper (`normdata/yandex-realty-scraper`) Actor

Get Russian real estate listings from Yandex Realty: price with real change history, exact address with GPS and metro walk time, area, rooms, floor, building detail, seller info, and photos. Filter by city, price, and rooms, paste a search URL, or look up specific offers by ID.

- **URL**: https://apify.com/normdata/yandex-realty-scraper.md
- **Developed by:** [Norm Data](https://apify.com/normdata) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.20 / 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.
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

![Norm Data](https://i.ibb.co/rGbhM5Y8/Chat-GPT-Image-Sep-8-2026-02-20-50-PM.png)

## Yandex Realty Scraper

Get Russian real estate listings from **Yandex Realty (realty.yandex.ru)** with no login and no
API key: price with real price-change history, exact address with GPS and metro walk time, area,
rooms, floor, building and renovation detail, seller info, photos, and the full listing
description. Search by city, deal type, and category with real server-side filters, paste any
search URL built on the site itself, or look up specific offers by ID.

Here is one real row:

```json
{
  "offer_id": "7035113340557210816",
  "offer_type": "SELL",
  "category": "APARTMENT",
  "url": "https://homeapp.ru/apartment/5844479",
  "price": 25600000,
  "currency": "RUR",
  "price_per_meter": 345946,
  "price_trend": "DECREASED",
  "previous_price": 26000000,
  "has_price_history": true,
  "rooms_total": 3,
  "area_sqm": 74,
  "floor": 12,
  "floors_total": 12,
  "address": "улица Адмирала Лазарева, 40",
  "latitude": 55.54426,
  "longitude": 37.52325,
  "metro_station": "Улица Горчакова",
  "metro_time_minutes": 7,
  "metro_transport": "ON_FOOT",
  "seller_type": "AGENCY",
  "seller_name": "Homeapp",
  "total_images": 18,
  "scraped_at": "2026-09-12T00:00:00.000Z"
}
```

### What it does

Reads realty.yandex.ru's own server-rendered pages directly and normalizes every listing into a
flat row.

- **Search** filters the whole catalog: city, buy or rent, property type, room count, price
  range, and area range.
- **Search URL** lets you paste any search-results link built on realty.yandex.ru's own site -
  every filter it supports, not just the ones exposed below.
- **Look up** resolves exact offer IDs (or full offer URLs), one row per offer, additionally
  including a real dated price-history series that search rows don't carry.
- Any search can also fetch each result's price history in the same run (`includePriceHistory`)
  instead of needing a separate `lookup` call per offer ID afterwards.

Missing source values are returned as `null`, never invented.

### Why this scraper

- **Real price history, without a separate `lookup` step.** Fetch it directly from a search with
  `includePriceHistory` - a full dated series of every past price for a listing. Billed as its own
  event, same as other Actors in this category, because it genuinely costs more: measured directly
  on two real runs of the same query, turning it on made the run 5.6x slower and pull 13x more
  data (one extra full page fetch per row).
- **A price range that reaches further than the site's own 25-page pagination limit.**
  realty.yandex.ru caps one filtered search at 25 pages no matter how many listings match -
  confirmed live. This Actor automatically splits an oversized price range in half (using the
  real sampled prices already fetched, not a blind numeric midpoint) and searches each half
  independently, so a broad city-wide search can still reach a large row count instead of
  silently stopping at the first ~500.
- **Full listing detail in one request** - exact address, GPS coordinates, metro station with
  real walk time, building year/type/parking/heating, renovation state, and the complete
  description, not just the headline numbers.
- **Paste-a-URL flexibility.** Anything you can filter for on realty.yandex.ru's own site, you
  can run here.
- **No API key, no login, no browser.** Reads only what a normal visitor's page load reads -
  confirmed captcha-free across every real request made while building this Actor.

### How it compares

Checked directly against both other Yandex Realty scrapers on Apify (their own declared pricing
and README, not a guess):

| Capability | This actor | zen-studio/yandex-realty-scraper | sian.agency/yandex-realty-scraper |
|---|:--:|:--:|:--:|
| Apartments, houses, rooms, land, garages, commercial | yes | yes | yes |
| Search by city/price/rooms/area filters | yes | yes | yes |
| Paste a custom search URL | yes | not documented | yes |
| **Price history** | **fetch it from a search directly (`includePriceHistory`), no separate run** | separate paid add-on (own run/mode) | not documented |
| Phone/contact number extraction | **not included** | yes (paid add-on) | not documented |
| Automatic handling of the site's 25-page search limit | yes (price-range splitting) | yes (geographic splitting) | not documented |

The one honest gap: phone/contact reveal is a client-side action behind what looks like a
session/token flow on Yandex's own site - not attempted here rather than faked or guessed at.

### Use cases

- **Investment sourcing.** Filter and rank listings by price, area, and price-per-m² across a
  whole city or price band.
- **Market research.** Track how many listings and at what price a city/category/room-count
  combination carries over time.
- **Relocation and rental search.** Pull every match for a city with real metro-distance and
  building detail, beyond what a manual browse would cover.
- **Price-change tracking.** Use `has_price_history`/`price_trend` from a search sweep, or the
  full dated series from a `lookup` call, to watch specific listings.
- **Lead generation.** Compile seller/agency lists by city and category.
- **Portfolio/appraisal feeds.** Structured price, area, and location data ready for a valuation
  model or a map.

### Quickstart

Search: 2-3 room apartments for sale in Moscow, 15-30M RUB.

```json
{ "mode": "search", "citySlug": "moskva", "dealType": "kupit", "category": "kvartira", "roomsTotal": [2, 3], "priceMin": 15000000, "priceMax": 30000000, "maxItems": 200 }
```

Search URL: paste any link built on realty.yandex.ru's own filters.

```json
{ "mode": "url", "searchUrl": "https://realty.yandex.ru/sankt-peterburg/snyat/kvartira/?roomsTotal=1", "maxItems": 100 }
```

Look up specific offers, including full price history:

```json
{ "mode": "lookup", "offerIds": ["7035113340557210816", "https://realty.yandex.ru/offer/123456789012/"] }
```

Search, but with each result's price history fetched too - no separate `lookup` call needed:

```json
{ "mode": "search", "citySlug": "moskva", "category": "kvartira", "roomsTotal": [2], "maxItems": 50, "includePriceHistory": true }
```

### Input reference

| Field | Applies to | Description |
|---|---|---|
| `maxItems` | all | Ceiling on rows written. |
| `mode` | all | `search`, `url`, or `lookup`. |
| `citySlug` | search | Yandex's own city URL segment, e.g. `moskva`, `sankt-peterburg`, `kazan`. |
| `dealType` | search | `kupit` (buy) or `snyat` (rent). |
| `category` | search | `kvartira`, `dom`, `komnata`, `uchastok`, `garazh`, or `kommercheskaya-nedvizhimost`. |
| `roomsTotal` | search | Array of 1-5 (5 = "5+"). Empty means any. |
| `priceMin` / `priceMax` | search | Price range, RUB. |
| `areaMin` / `areaMax` | search | Area range, m². |
| `sort` | search | `DATE_DESC` for newest first (verified); other values pass through as-is. |
| `includePriceHistory` | search, url | Also fetch each result's price history (one extra request per row - off by default). |
| `searchUrl` | url | A full search-results URL from realty.yandex.ru. |
| `offerIds` | lookup | Offer IDs or full offer URLs. |

### Output reference (selected)

| Field | Description |
|---|---|
| `offer_id` / `offer_type` / `category` / `url` | Identity. |
| `price` / `currency` / `price_per_meter` / `price_trend` / `previous_price` | Pricing. |
| `rooms_total` / `area_sqm` / `living_space_sqm` / `kitchen_space_sqm` / `floor` / `floors_total` | Layout. |
| `address` / `latitude` / `longitude` / `metro_station` / `metro_time_minutes` | Location. |
| `building_year` / `building_type` / `parking_type` / `heating_type` / `renovation` | Building. |
| `seller_type` / `seller_name` | Who's listing it. |
| `description` / `total_images` / `image_urls` | Content. |
| `price_history` | Look-up mode only: `{ date, price }[]`, `null` on a search row. |
| `price_out_of_requested_range` | `true` when a search row's live price no longer fits the `priceMin`/`priceMax` you asked for (see FAQ), `false` when it does, `null` when no price filter was set or the row isn't from a filtered search. |
| `query` / `error` | Set on an unresolved look-up row. |
| `scraped_at` | ISO 8601 collection timestamp. |

### Run via API and CLI

```bash
curl -X POST "https://api.apify.com/v2/acts/USERNAME~yandex-realty-scraper/run-sync-get-dataset-items?token=<TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"mode":"lookup","offerIds":["7035113340557210816"]}'
```

```bash
apify call USERNAME/yandex-realty-scraper --input '{"mode":"search","citySlug":"moskva","category":"kvartira","maxItems":500}'
```

### Fetch results

```bash
curl "https://api.apify.com/v2/datasets/<DATASET_ID>/items?token=<TOKEN>&clean=true&format=json"
curl "https://api.apify.com/v2/datasets/<DATASET_ID>/items?token=<TOKEN>&clean=true&format=csv"
```

### Billing and limits

- **Pay per result.** Billed per row written.
- **No charge on failure.** A run that produces zero rows fails with a message and is not billed.
- An unresolved look-up offer ID still writes one `error: "not found"` row and is billed as a
  base row.
- A row flagged `price_out_of_requested_range: true` (see FAQ) is still a real, fully-detailed
  row and is billed normally - it's kept and flagged rather than silently dropped.
- `includePriceHistory` bills a separate event per row it's actually fetched for (nothing charged
  on a failed fetch - that row's `price_history` is just left `null`). It's a real, separate cost:
  measured directly, turning it on made an otherwise-identical run 5.6x slower and pull 13x more
  data, one extra full page fetch per row.
- **Free Apify plans** run the built-in 10-row sample only.

### FAQ and troubleshooting

**Do I need a Yandex account or API key?** No. realty.yandex.ru's own public pages are read
directly, with no key and no login.

**Why did my search return zero rows?** No listing matched the filters, or `citySlug` doesn't
match a real Yandex city segment - browse realty.yandex.ru directly and copy the segment right
after the domain to confirm it.

**Why can a broad search take longer than expected?** realty.yandex.ru itself caps one filtered
search at 25 reachable pages (500 rows), no matter how many listings actually match. A search that
needs more than that automatically splits its price range and searches each half separately,
which takes real extra requests - the honest tradeoff for reaching a larger `maxItems` instead of
silently stopping at ~500.

**Can this get phone numbers?** No. Revealing a number on realty.yandex.ru is a client-side action
behind what looks like a session/token flow, not a plain page read - not attempted here rather
than faked.

**Why is `price_history` `null` on search rows?** It's only present on a single offer's own page
(`lookup` mode). Search-result rows don't carry it at all in the source - confirmed live, not a
gap in how this Actor reads it. Set `includePriceHistory: true` to have a search run fetch it for
every result automatically, instead of needing a separate `lookup` call per offer ID afterwards.

**Why is `rooms_total` empty for some listings?** Studios and non-residential categories (land,
garages, some commercial listings) genuinely have no room count in the source.

**Why does `currency` say "RUR" instead of "RUB"?** That's the real value Yandex's own API
returns (an older-style code still used internally) - passed through as-is, not corrected to a
guess.

**Why does a row have a price outside the `priceMin`/`priceMax` I set?** realty.yandex.ru's own
search index can lag a listing's live price by minutes to hours. Verified directly: one real
listing swung between 22.4M and 26.4M RUB five times in six days (agencies appear to do this
deliberately, to surface under more price-filter searches over time as the market re-indexes).
Yandex's own filter can still match the listing at a price that was true when it was indexed,
while the live price shown by the time this Actor reads it has already changed again. Rather than
silently dropping that row (or silently keeping it with no explanation), it's kept and flagged
`price_out_of_requested_range: true` - check `price_trend`/`previous_price` on the same row for
the change that caused it. Measured at scale: about 2-3% of rows in a filtered search.

**How current is the data?** Read live from realty.yandex.ru on every run. It is not cached.

**Is this an official Yandex tool?** No. Independent, not affiliated with Yandex. It reads only
publicly available pages.

### Local development

```bash
bun install
bun test                       # offline: normalisation, URL building, splitting logic (real fixtures)
bun run src/main.ts            # reads storage/key_value_stores/default/INPUT.json
```

# Actor input Schema

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

Caps how many rows this run writes. Starts at 10 for a quick sample; raise it for a full run.

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

Search filters build a URL from the fields below. Search URL lets you paste a link built on realty.yandex.ru's own site with any filters it supports. Look up resolves exact offer IDs to full detail rows.

## `citySlug` (type: `string`):

Yandex's own city URL segment, e.g. moskva (Moscow), sankt-peterburg (St. Petersburg), novosibirsk, ekaterinburg, kazan, krasnodar, sochi, nizhniy\_novgorod. Find it by browsing realty.yandex.ru and copying the segment right after the domain.

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

Whether to search for-sale or for-rent listings.

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

The property category to search within.

## `roomsTotal` (type: `array`):

Pick one or more room counts. Leave empty for any room count. Studios and non-residential categories (land, garages) have no room count and ignore this.

## `priceMin` (type: `integer`):

Only listings at or above this price.

## `priceMax` (type: `integer`):

Only listings at or below this price.

## `areaMin` (type: `integer`):

Only listings with total area at or above this, in square meters.

## `areaMax` (type: `integer`):

Only listings with total area at or below this, in square meters.

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

Newest first is verified live against real listing dates. Other values are passed through as-is if you know Yandex's own sort codes.

## `includePriceHistory` (type: `boolean`):

Fetch each result's full price-history series too, instead of needing a separate Look up run per offer ID afterwards. One extra full page fetch per row - a real, separately-billed event (see Billing) - so off by default.

## `searchUrl` (type: `string`):

Paste a full search-results URL from realty.yandex.ru, built with the site's own filters (any city, category, or filter combination it supports).

## `offerIds` (type: `array`):

realty.yandex.ru offer IDs, or full offer URLs (e.g. https://realty.yandex.ru/offer/7035113340557210816/) - either works.

## Actor input object example

```json
{
  "maxItems": 10,
  "mode": "search",
  "citySlug": "moskva",
  "dealType": "kupit",
  "category": "kvartira",
  "roomsTotal": [],
  "includePriceHistory": false
}
```

# Actor output Schema

## `listings` (type: `string`):

Complete dataset with all scraped listings.

## `overview` (type: `string`):

Overview view with key fields displayed in a table.

# 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 = {
    "maxItems": 10,
    "mode": "search",
    "citySlug": "moskva",
    "dealType": "kupit",
    "category": "kvartira",
    "roomsTotal": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("normdata/yandex-realty-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 = {
    "maxItems": 10,
    "mode": "search",
    "citySlug": "moskva",
    "dealType": "kupit",
    "category": "kvartira",
    "roomsTotal": [],
}

# Run the Actor and wait for it to finish
run = client.actor("normdata/yandex-realty-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 '{
  "maxItems": 10,
  "mode": "search",
  "citySlug": "moskva",
  "dealType": "kupit",
  "category": "kvartira",
  "roomsTotal": []
}' |
apify call normdata/yandex-realty-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,normdata/yandex-realty-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/kzYiVhqkfg1INpCuX/builds/EvZPPZc8Eec7nmxhw/openapi.json
