知识

价格一致性监控:发现自家库存被低价竞标

当酒店房间在其他渠道上显示更便宜的价格时,酒店会失去直接预订。如何在不引发误报的情况下监控各渠道和市场的价格一致性。

Matt Brown

Matt Brown

2026年10月3日 · 3 分钟阅读

旅客找到你的酒店,查看你的网站,然后在另一个网站上看到更便宜的同款房间。他们在那里预订,你支付了佣金,而这位旅客也得知在你自己的网站上预订并不划算。当这种情况是因为某个合作伙伴泄露了一个你本不打算面向公众出售的价格时,你自己的库存正在被用来压低你的价格。

价格一致性监控(Rate parity monitoring)就是酒店和旅游品牌用来发现这类情况发生在哪些市场、通过哪些渠道的方法。本指南将介绍在规则已经改变之后,价格一致性现在意味着什么,为什么大多数一致性检查会发出误报,以及经过测试的、可以做到同类比较的代码。

关键要点

  • 在欧洲大部分地区,价格一致性已不再是在线旅行社可以强加给酒店的事情。现在它主要关乎酒店自身的利益:不希望自己的房间被低价冲击。
  • 价格被压低的情况通常来自泄露的批发价或套餐价出现在酒店从未签约合作的网站上,而不是来自主要代理商本身。
  • 必须从旅客实际预订所在的市场检查价格。同一间房可能因购物者所在地的不同而显示不同的价格、货币和税费展示方式。
  • 只比较完全相同的产品:相同的日期、房型、餐食和取消条款,含税并换算成同一货币。在我们构建的示例中,一次走捷径的检查发出了五次警报,其中三次是误报;而经过规范化处理的检查恰好发出了两次真实的压价警报。
  • 把一次压价警报当作调查的起点:找出是哪个合作伙伴的价格泄露了,然后修正合同或分销设置。

价格一致性现在意味着什么

历史上,在线旅行社要求酒店向其提供至少与其他任何地方一样优惠的价格。“宽泛的”一致性条款覆盖所有渠道;“狭义的”一致性条款只覆盖酒店自己的网站。两者在欧洲大部分地区都已被废除:

  • 德国。 联邦卡特尔局(Federal Cartel Office)于2015年禁止了Booking.com的狭义最优价格条款,联邦最高法院(Federal Court of Justice)于2021年5月确认这些条款违反竞争法。
  • 欧盟。 2024年9月,欧洲法院(Court of Justice)裁定,平台价格一致性条款,无论宽泛还是狭义,原则上都不能被视为可豁免竞争法的附属限制。
  • 《数字市场法案》(Digital Markets Act)。 Booking.com于2024年5月被指定为”守门人”(gatekeeper),须在2024年11月前合规。根据该法案,价格一致性条款对其被禁止使用,因此酒店和其他供应商可以在自己的网站及其他渠道上提供不同的、包括更优惠的价格和条件,Booking.com也不得采取具有同等效果的措施,例如提高佣金或将其下架。
  • 其他地区。 包括法国、意大利、奥地利、比利时和瑞士在内的多个国家已在本国法律中对住宿平台的一致性条款作出限制。

这只是一般性信息,并非法律建议;各国规则和合同不同,请就你自己的协议咨询法律顾问。

实际后果是,价格一致性已经换了立场。酒店现在通常可以自由地在自己的网站上定价低于各代理商,而且许多酒店也希望这样做。它仍然需要知道的是,是否有人以低于其自身直销价的价格出售其房间,以及是通过什么方式。

压价从何而来

来源发生方式监控显示的结果
泄露的批发价本应打包进套餐出售的净价被当作独立房间转售不熟悉的网站价格低于你的直销价,通常出现在元搜索(metasearch)上
套餐拆分旅行社的”酒店+机票”套餐价被拆分出来,仅作为房间出售价格低于任何公开渠道理应提供的水平
会员价和移动端价格某渠道向已登录用户或应用用户显示更低价格仅在某些设备或会话中出现的价差
货币与市场定价某渠道按购物者所在国家或货币制定不同价格某些市场出现压价,其他市场则没有
过期或错误的价格加载价格变更未能同步到所有渠道特定日期上出现短暂的价差

这其中只有部分情况是某方的过错,也只有部分可以通过一通电话解决。监控必须显示足够的细节,以便将它们区分开来。

为什么简单化的一致性检查会发出误报

价格一致性检查看起来很简单:在几个网站上找到同一家酒店并比较价格。但这样做出来的结果,会让仪表盘充满并非真实的警报,同时又遗漏一些真实情况。常见原因如下:

  • 产品不同。 不可退款价或仅含房间不含餐食的价格,并不能算作对含早餐的灵活价格的压价。
  • 税费展示方式。 许多面向美国购物者展示的网站显示的是税前价格;大多数欧洲网站则含税显示。同样的总价看起来可能便宜好几个百分点。
  • 货币。 按不同汇率换算,或者干脆跨货币比较,本身就会造成百分之一二的价差。
  • 市场。 某渠道在美国显示的价格,与你在德国显示的直销价并不可比。应将每个市场与其自身进行比较。
  • 日期与入住人数。 不同的住宿天数和客人数量,即使每晚价格相同,也会产生不同的总价。

代码

下面的模块只在两份报价出售同一产品、条款相同时才进行比较,统一税费展示方式和货币,将每个渠道与面向同一市场显示的直销价进行比较,并报告差距。房型代码应使用你自己的规范化代码,由每个渠道的措辞映射而来,因为不同渠道对同一房型的描述方式各不相同。

from collections import defaultdict
from dataclasses import dataclass
from decimal import ROUND_HALF_UP, Decimal


@dataclass(frozen=True)
class Quote:
    channel: str          # "direct", or the name of the OTA or metasearch listing
    market: str           # where the shopper was, e.g. "DE" or "US"
    hotel: str
    check_in: str
    nights: int
    room: str             # your own normalised room code, not the channel's wording
    board: str            # "room-only", "breakfast", ...
    refundable: bool
    price: Decimal        # total for the stay, exactly as displayed
    currency: str
    taxes_included: bool


def product_key(q):
    """Two quotes are only comparable when they sell the same thing on the same terms."""
    return (q.hotel, q.check_in, q.nights, q.room, q.board, q.refundable)


def comparable_total(q, fx_to_base, hotel_tax):
    """Total price in the base currency with the destination's taxes included."""
    amount = q.price if q.taxes_included else q.price * (1 + hotel_tax[q.hotel])
    return (amount * fx_to_base[q.currency]).quantize(Decimal("0.01"), ROUND_HALF_UP)


def find_undercuts(quotes, fx_to_base, hotel_tax, tolerance=Decimal("0.01")):
    """Compare every channel with the direct rate shown to the same market, and report cheaper offers."""
    groups = defaultdict(list)
    for q in quotes:
        groups[(product_key(q), q.market)].append(q)
    undercuts, unmatched = [], []
    for (key, market), group in groups.items():
        direct = [comparable_total(q, fx_to_base, hotel_tax) for q in group if q.channel == "direct"]
        if not direct:
            unmatched.extend(q for q in group)
            continue
        best_direct = min(direct)
        for q in group:
            if q.channel == "direct":
                continue
            total = comparable_total(q, fx_to_base, hotel_tax)
            gap = (best_direct - total) / best_direct
            if gap > tolerance:
                undercuts.append({"channel": q.channel, "market": market, "hotel": key[0], "check_in": key[1],
                                  "direct": best_direct, "channel_total": total, "gap": round(gap, 3)})
    return sorted(undercuts, key=lambda u: u["gap"], reverse=True), unmatched

有两个设计选择值得注意。税费是按酒店所在地计算的,因为税费属于目的地,而非购物者所在国家。另外,没有可比直销报价的报价会被单独返回,而不是被丢弃,因为某渠道出售了一款你并未直接销售的产品,这本身就是一项值得关注的发现。

一个实例

我们在一组构建的数据上测试了该模块,数据涉及一家酒店、一次入住,面向德国、美国和英国的购物者展示,其中特意植入了两次真实的压价。这只是一个用虚构价格说明问题的示例,并非对任何真实酒店或渠道的测量。

检查方式发出的警报数发现的真实压价数误报数
走捷径:将显示价格与同一市场的直销价对比523
规范化:同一产品,含税,统一货币220

这三次误报是典型情况:一个美国站点显示的是税前价格,实际上价格一致;一个不可退款价;一个仅含房间不含餐食的价格。规范化检查将后两者作为不可比较的情况单独列出。一个英国站点换算后比直销价低0.5%,仍在1%的容差范围之内,而这正是单靠货币换算本身就可能造成的价差大小。

采集报价

价格一致性监控的质量取决于能否采集到旅客实际看到的信息:

  • 从旅客所在市场购物采集。 渠道和元搜索结果会因访问者所在国家而不同,因此应从对你重要的每个市场内部进行采集。我们关于航空公司和酒店是否按出发国不同而报价不同的测试说明了为何这并非可有可无的步骤。
  • 保持会话一致。 按照匹配代理地理位置、时区和语言环境一文所述,使语言、货币和时区与市场相匹配,这样渠道就不会在会话过程中把你切换到另一个市场的价格。
  • 分别检查各设备。 移动端和应用内价格可能与桌面端价格不同;在你的旅客同时使用这两者的地方,两者都应测试。
  • 记录完整条款。 记录餐食、取消政策、入住人数以及是否含税,而不仅仅是标题价格。显示的数字也需要按地区格式解析,正如跨地区规范化价格、数字和日期一文所解释的那样。
  • 采样多个日期,而不只是明天。 泄露情况往往只出现在特定的未来日期上,因此应覆盖一系列入住日期和住宿天数。
  • 礼貌地采集。 按计划检查一组合理的日期,而不是对搜索结果狂轰滥炸,并遵守每个网站的使用条款。

同样的采集方法也是抓取航班和酒店价格这一更广泛工作的基础,我们的旅游行业用例介绍了各团队如何在众多物业中运行这套方法。

从警报到修复

一次压价警报只是工作的开始,而非结束:

  1. 确认它。 从同一市场重新检查该报价,确保预订流程最终确实以该价格完成。
  2. 追溯它。 一次测试预订,或结账时显示的费率代码和供应商信息,通常可以确定是哪个合作伙伴的价格泄露了。
  3. 从源头处理。 收紧批发合同,改变合作伙伴能获得的价格种类,或在受影响的日期关闭该渠道。
  4. 持续跟踪。 按合作伙伴和市场记录压价情况。一个反复泄露价格的合作伙伴,需要的是一次商业层面的谈话,而不是监控层面的问题。

结论

价格一致性已不再是各代理商强制执行的条款,而变成了酒店自己要问的问题:有没有人以低于我的价格出售我的房间?要可靠地回答这个问题,就必须从旅客实际预订的市场采集价格,并且只比较完全相同的产品,同时规范化税费和货币。

做到这一点,一致性警报就会变得稀少而真实。省略这一步,仪表盘就会被税费展示方式和不可退款价格填满,而真正让你损失预订的泄露价格,就会淹没在这些噪音之中。

来源与参考资料

准备好开始了吗?

试用 Shifter 住宅代理,205M+ 个 IP,195+ 个国家,低至 $0.10/GB。

立即开始