# 2GIS Scraper: Places, Contacts & Reviews (privacy-safe) (`kyungminlee/twogis-places-reviews-scraper`) Actor

Unofficial 2GIS scraper, no login. Search places in any 2GIS city (Russia, Kazakhstan, UAE, Kyrgyzstan, Uzbekistan) past the 60-result limit, or paste place URLs. Address, coordinates, phones, website, socials, hours, rating and reviews with owner replies. No reviewer names.

- **URL**: https://apify.com/kyungminlee/twogis-places-reviews-scraper.md
- **Developed by:** [Kyungmin Lee](https://apify.com/kyungminlee) (community)
- **Categories:** Lead generation, Travel, Business
- **Stats:** 3 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 places

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?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

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

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

### What does 2GIS Scraper: Places, Contacts & Reviews do?

**2GIS Scraper: Places, Contacts & Reviews** collects public business listings, business contacts and reviews from **2GIS (2ГИС)**, the leading city directory and map in Russia, Kazakhstan, Kyrgyzstan, Uzbekistan, the UAE and other countries, without a 2GIS account, API key or browser. It is an **unofficial** tool and is not affiliated with, endorsed by or sponsored by 2GIS.

In one run you can:

- **Search places** by query — e.g. `кофейня`, `стоматология`, `автосервис`, `cafe` — in any 2GIS city: Moscow, Saint Petersburg, Novosibirsk, Kazan, Yekaterinburg and 1,100+ other Russian towns, Almaty, Astana, Bishkek, Tashkent, Dubai, Sharjah, Yerevan and more.
- **Go past the 60-result limit.** The 2GIS website stops every search at 60 places. This Actor automatically splits the city (or your bounding box) into map tiles and searches each tile, so one query can return **thousands of places**, each saved only once.
- **Look up specific places** by 2GIS URL (`2gis.ru/moscow/firm/…`, `2gis.kz`, `2gis.ae`, with or without a language prefix) or numeric place ID.
- Get the **place data**: name, categories, address, postcode, building, district, coordinates, rating, review and rating counts, brand and number of branches, opening hours (text and structured), attributes such as average bill, Wi-Fi or payment methods, photo count and promoted flag.
- Add the **contacts the business published on 2GIS**: phones (with notes like "delivery"), e-mails, websites (2GIS click-tracking removed) and social/messenger links (VK, Telegram, WhatsApp, Instagram, OK...).
- Collect **reviews** with star rating, text, dates, likes, photo count and the **business owner's reply**, in relevance or newest-first order, with star and date filters.
- Choose the **language** where the country site offers it: Kazakh on 2gis.kz, Arabic on 2gis.ae, Kyrgyz on 2gis.kg, Uzbek on 2gis.uz.

**Privacy by design:** reviewer names, user IDs, public profile IDs, avatars, profile links and "who reacted" lists are **never collected**. When an owner's reply (or the review itself) contains the reviewer's own name, it is replaced with `[name]`, and e-mail addresses and phone numbers inside review texts and replies are masked. Many other 2GIS scrapers export reviewer identities; this one does not.

### Why use this 2GIS scraper?

- **Lead generation** — every dentist, café or car service in a city with phones, e-mails, websites, messengers, hours and coordinates.
- **Market research and geo-analytics** — complete category coverage per city or per map rectangle thanks to the tile split, with districts and coordinates for mapping.
- **Reputation monitoring** — schedule a run for your own branches with `reviewSort: newest` and `reviewsSince` to get only new reviews and see which ones still lack a reply.
- **Competitor analysis** — compare ratings, review counts and brand-wide ratings across chains and cities.
- **Safer data handling** — no personal data about reviewers to store, secure or delete.

Places and reviews come from **one Actor and one dataset**. For Yandex Maps data, see the sister Actor *Yandex Maps Scraper: Places & Reviews*.

### How to scrape 2GIS places, contacts and reviews

1. Enter one or more **Search queries** and a **City** (slug such as `moscow`, `spb`, `almaty`, `dubai`, or a name such as `Москва`). You can also (or instead) paste **Place URLs or IDs**.
2. Set **Max places per query**: up to 60 come in 2GIS's own ranking; above 60 the city is split into map tiles. Optionally restrict the search to a **Bounding box**.
3. Keep **Include business contacts** on to get phones, e-mails, websites and social links (one extra request per place).
4. Set **Max reviews per place** (0 = none) and the **Review order**; optionally add star filters or a "published on or after" date.
5. Click **Start** and download the results as JSON, CSV, Excel or via the API.

**How the 60-result limit is handled:** the 2GIS website returns at most 5 pages × 12 places per search. The Actor first saves 2GIS's top 60 for the query, then searches the city's map area tile by tile with the website's own "search in this area" filter, starting at the city centre and moving outwards. Any tile that still has more than 60 matches is split into four smaller tiles (one zoom level closer) until every tile fits; tiles outside the city area are skipped. Places are de-duplicated by their 2GIS ID, so overlapping tiles never produce or charge duplicates. In a single building with more than 60 matches (a big mall) the Actor also reads the tile in alphabetical and rating order. In tests, "стоматология" in Kazan returned 586 places (2GIS shows 579 for the city search) and 1,500 places in Saint Petersburg in under 5 minutes.

The Actor reads the same public pages an anonymous visitor's browser loads. It does not log in, never solves captchas and only uses paths allowed by 2GIS's robots.txt. It runs two request streams, each sending about **one request every 0.8–1.5 seconds** with random jitter, and backs off when 2GIS refuses or delays requests. Review pages beyond the first 50 per place (and the newest-first order) come from 2GIS's reviews service, which the Actor calls at most about once every 3–5 seconds per run. Anything that fails after retries is logged and **never charged**.

Speed: about 12 places per search request, roughly 300 places per minute without contacts. With contacts or reviews the Actor opens each place's page (which also carries its first 50 reviews): roughly 50–80 places per minute.

### Input

| Field | Description |
|---|---|
| `searchQueries` | Queries as typed into 2GIS. Local-language terms are the most complete. |
| `city` | 2GIS city slug (`moscow`, `spb`, `kazan`, `almaty`, `astana`, `bishkek`, `tashkent`, `dubai`…), a Russian/English city name, or a 2GIS city URL. Required for searches. |
| `maxPlacesPerQuery` | 1–20,000 places per query (default 50). Above 60 the city is split into map tiles. |
| `placeUrls` | 2GIS place URLs or numeric place IDs. |
| `boundingBox` | `lat,lon,lat,lon` of two opposite corners inside the city; places outside are skipped and not charged. |
| `includeContacts` | Add the business's phones, e-mails, websites and social links (default on). |
| `maxReviewsPerPlace` | 0–10,000 reviews per place (default 0). |
| `reviewSort` | `relevance` (2GIS default) or `newest`. |
| `minReviewRating`, `maxReviewRating` | Keep only reviews with these stars (1–5). |
| `reviewsSince` | Keep only reviews written or edited on or after this date. |
| `includeReviewPhotoUrls` | Add URLs of review photos (off by default). |
| `language` | `auto` (site default) or `ru`, `en`, `kk`, `ky`, `uz`, `ar`, `az`, `hy`, `ka`, `tg`, `mn` where the country site offers it. |
| `proxyConfiguration` | Apify Proxy settings (off by default; 2GIS answers Apify's own IPs directly and faster). |

Example — all dentists in Kazan with contacts and the 20 newest reviews each:

```json
{
  "searchQueries": ["стоматология"],
  "city": "kazan",
  "maxPlacesPerQuery": 2000,
  "includeContacts": true,
  "maxReviewsPerPlace": 20,
  "reviewSort": "newest"
}
```

### Output

All results go to one dataset. Every item has a `type`:

- **`place`** — one item per place, with the `query` that found it, `foundBy` (`search` = 2GIS ranking, with `searchRank`; `mapTile` = found by the tile split; `placeUrl` = your input) and the `inputUrl` you entered. A place found by several queries or tiles is saved and charged only once per run.
- **`review`** — one item per review, linked to its place by `placeId`, `placeName` and `placeUrl`. Reviews follow their place item.

Use the **Places** and **Reviews** views in the Output tab, or filter on `type`. A `SUMMARY` record in the key-value store lists counts per query, map tiles searched and split, failed inputs, skipped duplicates and request statistics.

Place example (shortened):

```json
{
  "type": "place", "query": "кофейня", "searchRank": 2, "foundBy": "search",
  "placeId": "70000001007179642", "name": "Му-Му", "fullName": "Му-Му, кафе",
  "primaryCategory": "Кафе", "categories": ["Кафе", "Доставка еды", "Быстрое питание"],
  "address": "Манежная площадь, 1 ст2", "postcode": "125009", "buildingName": "Охотный ряд",
  "city": "Москва", "district": "Тверской", "latitude": 55.755301, "longitude": 37.614586,
  "rating": 4.8, "reviewCount": 1108, "ratingCount": 2090, "brandName": "Му-Му", "branchCount": 18,
  "workingHours": "Mon-Sun 08:00-23:00",
  "openingHours": {"Mon": ["08:00-23:00"], "Sun": ["08:00-23:00"]},
  "attributes": ["Чек 450 ₽", "Бизнес-ланч", "Бесплатный Wi-Fi", "Оплата картой"],
  "phones": [{"number": "+74957818781", "display": "+7 (495) 781-87-81", "comment": "служба доставки", "type": "phone"}],
  "emails": ["dostavka@cafemumu.ru"], "websites": ["http://cafemumu.ru"],
  "socialLinks": [{"type": "vkontakte", "url": "https://vk.com/cafemumu"}, {"type": "telegram", "url": "https://t.me/mumuchatbot"}],
  "contactsLoaded": true, "url": "https://2gis.ru/moscow/firm/70000001007179642"
}
```

Review example (shortened):

```json
{
  "type": "review", "placeId": "70000001007179642", "placeName": "Му-Му",
  "reviewId": "302554446", "rating": 5, "text": "В восторге",
  "dateCreated": "2026-10-05T17:34:59Z", "likesCount": 0, "photoCount": 0, "provider": "2gis",
  "ownerReply": "Здравствуйте! Большое спасибо, что нашли время поделиться впечатлениями…",
  "ownerReplyDate": "2026-10-06T09:35:08Z", "reviewSort": "relevance"
}
```

### Data fields

**Place:** `query`, `searchRank`, `foundBy`, `placeId`, `name`, `fullName`, `nameExtension`, `url`, `primaryCategory`, `categories`, `address`, `addressComment`, `postcode`, `buildingName`, `country`, `region`, `city`, `district`, `latitude`, `longitude`, `rating`, `reviewCount`, `ratingCount`, `brandRating`, `brandReviewCount`, `brandId`, `brandName`, `branchCount`, `workingHours`, `openingHours` (Monday first, 24-hour `HH:MM-HH:MM`, empty list = closed), `timezoneOffsetMin`, `attributes`, `photoCount`, `isPromoted` (paid placement), `updatedAt` (last 2GIS update), `citySlug`, `host`, `inputUrl`, `language`, `phones`, `emails`, `websites`, `socialLinks`, `contactsLoaded`, `scrapedAt`. Contact fields are `null` when contacts are switched off or the place page could not be loaded.

**Review:** `placeId`, `placeName`, `placeUrl`, `reviewId`, `rating` (1–5), `text`, `dateCreated`, `dateEdited` (UTC), `likesCount`, `commentsCount`, `photoCount`, `provider` (`2gis` or `flamp`), `isRated` (counted in the place rating), `ownerReply`, `ownerReplyDate`, `photoUrls` (optional), `reviewSort`, `scrapedAt`.

Not collected: reviewer name, user or public ID, avatar, profile link, review counts of the user, reactions with user names.

### Pricing

This Actor uses **pay per event** — you pay only for items that were saved:

| Event | Proposed price | What it covers |
|---|---|---|
| `place` | **$1.50 per 1,000 places** ($0.0015 each) | One place item without contacts (contacts off, or the business lists none) |
| `place-with-contacts` | **$2.50 per 1,000 places** ($0.0025 each) | One place item with at least one phone, e-mail, website or social link — charged *instead of* `place` |
| `review` | **$0.40 per 1,000 reviews** ($0.0004 each) | One review item |

The prices shown on the Actor's Store page are the ones that apply. There is no start fee. Failed searches, unknown places, refused requests, duplicates, places outside your bounding box and reviews removed by your filters are never charged. The run's maximum charge is checked before every item, so the Actor stops cleanly when your limit is reached.

Example: 1,000 cafés with contacts and 10 reviews each = 1,000 × $0.0025 + 10,000 × $0.0004 = **$6.50**.

### Tips

- **Complete coverage:** set `maxPlacesPerQuery` well above the number 2GIS reports in the log (e.g. 5,000) — the tile split stops by itself when every tile has been read. A 2GIS "city" often covers its whole surrounding region (Moscow includes nearby Moscow Oblast towns, Almaty includes towns of Almaty Region). The Actor works from the centre outwards, so smaller limits stay in the city itself; use `boundingBox` to stay inside an exact area, or filter on `city`/`district`.
- **Cheaper lists:** switch `includeContacts` off when you only need names, addresses, ratings and coordinates — it halves the requests and uses the lower `place` price.
- **New reviews only:** schedule the Actor with your place URLs, `reviewSort: newest` and `reviewsSince` set to the last run date. With "newest" the Actor stops reading as soon as it reaches older reviews.
- **Complaints only:** `maxReviewRating: 2`.
- **Several cities:** run once per city (or use Apify tasks), since 2GIS searches always run inside one city.
- **Many reviews per place, or newest-first for many places:** these need 2GIS's reviews service, which the Actor calls slowly on purpose (about one call of 50 reviews every 3–5 seconds, i.e. roughly 600–1,000 reviews per minute). The first 50 reviews of each place in relevance order need no extra calls. If the service keeps timing out, the Actor stops asking it for the rest of the run and lists the affected places in `SUMMARY.failedReviewPlaces` (nothing is charged for them); try again later or with Apify Proxy, which spreads the calls over several IPs.
- **Refused requests:** if the log shows repeated refusals (HTTP 403/429) or timeouts, switch on Apify Proxy in the input.

### FAQ

**Is this an official 2GIS API?** No. It is an unofficial scraper of publicly visible 2GIS data and is not affiliated with 2GIS. 2GIS can change its website at any time; the Actor is maintained, but results may briefly break after such changes.

**Do I need a 2GIS account or API key?** No. Nothing is logged in and no cookies or tokens of yours are used.

**Why does a search in 2GIS show 60 results but the Actor finds more?** The website limits every search to 5 pages of 12. The Actor repeats the search for small map areas and merges the results, as described above.

**Why are reviewer names missing?** On purpose. Review text is public, but this Actor does not collect who wrote it, so you can analyse feedback without handling reviewers' personal data. Please do not use the text to identify or contact reviewers.

**Are the phone numbers personal data?** They are the contacts each business chose to publish on its 2GIS listing. Use them in line with the laws that apply to you (for example, rules on unsolicited marketing).

**Which countries work?** Every country with a 2GIS city site; Russia, Kazakhstan, Kyrgyzstan, Uzbekistan and the UAE were tested. Paste a city URL if your city's slug is not recognised.

# Actor input Schema

## `searchQueries` (type: `array`):

What to search for on 2GIS, as you would type it, e.g. "кофейня", "стоматология", "автосервис", "cafe" or a brand name. Local-language terms return the most complete results. Each query is searched in the city below.

## `city` (type: `string`):

The 2GIS city to search in: its slug from a 2GIS URL ("moscow", "spb", "novosibirsk", "ekaterinburg", "kazan", "almaty", "astana", "bishkek", "tashkent", "dubai", "yerevan"...) or its name in Russian or English ("Москва", "Saint Petersburg", "Алматы"). You can also paste a 2GIS city URL such as https://2gis.kz/almaty. Required for search queries.

## `maxPlacesPerQuery` (type: `integer`):

How many places to save for each query. Up to 60 come in 2GIS's own ranking. Above 60 (the 2GIS per-search limit), the Actor splits the city into map tiles and searches each one, so you can collect thousands of places per query. A place found twice is saved and charged only once per run.

## `placeUrls` (type: `array`):

Specific places to collect, as 2GIS place URLs (https://2gis.ru/moscow/firm/70000001007179642, https://2gis.kz/almaty/firm/..., also with a language prefix like https://2gis.ae/ar/dubai/firm/...) or numeric place IDs (70000001007179642).

## `boundingBox` (type: `string`):

Only search inside this rectangle of the city, given as two opposite corners: latitude,longitude,latitude,longitude, e.g. "55.745,37.600,55.765,37.640". The box is split into map tiles like a whole city; places outside it are skipped and not charged. The box must lie inside the chosen city.

## `includeContacts` (type: `boolean`):

Open each place's 2GIS page to add the phones, e-mails, websites and social/messenger links the business published there. Costs one extra request per place and is charged as the "place with contacts" event (only when at least one contact is found). Turn off for a cheaper, faster list without contacts.

## `maxReviewsPerPlace` (type: `integer`):

Reviews to save for each place. 0 = no reviews. The first 50 come with the place page; more (and the newest-first order) are read from 2GIS's reviews service at a slower, polite pace. Each review is a separate dataset item and a separate review event. Reviewer names, IDs, avatars and profile links are never collected.

## `reviewSort` (type: `string`):

Order in which reviews are read. Use "Newest first" with "Only reviews published on or after" to monitor new reviews.

## `minReviewRating` (type: `integer`):

Only save reviews with at least this many stars (1-5). Skipped reviews are not charged.

## `maxReviewRating` (type: `integer`):

Only save reviews with at most this many stars (1-5), e.g. 2 to collect complaints only. Skipped reviews are not charged.

## `reviewsSince` (type: `string`):

Only save reviews written (or last edited) on or after this date (YYYY-MM-DD, UTC). Older reviews are skipped and not charged. With "Newest first" the Actor stops reading a place as soon as it reaches older reviews.

## `includeReviewPhotoUrls` (type: `boolean`):

Add the URLs of photos attached to each review. Off by default; the photo count is always included.

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

Language of categories, addresses and attributes, where the country's 2GIS site offers it (2gis.ru is Russian only; 2gis.kz offers Kazakh, 2gis.ae Arabic, 2gis.kg Kyrgyz, 2gis.uz Uzbek). Business names and review texts stay as written. An unavailable language falls back to the site default with a log message.

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

No proxy by default: 2GIS answers Apify's own IPs directly, about 4 times faster than through a proxy. The Actor paces requests (two streams, each about one request every 0.8-1.5 seconds with random jitter) and backs off when 2GIS refuses a request (HTTP 403/429). If refusals persist, switch on Apify Proxy (datacenter is enough): the Actor then uses three streams, moves to a new proxy session every 25 requests and immediately after a refusal.

## Actor input object example

```json
{
  "searchQueries": [
    "кофейня"
  ],
  "city": "moscow",
  "maxPlacesPerQuery": 12,
  "includeContacts": true,
  "maxReviewsPerPlace": 5,
  "reviewSort": "relevance",
  "includeReviewPhotoUrls": false,
  "language": "auto",
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `places` (type: `string`):

No description

## `reviews` (type: `string`):

No description

## `allItems` (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 = {
    "searchQueries": [
        "кофейня"
    ],
    "city": "moscow",
    "maxPlacesPerQuery": 12,
    "maxReviewsPerPlace": 5,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("kyungminlee/twogis-places-reviews-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 = {
    "searchQueries": ["кофейня"],
    "city": "moscow",
    "maxPlacesPerQuery": 12,
    "maxReviewsPerPlace": 5,
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("kyungminlee/twogis-places-reviews-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 '{
  "searchQueries": [
    "кофейня"
  ],
  "city": "moscow",
  "maxPlacesPerQuery": 12,
  "maxReviewsPerPlace": 5,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call kyungminlee/twogis-places-reviews-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,kyungminlee/twogis-places-reviews-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/XvS8hyXrYLVmIpGK3/builds/pEKeX2ouf7XqUka55/openapi.json
