Skip to content

宿泊予約サイト連携

概要

楽天トラベル・agoda・trip.com・booking.com の4社からAPIで宿泊情報を取得し、横断比較する。

Phase 2 実装状況(モック)

Phase 2 時点では、外部プロバイダー API ではなく自前の hotels テーブル(データベース設計 参照)からデータを返すモック実装になっています。フロントエンドから呼び出される実エンドポイントは以下のとおりです。

メソッドパス説明
GET/api/v1/hotels?destination=OKA目的地空港コードに紐付くホテル一覧を返却

hotels テーブルには name / description / image_url / price_per_night / rating / review_count / provider / booking_url / amenities が格納されており、Phase 3 で以下の 4 社 API 連携に置き換えます。それまでは /api/v1/accommodation/* の仕様書(本ページ以降)は Phase 3 の設計ガイド として参照してください。

対応サービス一覧

サービスAPI種別申請要否特徴
楽天トラベルRakuten Developers API要(無料)国内に強い、日本語対応完全
agodaPartner API (YCS)要(パートナー登録)アジア圏に強い
trip.comAffiliate API要(アフィリエイト登録)価格競争力高い
booking.comAffiliate Partner API要(パートナー登録)世界最大の在庫

楽天トラベル API(Phase 1 で実装)

利用するAPI

APIエンドポイント用途
施設検索API/Travel/SimpleHotelSearchエリア・キーワードでホテル検索
空室検索API/Travel/VacantHotelSearch日程指定で空室・料金取得
エリアコードAPI/Travel/GetAreaClass都道府県・市区町村コード取得

リクエスト例(空室検索)

javascript
const params = {
  applicationId: process.env.RAKUTEN_APP_ID,
  checkinDate: '2024-07-20',
  checkoutDate: '2024-07-22',
  largeClassCode: 'japan',
  middleClassCode: 'hokkaido',
  smallClassCode: 'sapporo',
  adultNum: 2,
  hits: 20,
  sort: '+roomCharge', // 料金安い順
};

const response = await fetch(
  `https://app.rakuten.co.jp/services/api/Travel/VacantHotelSearch/20170426?${new URLSearchParams(params)}`
);

レスポンスマッピング

javascript
function mapRakutenResponse(hotel) {
  return {
    provider: 'rakuten',
    hotelName: hotel.hotelBasicInfo.hotelName,
    pricePerNight: hotel.roomInfo[0].dailyCharge.price,
    totalPrice: hotel.roomInfo[0].totalCharge,
    rating: hotel.hotelBasicInfo.reviewAverage,
    imageUrl: hotel.hotelBasicInfo.hotelImageUrl,
    bookingUrl: hotel.hotelBasicInfo.planListUrl,
    address: hotel.hotelBasicInfo.address1 + hotel.hotelBasicInfo.address2,
    latitude: hotel.hotelBasicInfo.latitude,
    longitude: hotel.hotelBasicInfo.longitude,
  };
}

agoda API(Phase 2)

Partner API (YCS)

  • パートナー登録が必要
  • REST API でホテル検索・料金取得
  • アフィリエイト収益モデル

主要エンドポイント

API用途
Property Search施設検索
Availability空室・料金取得
Property Details施設詳細

trip.com API(Phase 2)

Affiliate API

  • アフィリエイトパートナー登録が必要
  • ホテル検索・料金比較

booking.com API(Phase 2)

Affiliate Partner API (via RapidAPI)

  • RapidAPI 経由でアクセス可能
  • 施設検索・料金・空室・レビュー取得

主要エンドポイント

API用途
/v1/hotels/searchホテル検索
/v1/hotels/dataホテル詳細
/v1/hotels/reviewsレビュー取得

統一レスポンス形式

全サービスのレスポンスを以下の統一フォーマットに変換する。

typescript
interface AccommodationResult {
  // 基本情報
  provider: 'rakuten' | 'agoda' | 'tripcom' | 'bookingcom';
  hotelId: string;
  hotelName: string;

  // 料金
  pricePerNight: number;    // 1泊料金(税込・円)
  totalPrice: number;       // 合計料金(税込・円)
  currency: 'JPY';

  // 評価
  rating: number;           // 0-5 に正規化
  reviewCount: number;

  // 位置情報
  address: string;
  latitude: number;
  longitude: number;

  // メディア
  imageUrl: string;

  // 予約
  bookingUrl: string;       // アフィリエイトリンク
  cancelPolicy: string;     // キャンセルポリシー

  // メタデータ
  fetchedAt: string;        // 取得日時
}

キャッシュ戦略

外部APIの呼び出し回数を抑えるため、検索結果をキャッシュする。

項目設定
キャッシュ有効期間1時間
キャッシュキーprovider + destination + checkIn + checkOut + guests
保存先SQLite (accommodation_cache テーブル)
更新タイミングキャッシュ期限切れ時に再取得
javascript
async function searchAccommodation(params) {
  // キャッシュ確認
  const cached = db.prepare(`
    SELECT response_json FROM accommodation_cache
    WHERE provider = ? AND destination = ?
      AND check_in = ? AND check_out = ?
      AND guests = ? AND expires_at > datetime('now')
  `).get(params.provider, params.destination,
         params.checkIn, params.checkOut, params.guests);

  if (cached) {
    return JSON.parse(cached.response_json);
  }

  // API 呼び出し
  const result = await fetchFromProvider(params);

  // キャッシュ保存
  db.prepare(`
    INSERT INTO accommodation_cache
    (provider, destination, check_in, check_out, guests, response_json, expires_at)
    VALUES (?, ?, ?, ?, ?, ?, datetime('now', '+1 hour'))
  `).run(params.provider, params.destination,
         params.checkIn, params.checkOut, params.guests,
         JSON.stringify(result));

  return result;
}

料金比較ロジック

javascript
async function compareAccommodations(destination, checkIn, checkOut, guests) {
  // 全プロバイダーに並行リクエスト
  const results = await Promise.allSettled([
    searchAccommodation({ provider: 'rakuten', destination, checkIn, checkOut, guests }),
    searchAccommodation({ provider: 'agoda', destination, checkIn, checkOut, guests }),
    searchAccommodation({ provider: 'tripcom', destination, checkIn, checkOut, guests }),
    searchAccommodation({ provider: 'bookingcom', destination, checkIn, checkOut, guests }),
  ]);

  // 成功したレスポンスのみ統合
  const allHotels = results
    .filter(r => r.status === 'fulfilled')
    .flatMap(r => r.value);

  // 同一ホテルをグループ化(名前の類似度で判定)
  // 最安値順にソート
  return allHotels.sort((a, b) => a.pricePerNight - b.pricePerNight);
}