# Naver (네이버) Search Scraper - Blog, Cafe & News (`lanternlab318/naver-search`) Actor

Search Naver by Korean or English keyword: public blog, cafe (카페) and news results with title, snippet, link, author or outlet, thumbnail and a publish time that says how precise it is. Paid placements dropped. Deduplicated. Optional post body. No login.

- **URL**: https://apify.com/lanternlab318/naver-search.md
- **Developed by:** [Lantern Lab](https://apify.com/lanternlab318) (community)
- **Categories:** Social media, News
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.19 / 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.

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

## Naver (네이버) Search Scraper — Blog, Cafe & News

A Lantern Lab Actor. Give it Korean or English keywords and get Naver's public search results back from all three verticals in one run: **blog posts**, **cafe (카페) posts** and **news articles** — title, snippet, link, the blogger's pen name or the cafe's or the outlet's name, publish time and thumbnail. Optionally the full post body too. No Naver login, no cookies, no account of your own.

**Price: USD 1.19 per 1,000 result rows. No run-start fee.** Empty searches, per-search status rows and the run summary are free. Paid placements Naver mixes into the blog tab (파워컨텐츠) are dropped before anything is charged.

### What you get

- **All three verticals, one run.** `sources` defaults to blog + cafe + news. Each row says which vertical it came from (`recordType`, `source`), and each keyword/vertical pair gets its own status row.
- **Honest publish times.** Naver shows some posts as `2주 전` ("2 weeks ago") and others as `2026.10.23.`, so every row carries three fields: `publishedAt` (an ISO 8601 instant), `publishedAtPrecision` (how exact that instant actually is) and `publishedAtRaw` (what Naver printed). Nothing is passed off as more precise than it is. With `includeContent` on, blog and news rows get the **exact** time from the post's own page.
- **No advertisements in your data.** The blog tab's paid results (파워컨텐츠) are detected and dropped; `adsSkipped` on the status row and in the run summary says how many.
- **Nothing charged for an empty row.** A result in a template this Actor cannot read is dropped rather than written with a null title, and counted as `unparsedItems` so the gap is visible. Normally 0.
- **Blogs hosted outside Naver too.** The blog tab lists them (tistory and the like) and they are read the same way; they are identified by their URL, since they have no Naver blog id.
- **Deduplicated across the whole run.** `itemId` is built from the ids in Naver's own post URL (`blog:<blogId>:<logNo>`, `cafe:<cafeUrl>:<articleId>`, `news:<officeId>:<articleId>`), so a post that matches several of your keywords is written once and charged once.
- **Source id, publish time and collection time kept apart.** `itemId`, `publishedAt` and `collectedAt` are three separate fields.
- **Partial is never reported as empty.** A keyword Naver answered with its own "no results" notice is an empty result; a search cut short by a limit, a refusal, a page this Actor could not read, or a page Naver refused to let it continue past is `partial: true` with the reason. The two are different records.
- **It caps itself.** `maxUpstreamRequests` is a hard ceiling on requests to Naver for the whole run, derived from your input when you leave it empty, so a run is never unbounded.

### What it does not do

- **No login-only cafe posts.** Members-only cafe content is not collected, and cafe post **bodies** are not available at all: a cafe post page serves no text without a logged-in session, so those rows carry `content: null` with `contentStatus: "login_required"`.
- No comment threads, no image downloads, no Naver Shopping, Place, Kin or café-member lists.
- No personal data: the author field is the blogger's public pen name, the cafe's name or the news outlet's name — never a real name, phone number or email.
- No scheduling or monitoring built in; use Apify schedules.

### Input

| Field | Default | Meaning |
| --- | --- | --- |
| `keywords` | required | One keyword per line, Korean or English. Every keyword is searched in every vertical you picked. |
| `sources` | all three | Any of `blog`, `cafe`, `news`. |
| `sort` | `relevance` | `relevance` (관련도순) or `latest` (최신순). |
| `dateRange` | `any` | `any`, `1d`, `1w`, `1m`, `3m`, `6m`, `1y`. Only posts published inside the window. |
| `dateFrom` / `dateTo` | — | A custom window as `YYYY-MM-DD` in Korean time. Give both or neither; both override `dateRange`. |
| `maxResultsPerSource` | `50` | Upper bound on rows per keyword per vertical. Naver serves 30 blog/cafe or 15 news results per request. |
| `includeContent` | `false` | Also fetch each blog post's and news article's full body and exact publish time — one extra request per result, included in the price. |
| `maxUpstreamRequests` | derived | Hard ceiling on requests to Naver for the whole run. |
| `proxyConfiguration` | Apify Proxy | Datacenter by default, which is what this Actor is measured on. Switch to residential or Korean exit IPs only if searches come back blocked. |

```json
{
  "keywords": ["김치", "제주도 여행", "Samsung Galaxy"],
  "sources": ["blog", "cafe", "news"],
  "sort": "latest",
  "dateRange": "1m",
  "maxResultsPerSource": 50,
  "includeContent": false
}
```

### Output

One row per result, `recordType: "blog"`, `"cafe"` or `"news"`:

```json
{
  "recordType": "news",
  "itemId": "news:448:0000646542",
  "title": "\"15개월 아기에 빨간 김치와 햄을?\"…어린이집 급식에 학부모 분통",
  "snippet": "식단표에는 훈제오리와 백김치 등이 적혀 있었다. 그러나 실제 급식에는 …",
  "url": "https://n.news.naver.com/article/448/0000646542",
  "authorName": "TV조선",
  "authorUrl": "https://media.naver.com/press/448",
  "sourceName": "Naver News",
  "source": "news",
  "publishedAt": "2026-10-23T13:06:49+09:00",
  "publishedAtPrecision": "exact",
  "publishedAtRaw": "11시간 전",
  "thumbnailUrl": "https://search.pstatic.net/common/?src=…",
  "content": null,
  "contentStatus": "not_requested",
  "contentChars": null,
  "keyword": "김치",
  "sort": "latest",
  "rank": 1,
  "partial": false,
  "partialReason": null,
  "collectedAt": "2026-10-24T00:21:03.512Z"
}
```

| Field | Meaning |
| --- | --- |
| `itemId` | Built from the ids in Naver's own post URL (`blog:<blogId>:<logNo>`, `cafe:<cafeUrl>:<articleId>`, `news:<officeId>:<articleId>`), or from the URL itself (`<vertical>:url:<hash>`) for a post Naver does not host - a blog on tistory, an article on the outlet's own site. The dedupe key. |
| `title`, `snippet`, `url` | The post as the search page shows it, with Naver's tracking query stripped from the link. |
| `authorName` | The blogger's pen name, the cafe's name, or the news outlet's name — whichever this vertical shows publicly. |
| `authorUrl` | That author's public page on Naver. |
| `sourceName` / `source` | `Naver Blog` / `Naver Cafe` / `Naver News`, and the short vertical key. |
| `publishedAt` | The platform's publish time as an ISO 8601 instant, or `null` when Naver showed a label this Actor does not understand. |
| `publishedAtPrecision` | `exact` (from the post's own page), `day` (Naver showed a date), `relative_minute` / `relative_hour` / `relative_day` / `relative_week` / `relative_month` / `relative_year` (counted back from `collectedAt` at that granularity), or `unknown`. |
| `publishedAtRaw` | The label Naver printed, unparsed. |
| `thumbnailUrl` | The result's own thumbnail, or `null` when the result has none. Never the author's avatar. |
| `content` | The post body, when `includeContent` is on and this vertical has a reachable one. `null` otherwise. |
| `contentStatus` | `ok`, `not_requested`, `login_required` (cafe), `empty`, `request_failed`, `blocked`, `upstream_request_cap`. |
| `keyword`, `sort`, `rank` | Which search produced this row and where in its results it sat, counting from 1. |
| `partial`, `partialReason` | Always `false` / `null` here: the row exists because the result was collected. |
| `collectedAt` | Our own collection time, separate from `publishedAt`. |

One row per keyword and vertical, `recordType: "source_status"`, never charged:

| Field | Meaning |
| --- | --- |
| `resultsReturned` / `resultsCharged` | Rows this search wrote and charged. |
| `duplicatesSkipped` | Results an earlier search in the run had already collected. |
| `adsSkipped` | Paid placements dropped before any row was written. |
| `unparsedItems` | Results Naver listed in a template this Actor could not read, dropped rather than charged as an empty row. Normally 0. |
| `exhausted` | `true` when Naver's own result list ended before your `maxResultsPerSource` did. |
| `partial` / `partialReason` | `true` with `upstream_request_cap`, `charge_limit_reached`, `blocked`, `upstream_error`, `unreadable_page`, `no_next_page` or `not_collected` when this search's results are incomplete. |
| `statusText` | The same thing in a sentence, so the dataset explains itself. |

The last row is `recordType: "RUN_SUMMARY"` — results collected and charged per vertical, ads dropped, duplicates, empty searches, partial searches by reason, bodies read by status, requests sent per endpoint, bytes moved, and why collection stopped. The same object is in the run's key-value store under `RUN_SUMMARY`.

#### Partial is not empty

`resultsReturned: 0` with `partial: false` means Naver itself said there is nothing for that keyword in that vertical. `partial: true` means the results are incomplete and there may well be more. A run that collected nothing *and* had partial searches fails rather than reporting an empty success.

### Limits and boundaries

- Only what is publicly visible on Naver's search pages. No login, no cookies, no one else's account.
- Naver's result lists are not endless: a vertical typically runs out after a few hundred posts, and `exhausted` on the status row tells you when that, rather than your `maxResultsPerSource`, ended a list.
- Naver dropped `start=`-style paging from these tabs during 2026, so pages after the first are read through the next-page links Naver itself puts in the page. A redesign can therefore break paging before it breaks the first page; that shows up as `partialReason: "unreadable_page"`, never as an empty result.
- **The news tab does not always offer that link.** Naver serves its news results with a paging link some of the time and without one the rest of the time, for the same keyword. When it withholds the link, collection stops at the 15 results of the first page and the status row says `partial: true` with `partialReason: "no_next_page"` — asking again usually gets further. Blog and cafe paging has been reliable. Budget for news accordingly if you need more than 15 per keyword.
- A narrow `dateRange` on a quiet keyword can legitimately return nothing. That is an empty result, not a failure.
- `publishedAt` for a relative label is derived from `collectedAt`; `publishedAtPrecision` always says so. Use `publishedAtRaw` if you need Naver's exact wording.
- Cafe posts never carry a body. See "What it does not do" above.

# Actor input Schema

## `keywords` (type: `array`):

One Naver search keyword per line, in Korean or English. Every keyword is searched separately in every vertical you picked, and its results are labelled with it. A post that matches several of your keywords is returned once and charged once.

## `sources` (type: `array`):

Which Naver search tabs to read. Blog is personal blogs on blog.naver.com, cafe is posts in public Naver Cafe communities, news is the news tab. All three by default.

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

Naver's own ordering: relevance is the default tab order, latest returns the newest posts first.

## `dateRange` (type: `string`):

Only return posts Naver published within this window. "any" applies no window. Shorter windows return fewer results: a one-day window on a quiet keyword can legitimately return nothing. A custom window below overrides this.

## `dateFrom` (type: `string`):

Start of a custom publish window, as YYYY-MM-DD in Korean time. Give both ends or neither; when both are set they replace "Published within".

## `dateTo` (type: `string`):

End of the custom publish window, as YYYY-MM-DD in Korean time.

## `maxResultsPerSource` (type: `integer`):

Upper bound on the rows one keyword may produce in one vertical. Naver serves 30 blog or cafe results and 15 news results per request, so 50 costs two requests per vertical. Naver's own result list ends after a few hundred posts; when it ends before your limit does, the status row for that search says the vertical was exhausted.

## `includeContent` (type: `boolean`):

With this on, every blog post and news article is also fetched for its full body text and its exact publish time - one extra request per result, included in the price. Cafe posts are not fetched: a cafe post page serves no body without logging in, so those rows carry content null with contentStatus "login_required". Leave it off for a much faster, cheaper run that returns the search snippet only.

## `maxUpstreamRequests` (type: `integer`):

Hard ceiling on the HTTP requests this run sends to Naver: result pages, post bodies and retries together. At the ceiling collection stops - the searches not reached are written with partialReason "upstream_request_cap" and the run then ends normally. Leave empty and the Actor works a ceiling out from your keywords, verticals and limits, so a run is never unbounded.

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

The proxy the requests to Naver go through. The default (Apify Proxy, datacenter) is the cheapest and is what this Actor is measured on; Naver's mobile search tabs answer it without blocking. Switch to residential, or to Korean exit IPs, only if you see blocked searches.

## Actor input object example

```json
{
  "keywords": [
    "김치"
  ],
  "sources": [
    "blog",
    "cafe",
    "news"
  ],
  "sort": "relevance",
  "dateRange": "any",
  "maxResultsPerSource": 50,
  "includeContent": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `runSummary` (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 = {
    "keywords": [
        "김치"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("lanternlab318/naver-search").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 = { "keywords": ["김치"] }

# Run the Actor and wait for it to finish
run = client.actor("lanternlab318/naver-search").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 '{
  "keywords": [
    "김치"
  ]
}' |
apify call lanternlab318/naver-search --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,lanternlab318/naver-search"
        }
    }
}
```

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/s4bYvsZMt01HbSGze/builds/Ot9uVRwCeuCMxX3Xy/openapi.json
