# OpenRice 爬蟲｜香港餐廳、評分、菜式、地區、食評數一鍵匯出（免 API） (`claude_code_reviewer/openrice-scraper`) Actor

免登入抓 OpenRice 香港餐廳：名稱、地區、菜式、評分、食評數、地址、電話與圖片，輸出 JSON／CSV／Excel。餐飲選址、市場調查、競品分析必備。

- **URL**: https://apify.com/claude\_code\_reviewer/openrice-scraper.md
- **Developed by:** [Chad](https://apify.com/claude_code_reviewer) (community)
- **Categories:** Agents, Travel, Social media
- **Stats:** 15 total users, 5 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 搜尋餐廳

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?

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

## OpenRice 爬蟲｜香港餐廳資料、食評一鍵下載

從 [OpenRice](https://www.openrice.com/zh/hongkong) 抓取香港餐廳搜尋列表資料（餐廳名稱、菜式、地區、評分、食評數量、地址、電話、圖片網址）及個別餐廳的完整食評（評分、心情、內容、推薦菜式、人均消費）。

- **走公開 JSON API + HTML**，不用登入、不用 token、不用瀏覽器
- **唯一穩定競品**：Apify Store 上唯一的競品已長期 UNDER\_MAINTENANCE、30/38 runs timeout
- **兩種模式**：`search`（批量關鍵字搜尋，地區 / 菜式 ID 過濾）、`reviews`（依餐廳 ID 抓所有食評）
- **結構化輸出**：snake\_case 欄位，缺值補 `null`，一律附 ISO 8601 時間戳

***

### 輸出欄位

#### search 模式

| 欄位 | 類型 | 說明 | 版本 |
|---|---|---|---|
| `poi_id` | string | OpenRice 餐廳唯一 ID | v1.0 |
| `name` | string | null | 餐廳中文名稱 | v1.0 |
| `cuisine` | string\[] | null | 菜式陣列（如 `["日本菜", "壽司/刺身"]`） | v1.0 |
| `district` | string | null | 地區中文名稱（如「銅鑼灣」） | v1.0 |
| `price_range` | string | null | 價位：`$` / `$$` / `$$$` / `$$$$` | v1.0 |
| `rating` | number | null | OpenRice 整體評分（如 `3.87`） | v1.0 |
| `review_count` | integer | null | 食評數量 | v1.0 |
| `bookmark_count` | integer | null | 收藏人數 | v1.0 |
| `address` | string | null | 餐廳地址（中文） | v1.0 |
| `phone` | string | null | 電話號碼（第一個） | v1.0 |
| `opening_hours` | string | null | 營業時間 JSON 字串（v1.1 改為易讀格式） | v1.0 |
| `image_urls` | string\[] | 餐廳圖片 URL 陣列（無圖片為 `[]`） | v1.0 |
| `restaurant_url` | string | OpenRice 餐廳頁面完整 URL | v1.0 |
| `source_mode` | string | 資料來源模式（目前為 `"search"`） | v1.0 |
| `source_query` | string | 觸發此筆資料的搜尋關鍵字 | v1.0 |
| `scraped_at` | string | 抓取時間戳（ISO 8601，UTC） | v1.0 |

#### reviews 模式

| 欄位 | 類型 | 說明 |
|---|---|---|
| `review_id` | integer | null | 食評唯一 ID |
| `restaurant_id` | string | 餐廳 POI ID |
| `author` | string | null | 食評作者暱稱 |
| `rating` | number | null | 該則食評評分（0–5，1 位小數） |
| `sentiment` | string | null | 心情：`smile` / `ok` / `cry` |
| `score_taste` | number | null | 味道分數（1–5） |
| `score_environment` | number | null | 環境分數（1–5） |
| `score_service` | number | null | 服務分數（1–5） |
| `score_hygiene` | number | null | 衛生分數（1–5） |
| `score_value` | number | null | 抵食分數（1–5） |
| `title` | string | null | 食評標題 |
| `content` | string | null | 食評內容（已去除 BBCode，換行保留） |
| `content_raw` | string | null | 食評原文（含 OpenRice BBCode） |
| `language` | string | null | 語言代碼（如 `hk`） |
| `price_per_head` | number | null | 人均消費 |
| `price_type` | integer | null | 消費類型代碼 |
| `meal_date` | string | null | 用餐日期 |
| `dining_method` | integer | null | 用餐方式代碼 |
| `like_count` | integer | null | 讚好數 |
| `dislike_count` | integer | null | 踩數 |
| `photo_count` | integer | null | 附圖數量 |
| `comment_count` | integer | null | 留言數 |
| `view_count` | integer | null | 瀏覽數 |
| `recommended_dishes` | string\[] | 推薦菜式陣列（無則為 `[]`） |
| `is_editor_choice` | boolean | null | 是否為編輯精選 |
| `created_at` | string | null | 食評發佈時間 |
| `review_url` | string | 食評短連結（無則回退到該餐廳食評頁） |
| `source_mode` | string | 資料來源模式（固定為 `"reviews"`） |
| `source_query` | string | 觸發此筆資料的餐廳 ID |
| `scraped_at` | string | 抓取時間戳（ISO 8601，UTC） |

***

### 模式

| mode | 輸入 | 產出 | 計費事件 |
|---|---|---|---|
| `search`（v1.0，預設） | `keywords[]` + 可選 `district` / `cuisine` 過濾 | 搜尋列表結果 | `search-listing`（每筆 $0.002） |
| `reviews`（v1.1） | `restaurantIds[]` 或 `restaurantUrls[]` | 餐廳食評 | `review-item`（每則 $0.003） |

***

### 輸入參數

| 參數 | 類型 | 必填 | 預設 | 說明 |
|---|---|---|---|---|
| `mode` | string | — | `search` | `search`（搜尋餐廳）或 `reviews`（抓食評） |
| `keywords` | string\[] | search 模式 ✓ | — | 搜尋關鍵字（餐廳名稱/菜式），最多 50 個 |
| `bulkInput` | string | — | — | 一行一個關鍵字（search）或餐廳 ID／網址（reviews），自動合併 |
| `restaurantIds` | string\[] | reviews 模式 ✓（三選一） | — | 餐廳 ID，網址最後的 `r` 後數字（如 `808150`） |
| `restaurantUrls` | string\[] | reviews 模式 ✓（三選一） | — | 餐廳頁面網址，自動擷取結尾的 `r` 後數字作為 ID |
| `district` | string | — | — | 地區 ID（如 `1019` = 銅鑼灣，見下方說明；僅 search 模式） |
| `cuisine` | string | — | — | 菜式 ID（如 `2009` = 日本菜，見下方說明；僅 search 模式） |
| `maxItems` | integer | — | `50` | search：每關鍵字最多筆數；reviews：每間餐廳最多食評數（1–2000） |
| `proxyConfiguration` | object | — | Apify datacenter proxy | 預設開啟；關掉會與其他 run 共用出口 IP，食評頁可能拿不到資料 |

#### 常用地區 ID（district）

| districtId | 地區 |
|---|---|
| 1999 | 香港島（全區） |
| 1019 | 銅鑼灣 |
| 1001 | 上環 / 中環 |
| 2999 | 九龍（全區） |
| 2010 | 旺角 |
| 2001 | 尖沙咀 |
| 3999 | 新界（全區） |

完整列表可呼叫 `GET https://www.openrice.com/api/v2/metadata/region/all?uiLang=zh&uiCity=hongkong` 取得。

#### 常用菜式 ID（cuisine）

| cuisineId | 菜式 |
|---|---|
| 1004 | 港式 |
| 2009 | 日本菜 |
| 4000 | 西式 |
| 1007 | 中式 |
| 2001 | 韓國菜 |

***

### 輸入範例

#### 1. 搜尋壽司（預設設定）

```json
{
    "keywords": ["壽司"],
    "maxItems": 20
}
```

#### 2. 中環燒烤篩選（上環/中環地區，districtId=1001）

```json
{
    "keywords": ["燒烤"],
    "district": "1001",
    "maxItems": 20
}
```

#### 3. 多關鍵字批量搜尋

```json
{
    "keywords": ["日本料理", "韓國烤肉"],
    "maxItems": 50
}
```

#### 4. 旺角日本菜（地區 + 菜式雙重過濾）

```json
{
    "keywords": ["壽司"],
    "district": "2010",
    "cuisine": "2009",
    "maxItems": 30
}
```

#### 5. 批量輸入多關鍵字

```json
{
    "bulkInput": "壽司\n燒烤\n火鍋\n茶餐廳\n點心",
    "maxItems": 30
}
```

#### 6. 抓單一餐廳的食評（reviews 模式）

```json
{
    "mode": "reviews",
    "restaurantIds": ["808150"],
    "maxItems": 100
}
```

***

### 餐飲市場研究

`reviews` 模式抓的是逐則食評（評分、心情、味道/環境/服務/衛生/抵食子分數、推薦菜式、人均消費），適合外送平台、連鎖餐飲品牌做菜式趨勢分析、競品評分監測與消費行為研究。建議流程：先用 `search` 模式依關鍵字或地區找出目標餐廳、記下 `poi_id`，再把這些 ID 丟進 `restaurantIds` 跑 `reviews` 模式取得完整食評資料。

***

### 注意事項

- **穩定性賣點**：唯一競品長期 UNDER\_MAINTENANCE（30/38 runs timeout），本 Actor 走純 JSON/HTML、無瀏覽器、指數退避重試，跑得完才算贏
- **計費時機**：`Actor.charge()` 只在資料成功 push 到 dataset 之後才呼叫，中途失敗不計費
- **連續空頁偵測**：不論 search 或 reviews 模式，連續 5 頁拿不到新資料自動停止，不空轉燒錢
- **reviews 模式去重**：同一 `review_id` 只會計費一次，即使因重試等原因重複抓到同一頁
- **`detail` 模式**：OpenRice 的 POI 詳情 API 端點目前未公開，probe 全部回 404；待找到或逆向工程後加入

***

### 常見問題 FAQ

**Q: 為什麼有些欄位是 `null`？**\
A: OpenRice 部分餐廳資料不完整（如沒有電話、未填地址），本 Actor 一律補 `null` 而不省略欄位，確保每筆資料結構一致。

**Q: `opening_hours` 是什麼格式？**\
A: v1.0 輸出 JSON 字串，包含每天的 `dayOfWeek`（1=日、2=一...7=六）、`period1Start`、`period1End`、`is24hr`、`isClose` 等欄位。v1.1 將改為人類可讀的格式。

**Q: 可以只搜尋特定地區嗎？**\
A: 可以，在 `district` 填入 districtId（如 `1019` = 銅鑼灣）。完整 districtId 清單可從 metadata API 取得。

**Q: 支援 proxy 嗎？**\
A: 支援 Apify Proxy 或自訂 proxy，預設不使用（直連已可正常爬取）。

**Q: `image_urls` 是否包含所有圖片？**\
A: 包含餐廳主圖（doorPhoto）及搜尋列表中回傳的最新圖片（通常 3–5 張）。完整相簿需 detail 模式（v1.1）。

**Q: 可以抓食評內容嗎？**\
A: 可以，設定 `"mode": "reviews"` 並提供 `restaurantIds` 或 `restaurantUrls`，即可抓該餐廳的完整食評（評分、心情、內容、推薦菜式等）。

**Q: 餐廳 ID 在哪裡找？**\
A: 打開任一 OpenRice 餐廳頁面，網址結尾會有 `r{數字}`（如 `.../r-milu-thai-tsim-sha-tsui-thai-r808150` 的 `808150`）；或先用 `search` 模式搜尋，輸出的 `poi_id` 欄位就是餐廳 ID。也可以直接把整條餐廳網址貼進 `restaurantUrls`，Actor 會自動擷取 ID。

***

### 部署

```bash
npm install
npm run build
apify push
```

本地測試：

```bash
apify run --input='{"keywords":["壽司"],"maxItems":5}'
apify run --input='{"mode":"reviews","restaurantIds":["808150"],"maxItems":45}'
```

***

### 免責聲明

本工具僅用於公開可存取資料的合法研究與分析用途。使用者應自行遵守 [OpenRice 服務條款](https://www.openrice.com/zh/hongkong/legal/terms)，請勿大量爬取、勿用於商業競爭目的、勿儲存個人資料。作者對使用者行為不負任何責任。

***

OpenRice 爬蟲、香港餐廳、餐廳資料、食評、餐廳評分、香港美食數據、食評、評論抓取

# Actor input Schema

## `mode` (type: `string`):

選擇要抓取的資料類型：搜尋餐廳列表，或依餐廳 ID 抓食評。

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

要搜尋的餐廳名稱或菜式關鍵字。每筆搜尋結果計費 $0.002。

## `bulkInput` (type: `string`):

search 模式：一行一個關鍵字，自動合併到 keywords。reviews 模式：一行一個餐廳 ID 或網址，自動合併到 restaurantIds/restaurantUrls。

## `restaurantIds` (type: `array`):

reviews 模式用：餐廳 ID，網址最後的 r 後數字，例如 808150。

## `restaurantUrls` (type: `array`):

reviews 模式用：餐廳頁面網址，自動擷取結尾的 r 後數字作為 ID。

## `district` (type: `string`):

districtId 篩選（由 metadata API 取得）。例如：1019 = 銅鑼灣、2010 = 旺角、1001 = 上環。留空則不篩選地區。僅 search 模式適用。

## `cuisine` (type: `string`):

cuisineId 篩選（由 metadata API 取得）。例如：2009 = 日本菜、1004 = 港式。留空則不篩選菜式。僅 search 模式適用。

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

search 模式為每個關鍵字最多抓幾筆結果，reviews 模式為每間餐廳最多抓幾則食評（1–2000，預設 50）。

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

預設走 Apify datacenter proxy：不用 proxy 時會跟平台上其他 run 共用出口 IP，OpenRice 可能回傳不含資料的頁面（食評模式會抓不到）。自備 proxy 或本機測試才關掉。

## Actor input object example

```json
{
  "mode": "search",
  "keywords": [
    "壽司",
    "燒烤",
    "日本料理"
  ],
  "bulkInput": "壽司\n燒烤\n港式",
  "restaurantIds": [
    "808150"
  ],
  "restaurantUrls": [
    "https://www.openrice.com/zh/hongkong/r-milu-thai-tsim-sha-tsui-thai-r808150"
  ],
  "district": "1019",
  "cuisine": "2009",
  "maxItems": 50,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `dataset` (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 = {
    "mode": "search",
    "keywords": [
        "壽司"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("claude_code_reviewer/openrice-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 = {
    "mode": "search",
    "keywords": ["壽司"],
}

# Run the Actor and wait for it to finish
run = client.actor("claude_code_reviewer/openrice-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 '{
  "mode": "search",
  "keywords": [
    "壽司"
  ]
}' |
apify call claude_code_reviewer/openrice-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,claude_code_reviewer/openrice-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/auya2ehdSn75MC7LW/builds/FZcGdSlLTUpuyBigA/openapi.json
