# Tabelog Restaurant Scraper (Japan) (`scrapyx/tabelog-restaurants-scraper`) Actor

Scrapes restaurants from Tabelog — Japan's #1 restaurant review site. Search any Japanese area by prefecture, district or neighbourhood with 35 cuisine genres; returns name, rating, review count, nearest station, budget, feature tags, address, GPS coordinates and phone number.

- **URL**: https://apify.com/scrapyx/tabelog-restaurants-scraper.md
- **Developed by:** [Ibnu Adzim](https://apify.com/scrapyx) (community)
- **Categories:** Travel, Business, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.49 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Tabelog Restaurant Scraper (Japan)

Scrapes restaurants from **[Tabelog](https://tabelog.com)** (食べログ) —
Japan's #1 restaurant review site, run by the Kakaku.com group. Think Yelp,
but the one Japan actually uses.

Public data only. No login, no cookies, no browser. **No bot challenge of any
kind** — 8 of 8 TLS profiles returned clean 200s on both list and detail
pages.

### The one thing you need to know before using this

**A wrong area CODE returns the parent area, silently.**

Tabelog areas are hierarchical, and a code it does not recognise is not
rejected — it just drops that level:

| Requested | What you actually get | Results |
| --- | --- | --- |
| `tokyo/A1301/A130101` | Ginza | 2,669 |
| `tokyo/A1301/A999999` | Ginza & Shimbashi & Yurakucho | 6,260 |
| `tokyo/A9999/A999999` | **all of Tokyo** | 139,211 |

All three answer HTTP 200 with real restaurants. This actor reads Tabelog's
own breadcrumb — the one echo that reveals what it genuinely resolved — and
if a level was dropped it **stops before paging**, emitting one
`SEARCH_SUMMARY` with `areaApplied: false` plus `upstreamHeading` and
`upstreamBreadcrumb` showing what Tabelog actually searched.

A wrong *prefecture* (`notaprefecture`) is a clean 404, and a wrong *genre*
is refused before the run starts — so only the area codes need this defence.

### What you get

Three record types share one dataset, told apart by `recordType`.

#### `RESTAURANT` — one row per restaurant

List rows carry name, rating, review count, nearest station with walking
distance, genre, dinner and lunch budget bands, and feature tags
(credit cards, non-smoking, Wi-Fi, private rooms…).

With **Fetch full restaurant details** on (the default), each row also gets
`restaurantDetails` — Tabelog's Schema.org record, verbatim: exact street
address with postal code, **GPS coordinates**, phone number, price range and
cuisine list.

#### `SEARCH_SUMMARY` — one row per area searched

Tabelog's own result total, how deep this run paged, the resolved area
(heading + breadcrumb), `areaApplied`, and whether the query hit the
1,200-row ceiling.

#### `ERROR` — one row per input that could not be processed

Every input maps to at least one row, so nothing disappears silently.

### Input

| Field | What it does |
| --- | --- |
| **Areas** | `tokyo`, `tokyo/A1301`, `tokyo/A1301/A130101`, or a pasted list URL — one search each |
| **Genre** | one of 35 verified cuisine slugs, or any |
| **Sort by rating** | highest-rated first (the only sort Tabelog honours) |
| **Restaurant URLs** | scrape specific restaurants directly, skipping search |
| **Max restaurants per area** | `0` = unlimited, meaning up to Tabelog's own 1,200 cap |
| **Min interval between requests** | the speed control — 0.4s default ≈ 2.5 req/s |

#### Getting an area path

Browse to the area on tabelog.com and copy the address — that is the whole
trick. `https://tabelog.com/en/tokyo/A1301/A130101/rstLst/` can be pasted in
as-is, or shortened to `tokyo/A1301/A130101`.

#### Example

```json
{
  "areas": ["tokyo/A1301/A130101", "osaka/A2701/A270101"],
  "genre": "ramen",
  "sortByRating": true,
  "maxItems": 200,
  "includeRestaurantDetails": true
}
```

### Notes on reliability

- **No WAF.** Both surfaces answered 200 on `chrome124`, `chrome120`,
  `chrome131`, `safari17_0`, `safari18_0`, `edge101`, `firefox133` and
  `chrome99_android`.
- **`chrome99_android` is excluded anyway** — it is served the *mobile*
  document (989 KB vs 282 KB), whose markup has no result counter, so it
  would quietly degrade every row it touched. Desktop profiles only.
- **Speed is controlled by request rate, not concurrency.** The crawl is
  paced to one request every 0.4s by default (≈2.5/second) across all
  workers. `maxConcurrency` only absorbs latency variance — raising it past
  about 5 will not make a run finish sooner, because the rate cap binds
  first. To go faster, lower **Min interval between requests**; a 20-row
  crawl takes ~9s at 0.4s and ~4.6s at 0.15s.
- **Politeness is deliberate.** Tabelog's `robots.txt` gives named crawlers
  `Crawl-delay: 5` and states no rule for generic clients, but a site with no
  bot challenge at all is one worth not provoking.
- **Residential proxy pinned to Japan** is the cloud default — a JP exit
  sees the same regional content a real visitor does.
- Pagination ends honestly: page 61 is genuinely empty rather than looping.

### Known limits

- 1,200 rows per query (60 pages × 20), whatever the total says. Use a
  deeper area code or a genre to reach the rest.
- **Reviews are not included.** Review text is a different data-type for a
  different audience, it multiplies request count without bound, and
  `robots.txt` disallows the per-reviewer paths. It belongs in a sibling
  actor.
- Only the rating sort is offered — review-count and budget sorts were
  tested and Tabelog silently ignores them.
- This scrapes the **English** site (`/en/`). Some smaller restaurants have
  thinner English records than their Japanese ones.

# Actor input Schema

## `areas` (type: `array`):

Tabelog area paths — one search per entry, each with its own SEARCH_SUMMARY row. Areas are hierarchical and every depth works:

• `tokyo` — the whole prefecture (139,211 restaurants)
• `tokyo/A1301` — Ginza & Shimbashi & Yurakucho (6,260)
• `tokyo/A1301/A130101` — Ginza (2,669)

A full list URL works too (https://tabelog.com/en/tokyo/A1301/A130101/rstLst/) — the easiest way to get one is to browse to the area on tabelog.com and copy the address.

If an area CODE is wrong, Tabelog does NOT error: it silently searches the parent area instead. The actor detects that via Tabelog's own breadcrumb, refuses to page it, and reports areaApplied: false. (A wrong prefecture returns a clean 404.)

Because each query caps at 1,200 rows, several narrow areas return far more data than one broad one.

## `genre` (type: `string`):

Restrict to one cuisine/genre. An unrecognised slug returns a clean HTTP 404 (no silent-widen risk here), and every value below was verified to genuinely narrow the results.

## `sortByRating` (type: `boolean`):

Return the highest-rated restaurants first instead of Tabelog's default order. This is the only sort Tabelog actually honours — review-count and budget sorts were tested and are silently ignored, so they are not offered.

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

Scrape specific restaurants directly, skipping the area search. Accepts full Tabelog restaurant URLs (https://tabelog.com/en/tokyo/A1301/A130101/13277198/). Can be combined with an area search in the same run.

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

Stop paginating an area after this many restaurants. Set to 0 for unlimited — note Tabelog itself stops at 1,200 rows per query (60 pages × 20) however many results it reports. Use a deeper area code or a genre to reach the rest.

## `includeRestaurantDetails` (type: `boolean`):

Fetch each restaurant's Schema.org record via one extra request: exact street address, postal code, GPS coordinates, phone number, price range and cuisine. Turn this off for a fast list-only crawl — list rows already carry name, rating, review count, nearest station, genre, dinner/lunch budget and feature tags.

## `maxConcurrency` (type: `integer`):

How many requests may be in flight at once. This absorbs latency variance; it is NOT the throttle — the crawl rate is set by 'Minimum interval between requests' below. Raising this past ~5 will not speed up a run on its own.

## `minRequestInterval` (type: `number`):

The actual speed control: the shortest gap between two requests to Tabelog, across all workers. 0.4s (default) ≈ 2.5 requests/second. Lower it to finish faster, raise it to be gentler.

Tabelog serves no bot challenge at all, which is worth preserving — an unthrottled crawl is how a WAF-free site stops being WAF-free. Values below 0.05s are clamped.

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

Tabelog runs no bot challenge at all (8/8 TLS profiles clean), but Residential pinned to Japan is the cloud default — container egress is a different posture than a home connection, and a JP exit gets the same regional content a real visitor sees.

## Actor input object example

```json
{
  "areas": [
    "tokyo/A1301/A130101"
  ],
  "genre": "",
  "sortByRating": false,
  "restaurantUrls": [],
  "maxItems": 100,
  "includeRestaurantDetails": true,
  "maxConcurrency": 5,
  "minRequestInterval": 0.4,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "JP"
  }
}
```

# Actor output Schema

## `items` (type: `string`):

One row per scraped record. See the dataset's default view for field definitions.

# 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 = {
    "areas": [
        "tokyo/A1301/A130101"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapyx/tabelog-restaurants-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 = { "areas": ["tokyo/A1301/A130101"] }

# Run the Actor and wait for it to finish
run = client.actor("scrapyx/tabelog-restaurants-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 '{
  "areas": [
    "tokyo/A1301/A130101"
  ]
}' |
apify call scrapyx/tabelog-restaurants-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapyx/tabelog-restaurants-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/rHaZSHK8ISg4DygQi/builds/4fbzBlH9D4RKXCpZg/openapi.json
