# Olive Young Ranking Scraper (K-Beauty Best Sellers) (`magenta_courser/oliveyoung-ranking-scraper`) Actor

Get the Olive Young Top 100 best-seller ranking for Korea and/or the global store by category: K-beauty skincare, makeup, suncare, hair and body products with prices, discounts, promo flags and rank change since your last run.

- **URL**: https://apify.com/magenta\_courser/oliveyoung-ranking-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 $3.00 / 1,000 products

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

## Olive Young Ranking Scraper

Pick a market (Korea, global or both) and categories, and get Olive Young's **Top 100 best-seller ranking** as flat JSON: rank, brand, product, price, discount, review count, rating, promo flags and the **rank change since your previous run**.

Olive Young is South Korea's largest health and beauty retailer and the place where K-beauty trends show up first. The Korean ranking (oliveyoung.co.kr) shows what Korean shoppers buy right now; the global ranking (global.oliveyoung.com) shows what international shoppers buy.

### What you get

One dataset item per ranked product:

- Rank, category, and the ranking page URL
- Brand and product name (Korean for the Korean site; English and Korean for the global site)
- Original price, sale price, discount rate, currency (KRW or USD)
- Promo flags: on sale, coupon, free gift, sold out, new product
- **Korean site only:** review count and rating, Olive Young `masterGoodsNumber` (and `gtin` when it passes a barcode check digit), 3-level product category, same-day delivery, Olive Young exclusive flag, ranking timestamp
- **Global site only:** stock quantity, global review count and rating, and the raw `sellTotalAmt` value of the list (`globalSellTotalAmount` - meaning and period not verified, see Notes)
- **Rank tracking:** `previousRank`, `rankChange` (positive = moved up) and `isNewEntry`, compared with the last complete run, which is named in `previousRunId` / `previousScrapedAt`
- **Run summary** (`SUMMARY` record, not charged): status and stop reason for every market and category

No login and no API key needed.

### Use cases

- **K-beauty trend tracking** — schedule a daily run and see which products and brands climb or enter the Top 100
- **Brand and competitor monitoring** — track your products' rank, price and promotions against competitors
- **Product sourcing for resellers** — find what sells in Korea before it reaches other markets; compare Korean and global rankings
- **Demographic insight** — rank the Korean site by shopper gender and age group (teens, 20s, 30s, 40s+)
- **AI agents** — one call returns a current, structured best-seller list

### Input

All fields are optional. With no input you get the overall Top 100 of both markets.

| Field | Description |
|---|---|
| `market` | `korea`, `global` or `both` (default). |
| `categories` | Ranking categories such as `skincare`, `maskPacks`, `suncare`, `makeup`, `hairCare`, `bodyCare`. Empty or `all` = overall Top 100. Categories that do not exist in a market are skipped. |
| `maxItems` | Products per market and category, 1–100. Default 100. |
| `koreaPeriod` | Korean ranking window: `realtime` (default), `day`, `week`, `month`. |
| `koreaGender` | Korean ranking by shopper gender: `all`, `female`, `male`. |
| `koreaAge` | Korean ranking by shopper age: `all`, `teens`, `twenties`, `thirties`, `fortiesPlus`. |
| `historyStoreName` | Name of the key-value store that remembers the previous ranking. Use different names for separate watchlists. |
| `koreaProxyConfiguration` | Proxy for the Korean site. Keep the default (Korean residential proxy). |
| `globalProxyConfiguration` | Proxy for the global site. The default datacenter proxy works. |

```json
{
  "market": "both",
  "categories": ["skincare", "suncare"],
  "maxItems": 100,
  "koreaPeriod": "realtime"
}
```

### Output

A row from the Korean suncare ranking (second run on the same history store):

```json
{
  "schemaVersion": 2,
  "source": "korea",
  "category": "suncare",
  "categoryId": "10000010011",
  "rank": 1,
  "productId": "A000000209913",
  "brand": null,
  "brandKo": "제로이드",
  "name": null,
  "nameKo": "[세정이 쉬운 무기자차] 제로이드 이지워시 마일드 선크림 50ml",
  "originalPrice": 32000,
  "salePrice": 22900,
  "maxSalePrice": 22900,
  "currency": "KRW",
  "discountRate": 28,
  "hasOptions": false,
  "isOnSale": false,
  "hasCoupon": true,
  "hasGift": true,
  "todayDelivery": true,
  "freeDelivery": false,
  "isExclusive": false,
  "isSoldOut": false,
  "isNewProduct": false,
  "reviewCount": 1725,
  "rating": 4.8,
  "globalStockQty": null,
  "globalReviewCount": null,
  "globalRating": null,
  "globalSellTotalAmount": null,
  "productCategory": "더모 코스메틱 > 선케어 > 선크림/선로션",
  "masterGoodsNumber": "8809911694320",
  "gtin": "8809911694320",
  "barcode": "8809911694320",
  "globalSalesAmountUsd": null,
  "url": "https://www.oliveyoung.co.kr/store/goods/getGoodsDetail.do?goodsNo=A000000209913",
  "imageUrl": "https://image.oliveyoung.co.kr/cfimages/cf-goods/uploads/images/thumbnails/400/10/0000/0020/A00000020991318ko.jpg?l=ko",
  "rankingUrl": "https://www.oliveyoung.co.kr/store/main/getBestList.do?dispCatNo=900000100100001&fltDispCatNo=10000010011",
  "rankingPeriod": "realtime",
  "rankingGender": "all",
  "rankingAge": "all",
  "rankingUpdatedAt": "2026-10-01T13:06:09.000Z",
  "rankingDateText": "2026/10/01 22:06:09",
  "previousRank": 1,
  "rankChange": 0,
  "isNewEntry": false,
  "comparisonStatus": "compared",
  "qualityStatus": "ok",
  "previousRunId": "6cowfXIi0Wtoqz7b5",
  "previousScrapedAt": "2026-10-01T13:35:47.703Z",
  "runId": "UFl5EsO3HpAx8pwf0",
  "scrapedAt": "2026-10-01T13:38:37.990Z"
}
```

Rows from the global site use the same fields. There `brand` and `name` are in English (for example `"Dr. Althea"`, `"Dr. Althea 345 Relief Cream 50ml Set (+10ml*2ea+Tube Wringer)"`), `currency` is `USD`, and `globalStockQty`, `globalReviewCount`, `globalRating` and `globalSellTotalAmount` are filled, while the Korea-only fields are `null`.

Every row has the same fields, including `schemaVersion` (currently `2`) and `source` (`korea` or `global`). A value the site did not provide is `null` rather than left out. A few global flags are derived from other values (`isOnSale` from the discount rate, `hasOptions` from the min/max price).

**Deprecated fields** (kept so existing integrations keep working, same values as before): `barcode` = `masterGoodsNumber`, `globalSalesAmountUsd` = `globalSellTotalAmount`. Please switch to the new names.

### Rank tracking

Each run saves its ranking as its own snapshot (named after the run ID) in a key-value store in your account (`historyStoreName`). A **baseline** pointer says which snapshot the next run compares against. The next run with the same market, category and Korean filters fills:

- `previousRank` — rank in the baseline snapshot
- `rankChange` — positive means the product moved up, negative means down
- `isNewEntry` — `true` if the product was not in the baseline ranking
- `previousRunId`, `previousScrapedAt` — which run and time the comparison used
- `comparisonStatus` — `compared`, `noBaseline` (first run), `skippedIncomplete` (this ranking looked incomplete; `isNewEntry` and `rankChange` are `null`) or `baselineSmaller` (the baseline had clearly fewer products; `isNewEntry` is `null`)
- `qualityStatus` — `ok`, `warning` (for example duplicates removed) or `incomplete`

The baseline only moves to a new run when **all** of that ranking's rows were saved to the dataset and the ranking looked complete. A ranking counts as incomplete when it has clearly fewer products than the baseline (under 80% and at least 5 fewer), more than 5% duplicates or items without an ID, or many more missing prices than before. Rankings are not assumed to have exactly 100 products; if the smaller size shows up again in a later run it is accepted as a real change. A failed save, a run stopped by your maximum charge, or an incomplete ranking leaves the previous baseline in place.

If two runs use the same history store at the same time, each compares with the baseline it read at the start (never a mix of two runs). When both try to become the baseline, the run with the newer ranking wins: after writing, a run waits about a second, re-checks, and steps back if a newer run took over. This is a simple check, not a lock; `baselineUpdate` in the `SUMMARY` record shows what happened (`promoted`, `promotedAfterConcurrentUpdate`, `keptNewerBaseline`, or `lostRace` if it could not settle).

On the first run the tracking fields are `null`. Schedule the Actor (hourly, daily or weekly) to build a rank history.

### Run summary

The default key-value store record `SUMMARY` (not charged) lists every market and category with `status` (`succeeded`, `partial`, `failed`, `skipped`, `notStarted`), `stopReason` (`completed`, `chargeLimitReached`, `blocked`, `emptyRanking`, `datasetWriteFailed`, `categoryNotInMarket`, `error`), product counts, quality issues, the comparison baseline and whether this run became the new baseline (`baselineUpdate`). The run fails only when every ranking failed.

### Tips

- Korean and global review numbers are separate fields: `reviewCount` / `rating` (Korean site) and `globalReviewCount` / `globalRating` (global site). They count different reviews.
- Product IDs differ between the Korean and global sites, so the same product is not matched across markets automatically. Match by `brandKo` and `nameKo`.
- Use `source` to split Korean and global rows.

### Notes

- Rows from the Korean site have Korean names only (`brand` and `name` are `null`; use `brandKo` and `nameKo`).
- `rankingUpdatedAt` and `rankingDateText` are provided only for the Korean real-time ranking.
- `masterGoodsNumber` is Olive Young's own field. Most values look like EAN/UPC barcodes, and `gtin` is filled only when the check digit is valid, but Olive Young does not document it as a barcode.
- `globalSellTotalAmount` is the raw `sellTotalAmt` value of the global best-seller list. Its unit, period and exact meaning are not documented or verified, so do not read it as revenue or monthly sales.
- The Korean site blocks most datacenter IPs. Keep the default Korean residential proxy for `koreaProxyConfiguration`.
- This Actor collects publicly visible ranking data only. It does not log in and collects no personal data.

# Actor input Schema

## `market` (type: `string`):

Which Olive Young ranking to scrape: Korea (oliveyoung.co.kr, prices in KRW), global (global.oliveyoung.com, prices in USD) or both.

## `categories` (type: `array`):

Ranking categories to scrape. Leave empty for the overall Top 100. Categories that do not exist in a market are skipped.

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

How many top-ranked products to return per market and category (Olive Young ranks up to 100).

## `koreaPeriod` (type: `string`):

Time window of the Korean sales ranking. Global ranking ignores this.

## `koreaGender` (type: `string`):

Rank by purchases of female or male shoppers only. Global ranking ignores this.

## `koreaAge` (type: `string`):

Rank by purchases of one age group only. Global ranking ignores this.

## `historyStoreName` (type: `string`):

Named key-value store that keeps the comparison baseline for rankChange and isNewEntry. The baseline only moves to a new run after that run's rows were saved and its ranking looked complete. Use a different name to track separate watchlists.

## `koreaProxyConfiguration` (type: `object`):

Proxy for oliveyoung.co.kr requests. Korean residential proxy (RESIDENTIAL, country KR) is the most reliable.

## `globalProxyConfiguration` (type: `object`):

global.oliveyoung.com works with the default Apify datacenter proxy.

## Actor input object example

```json
{
  "market": "both",
  "categories": [
    "all"
  ],
  "maxItems": 100,
  "koreaPeriod": "realtime",
  "koreaGender": "all",
  "koreaAge": "all",
  "historyStoreName": "oliveyoung-ranking-history",
  "koreaProxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "KR"
  },
  "globalProxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `rankings` (type: `string`):

All ranked products as dataset items.

## `summary` (type: `string`):

Status, stop reason, quality and baseline update for each market and category ranking (not charged).

# 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 = {
    "market": "both",
    "categories": [
        "all"
    ],
    "koreaProxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "KR"
    },
    "globalProxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("magenta_courser/oliveyoung-ranking-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 = {
    "market": "both",
    "categories": ["all"],
    "koreaProxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "KR",
    },
    "globalProxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("magenta_courser/oliveyoung-ranking-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 '{
  "market": "both",
  "categories": [
    "all"
  ],
  "koreaProxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "KR"
  },
  "globalProxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call magenta_courser/oliveyoung-ranking-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,magenta_courser/oliveyoung-ranking-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/kbE4pFxEbMvdCQnYT/builds/oS38ar28GnDFMiUP9/openapi.json
