# Kakao Map Scraper (KakaoMap Place Search) (`magenta_courser/kakaomap-place-search-scraper`) Actor

Search Kakao Map (KakaoMap) by keyword and get Korean business listings: restaurants, cafes, clinics and shops in Seoul and all of South Korea, with name, category, address, phone, coordinates, rating and review counts. Same output fields as our Naver Map Scraper.

- **URL**: https://apify.com/magenta\_courser/kakaomap-place-search-scraper.md
- **Developed by:** [SUNGHWAN CHO](https://apify.com/magenta_courser) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 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

## Kakao Map Place Search Scraper

Enter a Korean search keyword such as `강남 맛집` (Gangnam restaurants) and get up to 500 **Kakao Map** places per query: name, category, address, phone, coordinates, rating and review counts, with optional opening hours and menu prices. Restaurants, cafes, clinics, salons, shops and any other local business in South Korea.

Kakao Map is one of Korea's two major map services, alongside Naver Map. Each keeps its own ratings, review counts and ranking, so checking both gives a fuller picture of a local market. This Actor returns **the same input and the same field names, in the same order, as [Naver Place Search Scraper](https://apify.com/magenta_courser/naver-place-search-scraper)**, including `source` (`kakao` here, `naver` there). Run both with the same keywords and append the two datasets into one table, with no field mapping.

### What you get

For each search query, up to 500 places in Kakao's own ranking order:

- Name, category, full category path and business type
- Road address, lot-number address, district and postal code
- Phone number and homepage
- Latitude and longitude (WGS84)
- Kakao rating (out of 5), rating count, blog review count
- Booking availability, Kakao Talk channel, new-opening flag
- Amenities (parking, Wi-Fi, takeout, delivery, pets, …) and keyword tags
- Image URLs and photo count
- Direct Kakao Map URL

With `includeDetails` turned on, each place also gets:

- Open status right now, opening hours by day, days off
- Menu items with prices in KRW
- Review keyword counts (taste, kindness, mood, value, parking)
- Kakao Talk channel URL

No login, no API key and no browser needed.

### Use cases

- **Complete Korean local data** — combine with Naver Place Search Scraper to cover both Korean maps in one schema
- **Lead generation** — build lists of Korean businesses by area and category, with phone numbers, homepages and addresses
- **Market research** — count and compare competitors in a neighborhood, including menu prices, before entering the Korean market
- **Local SEO** — check where a business ranks on Kakao Map for a keyword, next to its Naver rank
- **Location data for AI agents and apps** — feed Korean place data with coordinates into your own pipeline

### Input

| Field | Description |
|---|---|
| `queries` | Search keywords, one per line. Combine an area with a category in Korean: `강남 맛집` (Gangnam restaurants), `성수 카페` (Seongsu cafes), `해운대 맛집` (Haeundae restaurants), `홍대 미용실` (Hongdae hair salons). |
| `maxPlacesPerQuery` | Maximum places per query (1–500). Default 50. |
| `includeDetails` | Add opening hours, days off, menu with prices, review keyword counts and Kakao Talk channel URL. One extra request per place, about 4 times slower. Default `false`. |
| `proxyConfiguration` | Proxy settings. Keep the Apify Proxy enabled; the default datacenter proxy is enough. |

```json
{
  "queries": ["강남 맛집", "성수 카페"],
  "maxPlacesPerQuery": 100,
  "includeDetails": false
}
```

The input is the same as Naver Place Search Scraper's (`queries`, `maxPlacesPerQuery`, `proxyConfiguration`), so you can send the same JSON to both Actors. `includeDetails` is Kakao only.

### Output

One dataset item per place. Example from a real run (`imageUrls` shortened here; a run returns up to 10):

```json
{
  "source": "kakao",
  "query": "홍대 카페",
  "rank": 1,
  "id": "27194461",
  "name": "제비다방",
  "category": "테마카페",
  "businessType": "음식점",
  "roadAddress": "서울 마포구 와우산로 24",
  "address": "서울 마포구 상수동 330-12 지하1층, 1층",
  "district": "서울 마포구 상수동",
  "phone": "02-325-1969",
  "latitude": 37.54661695,
  "longitude": 126.92310099,
  "visitorReviewScore": 4.5,
  "visitorReviewCount": 145,
  "blogCafeReviewCount": 184,
  "bookingReviewCount": null,
  "hasBooking": false,
  "hasNaverPay": null,
  "talktalkUrl": null,
  "amenities": ["주차", "무선 인터넷", "포장"],
  "promotion": null,
  "isNewOpening": false,
  "imageUrl": "https://t1.kakaocdn.net/local/kakaomapPhoto/review/7e53b0990cdebfd4c17b08481babb1fdd97a3b72?original",
  "imageUrls": [
    "https://t1.kakaocdn.net/local/kakaomapPhoto/review/7e53b0990cdebfd4c17b08481babb1fdd97a3b72?original",
    "https://t1.kakaocdn.net/local/kakaomapPhoto/review/a80a954c441d67b725c37e701c1aacbf8d35c89b?original"
  ],
  "imageCount": 1021,
  "url": "https://place.map.kakao.com/27194461",
  "categoryPath": "음식점 > 카페 > 테마카페",
  "keywords": ["라이브공연", "라이브바"],
  "homepage": "https://www.ctrplus.com/jebi",
  "zipCode": "04075",
  "hasKakaoChannel": false,
  "openStatus": null,
  "openingHours": null,
  "daysOff": null,
  "menu": null,
  "keywordStrengths": null,
  "kakaoChannelUrl": null,
  "detailsStatus": "notRequested",
  "detailsErrorCode": null,
  "scrapedAt": "2026-10-01T10:00:20.443Z",
  "schemaVersion": 2
}
```

With `includeDetails: true`, the detail fields are filled. From a real run (`을지로 맛집`, Euljiro restaurants):

```json
{
  "name": "문경식당",
  "openStatus": "OPEN",
  "openingHours": ["월 11:00 ~ 21:30", "화 11:00 ~ 21:30", "수 11:00 ~ 21:30", "목 11:00 ~ 21:30", "금 11:00 ~ 21:30"],
  "daysOff": "매주 토요일, 일요일, 26/10/5 개천절 대체공휴일, 공휴일",
  "menu": [
    { "name": "하이포크 삼겹살 (180g)", "price": 17000 },
    { "name": "고등어백반", "price": 10000 },
    { "name": "삼치백반", "price": 10000 },
    { "name": "굴비백반", "price": 10000 },
    { "name": "비빔밥", "price": 10000 }
  ],
  "keywordStrengths": { "taste": 10, "kindness": 7, "value": 5, "mood": 5 },
  "kakaoChannelUrl": "https://pf.kakao.com/_cvjxan",
  "detailsStatus": "ok",
  "detailsErrorCode": null
}
```

`detailsStatus` tells why detail fields are `null`:

| `detailsStatus` | Meaning | Billed for details |
|---|---|---|
| `notRequested` | `includeDetails` was off | No |
| `ok` | Detail page loaded; a `null` detail field means the business did not publish it | Yes |
| `notAvailable` | Detail page loaded but had no hours, menu, keyword counts or channel | No |
| `failed` | Detail page could not be loaded after retries; `detailsErrorCode` says why (e.g. `HTTP_403`, `EMPTY_BODY`, `ETIMEDOUT`) | No |

Every row has every field, always in this order. A value Kakao did not provide is `null`, never a missing key. For `visitorReviewCount` and `blogCafeReviewCount`, `0` means Kakao reports zero and `null` means Kakao did not provide the count. `schemaVersion` changes when fields are added or their meaning changes (version 2, 2026-10-01: added `detailsStatus`, `detailsErrorCode` and `schemaVersion`; missing counts are now `null` instead of `0`).

Export the dataset as JSON, CSV or Excel, or read it through the Apify API.

#### Field compatibility with Naver Place Search Scraper

| | Fields |
|---|---|
| Same name, same meaning, filled from Kakao | `query`, `rank`, `id`, `name`, `category`, `businessType`, `roadAddress`, `address`, `district`, `phone`, `latitude`, `longitude`, `visitorReviewScore`, `visitorReviewCount`, `blogCafeReviewCount`, `hasBooking`, `amenities`, `isNewOpening`, `imageUrl`, `imageUrls`, `imageCount`, `url`, `scrapedAt` |
| Naver only, always `null` here | `bookingReviewCount`, `hasNaverPay`, `talktalkUrl`, `promotion` |
| In both Actors | `source` (`"kakao"` here, `"naver"` there), `schemaVersion` |
| Added in this Actor | `categoryPath`, `keywords`, `homepage`, `zipCode`, `hasKakaoChannel`, the six detail fields, `detailsStatus`, `detailsErrorCode` |

Differences to keep in mind when merging:

- `businessType` is a Korean top-level category here (for example `음식점` = food and drink, which includes cafes; the finer type is in `category` and `categoryPath`), while Naver returns an English code (`cafe`).
- `roadAddress` includes the city and district here (`서울 마포구 와우산로 24`); Naver starts at the street name.
- `visitorReviewScore` and `visitorReviewCount` are Kakao's own star ratings, not Naver's visitor reviews.
- **Use `source` + `id` as the row key.** `id` is the Kakao place ID; Naver and Kakao IDs are separate systems, so the same number can mean different places. (Naver rows from runs before 2026-10-01 have no `source`; treat an empty `source` as `naver`.)
- **Decide that two rows are the same business by phone number, address and coordinates**, not by name. Many different businesses share a name (chains, common names like `스타벅스` or `김밥천국`). If phone, address and coordinates do not agree, keep the rows separate.

### Run summary: did every query finish?

Each run saves a free summary in the key-value store as `RUN_SUMMARY` (the **Run summary** link on the Output tab, or `GET https://api.apify.com/v2/key-value-stores/{defaultKeyValueStoreId}/records/RUN_SUMMARY`). It is not a dataset row, so it is not billed. One entry per query, in the same format as Naver Place Search Scraper, plus detail counts:

```json
{
  "query": "을지로 맛집",
  "status": "completed",
  "stopReason": "maxPlacesReached",
  "reportedTotal": 1203,
  "returnedCount": 30,
  "coverageLimit": 30,
  "errorMessage": null,
  "detailsOk": 29,
  "detailsNotAvailable": 0,
  "detailsFailed": 1
}
```

- `status`: `completed`, `partial` (some places saved, then Kakao kept rejecting requests), `failed` (nothing saved for this query), `budgetLimited` (your "Maximum cost per run" was reached).
- `stopReason`: `maxPlacesReached`, `endOfResults`, `noResults`, `coverageLimitReached` (Kakao's limit of 500 places per query), `requestFailed`, `chargeLimitReached`, `notStartedChargeLimit`.

The run fails only when every query failed. If some queries fail, the other results are kept and the run succeeds; check `RUN_SUMMARY` to re-run just the failed ones.

### Tips

- **Put an area name in every query.** Without one (`맛집` alone), Kakao ranks results around central Seoul.
- **Go beyond 500 places** by splitting an area into smaller ones: instead of `서울 카페`, search `성수 카페`, `연남 카페`, `한남 카페`, and so on. Use the `id` field to remove duplicates.
- **Turn on `includeDetails` only when you need hours or menus.** It adds one request per place and is billed per place with details; places whose detail page fails to load are saved without details (`detailsStatus: "failed"`) and are not billed for it.
- Detail fields depend on what the business published. In a test run of 90 restaurants, opening hours, menu and keyword counts were present for all 90, a Kakao Talk channel URL for 38 and days off for 16. Missing values are `null`.
- **Sponsored listings are excluded**; `rank` reflects the organic order.
- Text fields such as names, categories, amenities and opening hours are returned in Korean, as shown on Kakao Map.
- Some businesses do not publish a phone number, homepage or rating; those fields are `null`.

### Reviews

Kakao place IDs do not work in [Naver Place Reviews Scraper](https://apify.com/magenta_courser/naver-place-reviews-scraper). To get reviews, run [Naver Place Search Scraper](https://apify.com/magenta_courser/naver-place-search-scraper) with the same keywords and pass its `id` values as `places`, e.g. `{"places": ["1922651675"], "maxReviewsPerPlace": 100}`. Its README has a step-by-step example.

### Notes

This Actor collects only publicly visible business listing data. It reads aggregate ratings and keyword counts only; it does not collect review texts, reviewer nicknames, user IDs or profile images, and it does not log in to any account.

Kakao Map has no public API with ratings, so this Actor uses the same JSON endpoints the Kakao Map website calls. If Kakao changes them, the Actor may stop working until it is updated.

# Actor input Schema

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

Keywords to search on Kakao Map, one per line. Combine an area and a category in Korean, e.g. 강남 맛집 (Gangnam restaurants), 성수 카페 (Seongsu cafes), 해운대 맛집 (Haeundae restaurants), 홍대 미용실 (Hongdae hair salons). Without an area name, Kakao ranks results around central Seoul.

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

Maximum number of places to return for each query, from 1 to 500. Default 50. Kakao Map stops returning results after 500 places per query.

## `includeDetails` (type: `boolean`):

true = also open each place page and add openStatus, openingHours, daysOff, menu (name and price in KRW), keywordStrengths and kakaoChannelUrl. Makes one extra request per place, so runs take about 4 times longer. Each place whose details loaded (detailsStatus "ok") is billed as an extra event; failed or empty detail pages are not. false (default) = these fields are null and detailsStatus is "notRequested".

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

Proxy settings. Keep the Apify Proxy enabled (the default datacenter proxy is enough); Kakao Map may reject repeated requests from one IP.

## Actor input object example

```json
{
  "queries": [
    "강남 맛집"
  ],
  "maxPlacesPerQuery": 50,
  "includeDetails": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

All scraped places as dataset items.

## `runSummary` (type: `string`):

Per-query status (completed, partial, failed, budgetLimited), stop reason, reported total and returned count. Not billed.

# 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": [
        "강남 맛집"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("magenta_courser/kakaomap-place-search-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": ["강남 맛집"],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("magenta_courser/kakaomap-place-search-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": [
    "강남 맛집"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call magenta_courser/kakaomap-place-search-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,magenta_courser/kakaomap-place-search-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/hxH2H8kcjlgcz01oU/builds/8LkTu7IKeQgLzLNSe/openapi.json
