# 🧪 Yandex Maps Scraper — Businesses by Area (`thenetaji/yandex-maps-scraper`) Actor

Export organisations from Yandex Maps by name, category or address, centred anywhere in the world. Rows carry the rating with its two separate counts, phone numbers, websites, social links, opening hours, categories, amenities, nearby metro and coordinates.

- **URL**: https://apify.com/thenetaji/yandex-maps-scraper.md
- **Developed by:** [The Netaji](https://apify.com/thenetaji) (community)
- **Categories:** Business, Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.43 / 1,000 organisation listeds

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

## Yandex Maps Scraper

Organisations from Yandex Maps, searched by name, category or address, centred anywhere in the
world. Each row carries the rating with its two counts kept apart, the phone numbers, the
websites, the social and booking links, the opening hours with the current open state, every
category, the amenity features, nearby metro stations, the coordinates, the photo count — and
the organisation's own id and page address, which the Place and Reviews Actors take back
verbatim.

**The contact details are on the search row.** Most place directories hold phone numbers and
websites back for a per-business lookup; here a whole page of twenty-five organisations
arrives with them attached.

### Accepted input

`queries` is required and takes one search term per line — a business name, a category, or an
address. A term is never split on a comma. An address returns a `toponym` row rather than an
organisation, which is a real answer with no rating and no page of its own.

`latitude` and `longitude` set the centre of the search. **Send both or neither.** One without
the other is silently ignored by Yandex, which then answers from its own default region — with
no centre at all, the term `coffee` measured as returning a cafe in a small town in Krasnodar
Krai. They are two named fields rather than one pasted `lat,lon` string on purpose: Yandex's
own viewport parameter is longitude-first, and a transposed pair is how a search for New York
quietly returns results in Somalia.

`span` is the half-width of the search area in degrees, applied to both axes. `0.05` is
roughly a city centre. Leave it empty to let Yandex choose.

`bounds` decides what happens to results outside that area. `widen` is Yandex's own behaviour
and searches beyond it; `restrict` refuses them and needs a `span` to restrict to. Measured: a
Red Square query with a New York area returns Moscow's Red Square when widening and nothing
when restricting.

`language` picks the language titles, addresses and opening hours come back in: English,
Russian, Turkish or Ukrainian. That is a closed set — a locale outside it is refused upstream
rather than falling back, so offering more would be offering a failure.

`startPage` defaults to `0` and is where each term's walk begins.

`maxItems` defaults to `100` and caps **each term separately**. `0` means no limit — the walk
then continues until a page carries nothing new, which is the only honest end on this surface.
Why, is answered under Frequently asked questions.

There is no page-size input. It is always the twenty-five Yandex serves: it answers 26 with a
failure rather than clamping, and a smaller page divides one request's cost among fewer rows
for nothing.

```json
{
  "queries": ["coffee"],
  "latitude": 40.7484,
  "longitude": -73.9857,
  "span": 0.05,
  "bounds": "widen",
  "language": "en_US",
  "maxItems": 100
}
```

### Response fields

```json
{
  "query": "coffee",
  "page": 0,
  "language": "en_US",
  "total_result_count_estimate": 89,
  "id": "1018907821",
  "type": "business",
  "title": "Pushkin",
  "short_title": "Pushkin",
  "description": "Restaurant",
  "url": "https://yandex.com/maps/org/pushkin/1018907821/",
  "uri": "ymapsbm1://org?oid=1018907821",
  "seoname": "pushkin",
  "is_advert": false,
  "address": "Tverskoy Blvd, 26А",
  "full_address": "Moscow, Tverskoy Blvd, 26А",
  "postal_code": "125009",
  "country": "Russia",
  "latitude": 55.7649,
  "longitude": 37.6039,
  "geo_id": 213,
  "metro": [{ "name": "Пушкинская", "distance": 320 }],
  "categories": [{ "name": "Restaurant" }],
  "features": [{ "name": "Average bill", "value": "2500 ₽" }],
  "phones": [{ "type": "phone", "number": "+7 495 739-00-33" }],
  "websites": ["https://cafe-pushkin.ru"],
  "working_time_text": "Daily, 12:00 AM – 12:00 AM",
  "rating_value": 4.6,
  "rating_count": 15219,
  "review_count": 6001,
  "photo_count": 4210
}
```

Fields absent from a record are returned as null rather than omitted, so every row has the
same shape.

`rating_count` and `review_count` are **two different numbers and both are published**. Yandex
counts people who rated separately from people who wrote, and the second is the one [Yandex
Maps Reviews Scraper](https://apify.com/thenetaji/yandex-maps-reviews-scraper) pages through.
A single column called "reviews" would silently be one of the two.

`id` and `url` are both published so nothing ever has to build an address. Either is accepted
by the Place and Reviews Actors, which is how the three compose.

`type` is `business` for an organisation and `toponym` for an address or a street. A toponym
carries no rating, no phone numbers and no page of its own; searching for an address returns
one, and it is a real answer rather than a broken row.

`is_advert` is true on a paid placement inside the map results. Yandex renders those in the
same shape as organic rows with nothing distinguishing them, exactly as it does on the web
results.

`total_result_count_estimate` is named for what it is: **an estimate, and it grows as the walk
deepens.** One six-page walk of the same term reported 89, 114, 169, 227, 284 and 338. Do not
compute a page count from it — this Actor does not either.

`categories`, `features`, `phones`, `websites`, `metro`, `chain` and `working_status` are
republished in Yandex's own shape. There is no measured field list to rename them into, and
renaming fields nobody has seen populated publishes a permanently-null column and calls it a
schema.

### Behaviour on partial results

The walk ends on an empty page, or on a page that carried nothing new. There is no "has more"
signal here and the stated total grows as you page, so a page of repeats is the only honest
stop condition — and it is a real one, because a map page is stable: fetched twice from one
pinned route, the first page returned all twenty-five rows in the same order both times.

Rows repeat as the walk deepens and the repeats are dropped here. One measured six-page walk
returned 150 rows of which 100 were distinct, so a run that fetched six pages can legitimately
save a hundred rows. The run log says how many it skipped.

A term that answers with nothing is skipped with a line in the log and the rest of the list
still runs. One coordinate without the other, or restricting without an area size, stops the
run before any request is made.

Coverage is deepest in Russia and the CIS. A Moscow restaurant carried 15,219 ratings and
6,001 written reviews while Manhattan coffee shops carried none — that is real sparsity in
Yandex's data rather than a field this Actor failed to read.

### Frequently asked questions

**Why is there no total I can plan a harvest against?**
Because Yandex's own number is an estimate and it moves as you page: 89, 114, 169, 227, 284
and 338 across one six-page walk of the same query. Dividing it by the page size would ask for
pages that do not exist, and treating it as a row count would report a result set nearly twice
the size of the one you collected. It is published under a name that says it is an estimate,
and the walk stops on what it actually gets.

**Why did I get fewer rows than pages × 25?**
Because deeper pages repeat organisations from earlier ones and the repeats are dropped. That
is paging drift rather than resampling — a single page fetched twice is identical, in the same
order.

**I searched without a centre and got results from somewhere random.**
That is Yandex's default region answering. Set `latitude` and `longitude`; one without the
other is ignored rather than refused, which is why this Actor stops the run instead of letting
it happen.

**Why are latitude and longitude separate fields?**
Because Yandex's own viewport parameter is longitude-first. A single "lat,lon" string handed
through unchanged is how a request for New York returns results in Somalia. Two named fields
cannot be transposed by accident.

**Why is `restrict` refusing everything?**
Because it needs an area to restrict to and the results genuinely fall outside it. Widening is
Yandex's own behaviour and is usually what you want; restricting is for when a query would
otherwise pull in a famous match from another country.

**Can I get more than the languages offered?**
No. Those four are what Yandex Maps answers; anything else is a failure upstream rather than a
fallback to a default, so offering it would be offering a broken run.

**Is a Yandex account or API key required?**
No. No account, session cookie or key of any kind is supplied to the Actor or needed by it.

### Related Actors

[Yandex Maps Place Scraper](https://apify.com/thenetaji/yandex-maps-place-scraper) takes the
`id` or `url` from any row here and reads one organisation on its own. It costs a whole request
per organisation — twenty-five times a row here — so it is for looking up a specific business
rather than for reading an area.

[Yandex Maps Reviews Scraper](https://apify.com/thenetaji/yandex-maps-reviews-scraper) takes
the same `id` and exports the written reviews, with the reviewer's contributor level and
Yandex's own per-aspect summary.

[Yandex Search Scraper](https://apify.com/thenetaji/yandex-search-scraper) is the other half
of this site: web results with paid placements flagged. Unlike the map surface it resamples
between identical requests, and its README says so.

# Actor input Schema

## `queries` (type: `array`):

What to search for, one per line. A term is never split on a comma, so a phrase with one in it stays a single search. Each term is run separately and every row names the term it answered.

## `latitude` (type: `number`):

Where to centre the map search. Send latitude and longitude together or neither: one without the other is silently ignored by Yandex, which then answers from its own default region — measured, the term `coffee` with no centre at all returned a cafe in a small town in Krasnodar Krai.

## `longitude` (type: `number`):

The other half of the centre. It is a field of its own rather than half of a pasted `lat,lon` string on purpose — Yandex's own viewport parameter is longitude-first, and a transposed pair is how a search for New York quietly returns results in Somalia.

## `span` (type: `number`):

Half-width of the search area in degrees, applied to both axes. 0.05 is roughly a city centre. Leave it empty to let Yandex choose.

## `bounds` (type: `string`):

What to do with results outside the area. Widening is Yandex's own behaviour; restricting needs an area size to restrict to. Measured: a Red Square query with a New York area returns Moscow's Red Square when widening and nothing when restricting.

## `language` (type: `string`):

The language titles, addresses and opening hours come back in. This is a closed set: a locale outside it is refused upstream rather than falling back to a default, so offering more would be offering a failure.

## `startPage` (type: `integer`):

Which page to begin each term at, counted from 0, at up to 25 organisations a page. Useful for resuming a harvest.

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

Maximum organisations to save for each term. Set 0 for no limit — the walk then continues until a page carries nothing new, which is the only honest end on this surface because the stated match count is an estimate that grows as you page.

## Actor input object example

```json
{
  "queries": [
    "coffee"
  ],
  "latitude": 40.7484,
  "longitude": -73.9857,
  "span": 0.05,
  "bounds": "widen",
  "language": "en_US",
  "startPage": 0,
  "maxItems": 25
}
```

# Actor output Schema

## `dataset` (type: `string`):

All records scraped by this run

# 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 = {
    "queries": [
        "coffee"
    ],
    "latitude": 40.7484,
    "longitude": -73.9857,
    "bounds": "widen",
    "language": "en_US",
    "startPage": 0,
    "maxItems": 25
};

// Run the Actor and wait for it to finish
const run = await client.actor("thenetaji/yandex-maps-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 = {
    "queries": ["coffee"],
    "latitude": 40.7484,
    "longitude": -73.9857,
    "bounds": "widen",
    "language": "en_US",
    "startPage": 0,
    "maxItems": 25,
}

# Run the Actor and wait for it to finish
run = client.actor("thenetaji/yandex-maps-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 '{
  "queries": [
    "coffee"
  ],
  "latitude": 40.7484,
  "longitude": -73.9857,
  "bounds": "widen",
  "language": "en_US",
  "startPage": 0,
  "maxItems": 25
}' |
apify call thenetaji/yandex-maps-scraper --silent --output-dataset

```

## MCP server setup

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