为什么酒店API集成这么难?(2)数据混乱:同一家酒店,5种不同的数据格式
这是”为什么酒店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 |
你怎么告诉用户”最便宜的价格”?
- 汇率转换(GBP/USD/EUR)
- 税费统一计算(含税vs不含税)
- 服务费处理
- 排序
你写了多少行代码?
痛点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 (纽约)
}
问题来了:
- 这3个时间是同一个吗?
- UTC: 2026-02-10 00:00
- HotelLocal: 2026-02-10 12:00 (伦敦)
- UserLocal: 2026-02-10 08:00 (纽约)
- 应该怎么处理?
- 都转换为UTC?
- 都转换为HotelLocal?
- 都转换为UserLocal?
- 夏令时/冬令时切换怎么办?
- 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天免费试用
- 无需信用卡
- 立即开始测试
查看文档
- API文档:openapi.hotelbyte.com
- SDK文档:docs.hotelbyte.com
- 最佳实践:blog.hotelbyte.com
联系我们
有问题?直接联系我们的工程师。
系列导航
下一篇预告:限流噩梦 - 你的搜索功能上线第1天就被供应商拉黑了吗?
阅读时间:约12分钟 难度:中等(需要了解API集成、数据标准化)