Skip to content

restaurant

gurume.restaurant

PriceRange

Bases: StrEnum

價格範圍

Restaurant dataclass

Restaurant(
    name: str,
    url: str,
    rating: float | None = None,
    review_count: int | None = None,
    save_count: int | None = None,
    area: str | None = None,
    station: str | None = None,
    distance: str | None = None,
    genres: list[str] = list(),
    description: str | None = None,
    lunch_price: str | None = None,
    dinner_price: str | None = None,
    address: str | None = None,
    phone: str | None = None,
    business_hours: str | None = None,
    closed_days: str | None = None,
    reservation_url: str | None = None,
    has_vpoint: bool = False,
    has_reservation: bool = False,
    image_urls: list[str] = list(),
)

餐廳資訊

RestaurantSearchRequest dataclass

RestaurantSearchRequest(
    area: str | None = None,
    keyword: str | None = None,
    genre_code: str | None = None,
    reservation_date: str | None = None,
    reservation_time: str | None = None,
    party_size: int | None = None,
    sort_type: SortType = SortType.STANDARD,
    page: int = 1,
    price_range: PriceRange | None = None,
    online_booking_only: bool = False,
    seat_only: bool = False,
    new_open: bool = False,
    has_private_room: bool = False,
    has_parking: bool = False,
    smoking_allowed: bool = False,
    card_accepted: bool = False,
)

餐廳搜尋請求

do async

do() -> list[Restaurant]

異步執行搜尋(已棄用,請使用 search())

Source code in src/gurume/restaurant.py
493
494
495
async def do(self) -> list[Restaurant]:
    """異步執行搜尋(已棄用,請使用 search())"""
    return await self.search()

do_sync

do_sync() -> list[Restaurant]

同步執行搜尋(已棄用,請使用 search_sync())

Source code in src/gurume/restaurant.py
489
490
491
def do_sync(self) -> list[Restaurant]:
    """同步執行搜尋(已棄用,請使用 search_sync())"""
    return self.search_sync()

search async

search(
    use_cache: bool = True, use_retry: bool = True
) -> list[Restaurant]

異步執行搜尋

Parameters:

Name Type Description Default
use_cache bool

是否使用快取(預設:True)

True
use_retry bool

是否使用重試機制(預設:True)

True

Returns:

Type Description
list[Restaurant]

餐廳列表

Source code in src/gurume/restaurant.py
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
async def search(self, use_cache: bool = True, use_retry: bool = True) -> list[Restaurant]:
    """異步執行搜尋

    Args:
        use_cache: 是否使用快取(預設:True)
        use_retry: 是否使用重試機制(預設:True)

    Returns:
        餐廳列表
    """
    params = self._build_params()
    headers = {"User-Agent": USER_AGENT}

    area_slug = get_area_slug(self.area) if self.area else None
    url, params = build_search_url_and_params(params, area_slug, self.genre_code)

    # 檢查快取
    if use_cache:
        cached_html = cached_get(url, params)
        if cached_html is not None:
            return self._parse_restaurants(cached_html)

    # 執行請求(使用或不使用重試)
    if use_retry:
        resp = await fetch_with_retry_async(url=url, params=params, headers=headers, request_timeout=30.0)
    else:
        async with httpx.AsyncClient(timeout=30.0, follow_redirects=True) as client:
            resp = await client.get(
                url=url,
                params=params,
                headers=headers,
            )
            resp.raise_for_status()

    # 儲存到快取
    if use_cache:
        cache_set(url, params, resp.text, ttl=1800.0)  # 30 minutes TTL

    return self._parse_restaurants(resp.text)

search_sync

search_sync(
    use_cache: bool = True, use_retry: bool = True
) -> list[Restaurant]

同步執行搜尋

Parameters:

Name Type Description Default
use_cache bool

是否使用快取(預設:True)

True
use_retry bool

是否使用重試機制(預設:True)

True

Returns:

Type Description
list[Restaurant]

餐廳列表

Source code in src/gurume/restaurant.py
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
def search_sync(self, use_cache: bool = True, use_retry: bool = True) -> list[Restaurant]:
    """同步執行搜尋

    Args:
        use_cache: 是否使用快取(預設:True)
        use_retry: 是否使用重試機制(預設:True)

    Returns:
        餐廳列表
    """
    params = self._build_params()
    headers = {"User-Agent": USER_AGENT}

    area_slug = get_area_slug(self.area) if self.area else None
    url, params = build_search_url_and_params(params, area_slug, self.genre_code)

    # 檢查快取
    if use_cache:
        cached_html = cached_get(url, params)
        if cached_html is not None:
            return self._parse_restaurants(cached_html)

    # 執行請求(使用或不使用重試)
    if use_retry:
        resp = fetch_with_retry(url=url, params=params, headers=headers, timeout=30.0)
    else:
        resp = httpx.get(
            url=url,
            params=params,
            headers=headers,
            timeout=30.0,
            follow_redirects=True,
        )
        resp.raise_for_status()

    # 儲存到快取
    if use_cache:
        cache_set(url, params, resp.text, ttl=1800.0)  # 30 minutes TTL

    return self._parse_restaurants(resp.text)

SortType

Bases: StrEnum

排序方式

build_search_url_and_params

build_search_url_and_params(
    params: dict[str, Any],
    area_slug: str | None,
    genre_code: str | None,
) -> tuple[str, dict[str, Any]]

Build the Tabelog search URL and adjust params for area/genre filters.

Area-only searches still use /{area_slug}/rstLst/. Area + cuisine searches now require a cuisine-specific path segment (for example .../yakiniku/ or .../MC0101/) because the legacy LstG query parameter is ignored by current Tabelog prefecture pages.

Parameters:

Name Type Description Default
params dict[str, Any]

Search params dict (mutated in place and returned).

required
area_slug str | None

Tabelog area slug like "tokyo" or None.

required
genre_code str | None

Tabelog genre code like "RC0201" or None.

required

Returns:

Type Description
tuple[str, dict[str, Any]]

Tuple of (url, params).

Source code in src/gurume/restaurant.py
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
def build_search_url_and_params(
    params: dict[str, Any],
    area_slug: str | None,
    genre_code: str | None,
) -> tuple[str, dict[str, Any]]:
    """Build the Tabelog search URL and adjust params for area/genre filters.

    Area-only searches still use ``/{area_slug}/rstLst/``. Area + cuisine searches
    now require a cuisine-specific path segment (for example ``.../yakiniku/`` or
    ``.../MC0101/``) because the legacy ``LstG`` query parameter is ignored by
    current Tabelog prefecture pages.

    Args:
        params: Search params dict (mutated in place and returned).
        area_slug: Tabelog area slug like ``"tokyo"`` or ``None``.
        genre_code: Tabelog genre code like ``"RC0201"`` or ``None``.

    Returns:
        Tuple of (url, params).
    """
    url = "https://tabelog.com/rst/rstsearch"
    cuisine_slug = get_cuisine_slug_by_code(genre_code) if genre_code else None

    if area_slug and cuisine_slug:
        params.pop("sa", None)
        params.pop("LstG", None)
        return f"https://tabelog.com/{area_slug}/rstLst/{cuisine_slug}/", params

    if area_slug:
        url = f"https://tabelog.com/{area_slug}/rstLst/"
        params.pop("sa", None)
    if genre_code:
        params["LstG"] = genre_code
    return url, params

query_restaurants cached

query_restaurants(
    area: str | None = None,
    keyword: str | None = None,
    reservation_date: str | None = None,
    reservation_time: str | None = None,
    party_size: int | None = None,
    sort_type: SortType = SortType.STANDARD,
    page: int = 1,
    price_range: PriceRange | None = None,
    online_booking_only: bool = False,
    seat_only: bool = False,
    new_open: bool = False,
    has_private_room: bool = False,
    has_parking: bool = False,
    smoking_allowed: bool = False,
    card_accepted: bool = False,
) -> list[Restaurant]

快速查詢餐廳

Source code in src/gurume/restaurant.py
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
@cache
def query_restaurants(
    area: str | None = None,
    keyword: str | None = None,
    reservation_date: str | None = None,
    reservation_time: str | None = None,
    party_size: int | None = None,
    sort_type: SortType = SortType.STANDARD,
    page: int = 1,
    price_range: PriceRange | None = None,
    online_booking_only: bool = False,
    seat_only: bool = False,
    new_open: bool = False,
    has_private_room: bool = False,
    has_parking: bool = False,
    smoking_allowed: bool = False,
    card_accepted: bool = False,
) -> list[Restaurant]:
    """快速查詢餐廳"""
    return RestaurantSearchRequest(
        area=area,
        keyword=keyword,
        reservation_date=reservation_date,
        reservation_time=reservation_time,
        party_size=party_size,
        sort_type=sort_type,
        page=page,
        price_range=price_range,
        online_booking_only=online_booking_only,
        seat_only=seat_only,
        new_open=new_open,
        has_private_room=has_private_room,
        has_parking=has_parking,
        smoking_allowed=smoking_allowed,
        card_accepted=card_accepted,
    ).search_sync()