这是”为什么酒店API集成这么难?”系列的第2篇。

第1篇:认证地狱 第3篇:限流噩梦(即将发布)

你写过50个if/else来处理数据格式吗?

真实场景

假设你要搜索伦敦的一家酒店:

用户请求

{
  "hotel_id": "LON123",
  "check_in": "2026-02-10",
  "check_out": "2026-02-12",
  "guests": 2
}

你调用了5家供应商API,得到了5种不同的响应格式

供应商A(简单)

{
  "hotel_id": "LON123",
  "hotel_name": "London Central Hotel",
  "rooms": [
    {
      "room_id": "R101",
      "room_name": "King Room",
      "price": 150.00,
      "currency": "GBP",
      "tax_included": true
    }
  ]
}

供应商B(嵌套结构)

{
  "property": {
    "code": "LON123",
    "name": "London Central Hotel"
  },
  "room_rates": [
    {
      "rate_plan_code": "R101",
      "description": "King Room",
      "rates": {
        "amount": 135.00,
        "currency": "USD",
        "tax_exclusive": true,
        "tax_amount": 27.00
      }
    }
  ]
}

供应商C(XML格式)

<hotels>
  <hotel code="LON123" name="London Central Hotel">
    <rooms>
      <room type="101" name="King Room">
        <price currency="EUR">162.50</price>
        <taxes included="true"/>
      </room>
    </rooms>
  </hotel>
</hotels>

供应商D(复杂嵌套)

{
  "results": {
    "hotels": [
      {
        "hotel": {
          "identifiers": [
            {"type": "internal", "value": "LON123"}
          ],
          "information": {
            "name": "London Central Hotel"
          }
        },
        "availability": {
          "rooms": [
            {
              "room": {
                "id": "101",
                "details": {
                  "name": "King Room",
                  "bed_type": "KING",
                  "square_meters": 25
                }
              },
              "pricing": {
                "base_rate": 150.00,
                "taxes": 30.00,
                "total_rate": 180.00,
                "currency": "USD",
                "tax_inclusive": true
              }
            }
          ]
        }
      }
    ]
  }
}

供应商E(最复杂)

{
  "search_response": {
    "hotels": [
      {
        "hotel_code": "LON123",
        "hotel_name": "London Central Hotel",
        "rooms": [
          {
            "room_code": "101",
            "room_name": "King Room",
            "rates": [
              {
                "rate_id": "RATE1",
                "price_per_night": 160.00,
                "currency": "GBP",
                "tax": {
                  "vat": 20.00,
                  "city_tax": 5.00,
                  "included_in_price": true
                },
                "fees": {
                  "service_fee": 10.00,
                  "included": false
                }
              }
            ]
          }
        ]
      }
    ]
  }
}

问题来了

同一个房型”King Room”,你得到了:

供应商 价格 货币 税费 总价
A 150.00 GBP 含税 150.00
B 135.00 USD 不含税 162.00
C 162.50 EUR 含税 162.50
D 180.00 USD 含税 180.00
E 160.00 GBP 含税+服务费 175.00

你怎么告诉用户”最便宜的价格”?

  1. 汇率转换(GBP/USD/EUR)
  2. 税费统一计算(含税vs不含税)
  3. 服务费处理
  4. 排序

你写了多少行代码?


痛点1:同一个房间,5种不同的名字

真实场景

用户搜索”King Room”,5家供应商返回:

供应商 房型名称
A King Room
B Deluxe King - City View
C King Bed City View
D King City View Room
E Superior King Room

你怎么匹配?

方法1:字符串匹配(简单但不可靠)

def match_rooms(user_query, supplier_rooms):
    matches = []
    for room in supplier_rooms:
        if user_query.lower() in room.name.lower():
            matches.append(room)
    return matches

问题

  • ❌ “King Room” vs “Deluxe King” - 匹配吗?
  • ❌ “King Room with City View” vs “King Room” - 是同一个吗?
  • ❌ “Superior King Room” - 这是什么?

方法2:关键词提取(稍微好一点)

def extract_keywords(room_name):
    keywords = []
    # 提取床型
    if "king" in room_name.lower():
        keywords.append("king")
    # 提取视图
    if "city" in room_name.lower():
        keywords.append("city_view")
    # 提取等级
    if "deluxe" in room_name.lower():
        keywords.append("deluxe")
    if "superior" in room_name.lower():
        keywords.append("superior")
    return keywords

def match_rooms_by_keywords(user_keywords, supplier_rooms):
    matches = []
    for room in supplier_rooms:
        room_keywords = extract_keywords(room.name)
        if set(user_keywords) <= set(room_keywords):
            matches.append(room)
    return matches

问题

  • ⚠️ 依赖规则,需要维护大量规则
  • ⚠️ 同义词问题:”City View” vs “Urban View” vs “Metropolis View”
  • ⚠️ 语境缺失:”Deluxe King” - Deluxe是等级还是描述?

我们遇到的坑

坑1:用户投诉”房间和图片不一样”

  • 用户看到:”Deluxe King - City View”
  • 图片显示:King Bed + City View
  • 实际入住:King Bed(普通床) + City View(普通窗户)

原因:供应商A的”Deluxe King” = 普通King Bed + City View

  • 供应商B的”Deluxe King” = Deluxe King Bed + City View
  • 供应商C的”Deluxe King” = King Bed + Deluxe Room + City View

用户投诉:”图片是Deluxe King,但我住的只是普通King!”

坑2:价格映射错误

用户订了”Deluxe King - City View”($180), 实际入住的是”King City View”($150)。

用户投诉:”我订的是$180的房间,为什么只给我$150的?”

原因:字符串匹配误判了房型。


痛点2:价格计算,5种不同的逻辑

价格格式的噩梦

供应商 格式 含税? 服务费?
A 150.00 GBP 含税 含在价格里
B 135.00 USD + 27.00 USD tax 不含税
C 162.50 EUR 含税 另收5 EUR清洁费
D 150.00 base + 30.00 tax 不含税 另收10%服务费
E 160.00 GBP + 20.00 VAT + 5.00 city tax 含税 另收10.00 GBP

你怎么计算总价?

步骤1:统一货币

# 汇率转换(假设1 USD = 0.80 GBP = 0.90 EUR)
exchange_rates = {
    "GBP": 1.0,
    "USD": 0.80,
    "EUR": 0.89
}

def convert_to_gbp(amount, currency):
    return amount * exchange_rates[currency]

步骤2:统一税费

def calculate_total_price(room, nights):
    # 情况A:含税
    if room.tax_included:
        base = room.price
    # 情况B:不含税
    else:
        base = room.price + room.tax_amount
    
    # 情况C:另收服务费
    if room.service_fee and not room.service_fee_included:
        base += room.service_fee
    
    return base * nights

步骤3:处理例外情况

  • 供应商C:另收5 EUR清洁费(每晚)
  • 供应商D:另收10%服务费(总价)
  • 供应商E:VAT(20%)+ city_tax(5%)

问题

  • ❌ 供应商文档没说清楚”另收费用”
  • ❌ 有些费用是”可能收取”,不是”一定收取”
  • ❌ 有些费用是”前台支付”,不是”在线支付”

真实踩坑

坑1:价格对不上

  • 显示:$150/晚
  • 实际支付:$180/晚(+ $20 city tax + $10 service fee)

用户投诉:”为什么价格变了?”

原因:供应商文档说”tax included”,但没说”city tax and service fee not included”。

坑2:汇率问题

  • 用户查询时:1 USD = 0.80 GBP
  • 用户下单时:1 USD = 0.82 GBP
  • 实际支付:比显示的多了2.5%

用户投诉:”价格不对!”


痛点3:库存状态,5种不同的含义

什么是”Available”?

供应商 Available状态 含义
A “Available” 有房,可以预订
B “Available” 有房,需要确认
C “OnRequest” 有房,需要24小时确认
D “Available” 有房,但可能超售
E “Available” 有房,但有最小入住天数

用户的困惑

  • 用户看到:”Available”
  • 用户下单:”确认中…”
  • 供应商回复:”抱歉,没有房了”

用户投诉:”明明显示有房,为什么说没有?”

库存类型的噩梦

供应商 库存类型 处理方式
A 实时库存 立即确认
B 24小时确认 提交后等待确认
C OnRequest 提交后等待24-48小时
D 超售风险 可能确认失败
E 最小入住 必须住X晚才能预订

真实踩坑

坑1:OnRequest确认失败

  • 用户下单OnRequest房型
  • 等待24小时
  • 供应商回复:”抱歉,没有房了”
  • 用户已经安排了行程,现在重新找酒店

用户投诉:”你们浪费了我24小时!”

坑2:超售风险

  • 显示”Available”
  • 用户下单
  • 供应商回复:”抱歉,超售了”
  • 用户投诉:”为什么不告诉我有风险?”

坑3:最小入住天数

  • 用户想住1晚
  • 显示”Available”
  • 用户下单
  • 供应商回复:”最小入住3晚”
  • 用户投诉:”为什么不提前告诉我?”

痛点4:时区混乱

3个时区,你搞清楚了吗?

时区 用途 示例
UTC API数据传输 “2026-02-10T00:00:00Z”
HotelLocal 酒店当地时间 “2026-02-10T12:00:00+01:00” (伦敦冬令时)
UserLocal 用户当地时间 “2026-02-10T08:00:00-03:00” (纽约)

真实场景

用户查询

  • 用户时间:2026-02-10(纽约时间)
  • 酒店地点:伦敦
  • 入住时间:当地时间2月10日14:00

供应商A返回

{
  "check_in": "2026-02-10T00:00:00Z",  // UTC
  "check_out": "2026-02-12T00:00:00Z"  // UTC
}

供应商B返回

{
  "check_in": "2026-02-10T12:00:00+01:00",  // HotelLocal (伦敦)
  "check_out": "2026-02-12T12:00:00+01:00"  // HotelLocal (伦敦)
}

供应商C返回

{
  "check_in": "2026-02-10T08:00:00-03:00",  // UserLocal (纽约)
  "check_out": "2026-02-12T08:00:00-03:00"  // UserLocal (纽约)
}

问题来了

  1. 这3个时间是同一个吗?
    • UTC: 2026-02-10 00:00
    • HotelLocal: 2026-02-10 12:00 (伦敦)
    • UserLocal: 2026-02-10 08:00 (纽约)
  2. 应该怎么处理?
    • 都转换为UTC?
    • 都转换为HotelLocal?
    • 都转换为UserLocal?
  3. 夏令时/冬令时切换怎么办?
    • 2026年3月28日,伦敦切换夏令时
    • 2月10日和3月10日,相差1小时

真实踩坑

坑1:用户订了昨天的酒店

  • 用户选择:2月10日入住
  • 系统转换:UTC 2月9日24:00
  • 供应商理解:2月9日入住
  • 用户投诉:”我订的是明天,为什么是昨天?”

原因:时区转换错误(UTC vs HotelLocal)

坑2:价格不对

  • 显示:$150/晚
  • 实际:$165/晚
  • 用户投诉:”为什么价格不对?”

原因

  • 2月10日:冬令时,$150/晚
  • 3月10日:夏令时,$165/晚(旺季调价)
  • 用户以为是同一个时间,实际上相差1小时+1个月

我们如何解决

核心思路:标准化

问题:5家供应商,5种不同的数据格式

解决方案:统一的标准化层

供应商A → 标准化层 → 统一数据模型
供应商B → 标准化层 → 统一数据模型
供应商C → 标准化层 → 统一数据模型
供应商D → 标准化层 → 统一数据模型
供应商E → 标准化层 → 统一数据模型

统一数据模型

Hotel:
  id: string
  name: string
  location: 
    latitude: float
    longitude: float
    timezone: string

Room:
  id: string
  hotel_id: string
  name: string
  bed_type: enum(KING, QUEEN, TWIN, SINGLE, etc.)
  area: int  # square meters
  amenities: [string]

RatePlan:
  id: string
  room_id: string
  name: string
  base_price: float
  currency: string
  tax_included: boolean
  tax_amount: float
  service_fee: float
  fees: [Fee]

Inventory:
  rate_plan_id: string
  check_in: date  # UTC
  check_out: date  # UTC
  availability: enum(AVAILABLE, ON_REQUEST, SOLD_OUT)
  confirmation_required: boolean
  min_nights: int

标准化流程

1. 接收供应商原始数据
   ↓
2. 解析数据格式(JSON/XML)
   ↓
3. 提取关键字段(hotel_id, room_id, price, etc.)
   ↓
4. 转换为统一数据模型
   ↓
5. 验证数据完整性
   ↓
6. 返回标准化数据

房间映射:GIATA + AI

GIATA(全球酒店信息数据库)

  • 全球酒店房型权威标准
  • 500,000+酒店,1,000,000+房型
  • 标准化房型名称和属性

AI房间映射

  • 向量化房型名称和描述
  • 相似度匹配(cosine similarity)
  • 属性补充(床型、面积、设施)

流程

1. 供应商房型 → GIATA查询
   ↓
2. GIATA返回标准化房型
   ↓
3. 如果GIATA没有匹配 → AI匹配
   ↓
4. AI返回相似房型(准确率85%+)
   ↓
5. 人工审核(可选)
   ↓
6. 建立映射关系

准确率

  • GIATA直接匹配:92%
  • AI匹配:85%
  • 人工审核后:95%+

价格标准化

# 标准化价格
def normalize_price(raw_price, supplier):
    # 1. 转换为统一货币(GBP)
    gbp_price = convert_to_gbp(raw_price.amount, raw_price.currency)
    
    # 2. 处理税费
    if raw_price.tax_included:
        net_price = gbp_price
    else:
        net_price = gbp_price + raw_price.tax_amount
    
    # 3. 处理服务费
    if raw_price.service_fee and not raw_price.service_fee_included:
        net_price += raw_price.service_fee
    
    # 4. 处理其他费用
    for fee in raw_price.fees:
        if not fee.included:
            net_price += fee.amount
    
    return {
        "currency": "GBP",
        "net_price": net_price,
        "tax_included": True,
        "fees_included": True
    }

库存状态标准化

供应商状态 标准化状态 含义
Available AVAILABLE 有房,可以预订
OnRequest ON_REQUEST 有房,需要24-48小时确认
SoldOut SOLD_OUT 没有房
Limited AVAILABLE 有房,但数量有限

特殊处理

  • 超售风险:标记为AVAILABLE,但添加风险提示
  • 最小入住:添加min_nights字段
  • 旺季限制:添加availability_notes字段

时区标准化

from datetime import datetime, timezone
import pytz

# 标准化时间
def normalize_datetime(raw_datetime, hotel_timezone):
    # 1. 解析原始时间(可能带时区)
    if isinstance(raw_datetime, str):
        dt = datetime.fromisoformat(raw_datetime)
        if dt.tzinfo is None:
            # 没有时区,假设是UTC
            dt = dt.replace(tzinfo=timezone.utc)
    else:
        dt = raw_datetime
    
    # 2. 转换为酒店时区
    hotel_tz = pytz.timezone(hotel_timezone)
    hotel_local_time = dt.astimezone(hotel_tz)
    
    # 3. 提取日期(忽略时间)
    hotel_date = hotel_local_time.date()
    
    # 4. 返回UTC时间(标准化)
    utc_time = hotel_local_time.astimezone(timezone.utc)
    
    return {
        "date": hotel_date,
        "utc_datetime": utc_time,
        "hotel_timezone": hotel_timezone
    }

我们的优势

1. 一个API,所有供应商

之前:接入5家供应商 → 5个API → 5种数据格式 现在:接入HotelByte → 1个API → 统一数据模型

2. 标准化数据模型

  • Hotel/Room/RatePlan/Inventory
  • 统一的房型名称和属性
  • 统一的价格计算(含税)
  • 统一的库存状态
  • 统一的时区处理

3. AI房间映射

  • GIATA权威数据库
  • AI相似度匹配
  • 准确率95%+

4. 完善的文档和SDK

  • API文档:openapi.hotelbyte.com
  • Go SDK:github.com/hotelbyte-com/sdk-go
  • Java SDK:github.com/hotelbyte-com/sdk-java
  • 代码示例和最佳实践

行动号召

你还在写50个if/else吗?

问题

  • 同一个酒店,5家供应商,5种数据格式
  • 同一个房型,5种不同的名字
  • 同一个价格,5种不同的计算逻辑
  • 你写了多少行代码?

我们的解决方案

  • 统一数据模型(Hotel/Room/RatePlan/Inventory)
  • AI房间映射(准确率95%+)
  • 价格标准化(自动计算总价)
  • 时区标准化(自动转换)

你只需要

import "github.com/hotelbyte-com/sdk-go/hotelbyte"

client := hotelbyte.NewClient("YOUR_API_KEY")

// 搜索酒店
result, err := client.SearchHotels(&hotelbyte.SearchRequest{
    Destination: "London",
    CheckIn:     time.Date(2026, 2, 10, 0, 0, 0, 0, time.UTC),
    CheckOut:    time.Date(2026, 2, 12, 0, 0, 0, 0, time.UTC),
    Guests:      2,
})

// 返回统一数据模型
for _, hotel := range result.Hotels {
    for _, room := range hotel.Rooms {
        fmt.Printf("%s: %s - $%.2f\n",
            hotel.Name,
            room.Name,
            room.TotalPrice,  // 已标准化价格
        )
    }
}

没有50个if/else。没有5种数据格式。只有统一的数据模型。


下一步

免费试用

  • 30天免费试用
  • 无需信用卡
  • 立即开始测试

免费试用30天

查看文档

联系我们

有问题?直接联系我们的工程师。

联系我们


系列导航


下一篇预告:限流噩梦 - 你的搜索功能上线第1天就被供应商拉黑了吗?


阅读时间:约12分钟 难度:中等(需要了解API集成、数据标准化)