「89.99が今なら59.99」というのは小売業界で最も古い謳い文句の一つであり、また最も偽りやすいものの一つでもある。数週間価格を吊り上げてから、元の水準に「値下げ」すればよいだけだからだ。消費者団体、ジャーナリスト、規制当局、そして小売業者自身のコンプライアンス部門が、これを見抜くために価格を監視している。しかし、その調査結果が意味を持つのは、小売業者から反論を受けても崩れない場合に限られる。そして小売業者は実際に反論してくる。
本ガイドが扱うのは、価格戦略のためではなく、説明責任を果たすための価格監視である。値引き表示に関するルールの内容、精査に耐えうる価格の証拠を収集する方法、そして値引き表示をその背後にある価格履歴と照合してテストするコードを取り上げる。
これは一般的な情報であり、法的助言ではない。ルールは国によって異なり、告発を公表したり苦情を申し立てたりする者は、まず法的助言を受けるべきである。
値引き表示に関するルールの内容
ほとんどの法域では、誤解を招く参照価格を不公正な商取引行為とみなしている。欧州連合はさらに踏み込み、具体的で検証可能なルールを定めている。
- 欧州連合。 2022年5月28日以降、値下げの告知には、値下げ前の少なくとも30日間において事業者が適用していた最安値として定義される「直前価格」を示さなければならない。加盟国は、急速に劣化する商品、販売期間が30日未満の商品、そして段階的に値下げ幅が増していく場合について、異なる規則を定めることができる。2024年9月、EU司法裁判所は小売業者Aldi Südが関わった事案において、パーセンテージで広告された値下げ、あるいは販促用の「価格ハイライト」として示された値下げについても、セール直前の価格ではなく、この30日間の最安値を基準に測定しなければならないとの判断を示した。
- 英国。 誤解を招く価格比較は不公正な商取引行為にあたり、2025年4月以降、競争市場庁(Competition and Markets Authority)は消費者法違反について、裁判所を介さずに自ら認定し事業者に直接罰金を科すことができるようになった。
- 米国。 連邦取引委員会(Federal Trade Commission)の「欺瞞的価格表示に対するガイド」は、相当な期間にわたり通常の方法で一般に提供されていた実際の正当な価格である場合に限り、旧価格を比較の正当な根拠として扱う。
EUのルールは、「この値引きは本物か?」という問いを単純な算術に変えるため、監視において最も有用である。セール前30日間の最安値を見つけて比較すればよいのだ。これは価格履歴があって初めて可能になるため、監視はテスト対象のセールよりもかなり前から始めなければならない。
価格の証拠が崩れないための条件
調査結果を突きつけられた小売業者は、結論の前にまずデータそのものを疑ってくる。証拠が崩れないのは、想定される異議にあらかじめ答えられている場合である。
| 想定される異議 | それに答えるもの |
|---|---|
| 「あなたのデータは間違っている。」 | URL、UTCタイムスタンプ、ハッシュとともに取得された生のページ。これにより元のソースから価格を再確認できる |
| 「抜けている日がある。」 | 日次の観測データ。各期間のカバレッジ(取得率)を報告し、カバレッジが低すぎる場合は判定を行わない |
| 「それは別の商品だった。」 | すべての観測データに記録された、安定した商品識別子(SKU、EAN、または小売業者独自のID) |
| 「それは別の地域向けの価格だった。」 | その価格が適用された所在地、店舗、または配送先郵便番号を記録し、一定に保つ |
| 「それは会員限定価格またはアプリ限定価格だった。」 | ログアウトした状態で公開価格を収集し、ページに表示された条件を記録する |
| 「最悪の事例だけを選んでいる。」 | 分析前に定義された公開済みの方法とサンプル。都合の悪い結果だけでなく、すべての結果を報告する |
日次での収集は、ほとんどの監視活動以上に重要な意味を持つ。オンライン価格に関する調査によれば、週次のサンプリングでも大手小売業者の通常の価格変動の大半は捉えられるが、偽の参照価格は1週間の中に収まってしまうことがある。オンライン価格はどれくらいの速さで変化するかに関する当社の調査では、この頻度について詳しく扱っている。スクリーンショットからハッシュ化、タイムスタンプに至るまでの取得作業は、オンライン掲載情報から法廷で通用する証拠を構築するのと同じ規律に基づいている。
値引き表示のテスト
以下の関数は、商品の日次価格履歴と告知された値引き幅を受け取り、EUのルールと同じ方法で検証する。欠落している日数が多すぎる場合は判定を行わず、証拠がどれだけ完全であったかを読み手が確認できるようカバレッジを記録する。もう一つの関数は、各観測データをタイムスタンプと生ページのハッシュとともに保存する。
import hashlib
from datetime import datetime, timedelta, timezone
def evidence_record(url, raw_body, price, currency):
"""One observation, stored so that anyone can later check what was seen and when."""
return {
"url": url,
"captured_at": datetime.now(timezone.utc).isoformat(timespec="seconds"),
"sha256": hashlib.sha256(raw_body).hexdigest(),
"price": price,
"currency": currency,
}
def lowest_prior_price(history, start, days=30, min_coverage=0.8):
"""Lowest observed price in the `days` before `start`, if we watched closely enough.
history: {date: price} from daily observations.
Returns (price, coverage); price is None when too many days are missing to say.
"""
window = [start - timedelta(days=d) for d in range(1, days + 1)]
seen = [history[d] for d in window if d in history]
coverage = len(seen) / days
if coverage < min_coverage:
return None, round(coverage, 2)
return min(seen), round(coverage, 2)
def check_discount(history, start, sale_price, shown_prior=None, shown_percent=None):
"""Compare an announced reduction with the lowest price in the 30 days before it."""
prior, coverage = lowest_prior_price(history, start)
result = {"start": start.isoformat(), "sale_price": sale_price, "lowest_30d": prior,
"coverage": coverage, "findings": []}
if prior is None:
result["findings"].append("not enough observations before the sale to judge")
return result
real_percent = round(100 * (prior - sale_price) / prior, 1)
result["real_percent"] = real_percent
if sale_price >= prior:
result["findings"].append("no real reduction: the sale price is not below the 30-day low")
if shown_prior is not None and shown_prior > prior:
result["findings"].append(f"shown prior price {shown_prior} is above the 30-day low {prior}")
if shown_percent is not None and shown_percent > real_percent + 0.5:
result["findings"].append(f"claims {shown_percent}% off, but it is {real_percent}% off the 30-day low")
return result
同じセール日に実施された、説明用の3つのオファーを以下に示す(作り話の履歴であり、実在する小売業者のものではない)。
from datetime import date, timedelta
from discounts import check_discount
sale = date(2026, 11, 27)
def daily(prices_before_sale):
"""Daily observations for the days before the sale, oldest first."""
n = len(prices_before_sale)
return {sale - timedelta(days=n - i): p for i, p in enumerate(prices_before_sale)}
offers = {
"kettle": (daily([49.99] * 40), 39.99, 49.99, 20),
"blender": (daily([59.99] * 25 + [89.99] * 15), 59.99, 89.99, 33),
"toaster": (daily([34.99] * 30 + [29.99] * 10), 27.99, 34.99, 20),
}
for name, (history, price, shown_prior, shown_percent) in offers.items():
result = check_discount(history, sale, price, shown_prior, shown_percent)
print(name, result["lowest_30d"], f"{result['real_percent']}%", result["findings"] or "ok")
kettle 49.99 20.0% ok
blender 59.99 0.0% ['no real reduction: the sale price is not below the 30-day low', 'shown prior price 89.99 is above the 30-day low 59.99', 'claims 33% off, but it is 0.0% off the 30-day low']
toaster 29.99 6.7% ['shown prior price 34.99 is above the 30-day low 29.99', 'claims 20% off, but it is 6.7% off the 30-day low']
- ケトルは期間全体を通じて49.99で、39.99で提供されている。20%という表示は正確である。
- ブレンダーは期間の大半で59.99だったが、直近15日間で89.99に値上がりし、33%オフという表示とともに59.99に「値下げ」されている。30日間の最安値と比較すると、実質的には一切値引きされていない。これが典型的な手口である。
- トースターには実際の値引きがあるが、表示より小さい。セール前の10日間はすでに29.99だったため、27.99は実質6.7%オフであり、20%ではない。
80%というカバレッジの閾値は判断の問題である。開始前に一つ決め、すべての商品に適用し、結果とともにその閾値を報告すること。
監視プロジェクトの進め方
- 先にサンプルを定義する。 セールシーズンが始まる前に、対象とする小売業者、カテゴリ、商品を選び、その理由を記録しておく。価格を見た後で選んだサンプルは、都合の良いものだけを選んだという批判を招く。
- 早く始める。 EUのテストにはセール前の少なくとも30日間の履歴が必要である。欠落に備えて6週間前から開始する。
- 公開価格を収集する。 ログアウトした状態で、固定した所在地から、小売業者自身の商品ページを使って行う。収集した所在地を記録する。価格やプロモーションは地域によって異なることが多い。
- すべてを取得する。 すべての観測について、生のページ、解析後の価格、証拠記録を保存する。保存コストは安いが、欠落した日は後から取り戻せない。
- 公表する前に確認する。 フラグが立った価格を生のページから手作業で再確認する。見かけ上の違反の多くは、解析エラー、別のバリエーション、あるいはセット商品であることが判明する。
- 事業者に反論の機会を与える。 ジャーナリストや消費者団体は通常、公表前に調査結果を小売業者に提示する。回答によって、把握していなかった地域限定価格のような正当な説明が明らかになることもある。
- 方法を公表する。 サンプル、期間、収集頻度、カバレッジの閾値、そしてすべての結果を報告する。
責任を持って収集する
公益のための監視であっても、それはデータ収集であることに変わりはなく、同じ原則が適用される。公開価格は、購入者が開けるような商品ページから、ログインせずに、控えめな頻度で収集する。個人データはデータセットから完全に排除する。価格の証拠に必要なのは商品と価格であり、レビュアーや顧客の情報ではない。残りの事項については、当社のウェブスクレイピングのベストプラクティスを参照されたい。
価格が国によって異なる場合は、各市場内から収集し、現地の買い物客が目にする価格を確認できるようにする。国別ターゲティングが可能なレジデンシャルプロキシは、これを大規模に行うための一般的な方法であり、各市場で所在地を固定しておくことは、収集頻度と同じくらい重要である。
よくある質問
値引きに関する30日ルールとは何か。
欧州連合では、値下げの告知には直前価格を示さなければならず、これは値下げ前の少なくとも30日間において事業者が適用していた最安値である。EU司法裁判所は2024年、パーセンテージによる値引きもこの価格を基準に算出しなければならないことを確認した。
値引きが偽物かどうかはどうすれば分かるか。
セール前少なくとも30日間、商品の価格を日次で追跡する。セール価格がその期間の最安値を下回っていない場合、または表示されたパーセンテージがその最安値からの実際の値引き幅より大きい場合、その値引き表示はEUのルールの下で誤解を招くものである可能性が高い。
セール前にどのくらいの期間、価格を監視する必要があるか。
EUのテストには少なくとも30日間が必要であり、実務上は数日の欠落があっても期間が不完全にならないよう、約6週間が目安となる。
価格監視の証拠は苦情申し立てに使えるか。
十分に文書化されていれば、申し立てを裏付けることができる。生のページ、タイムスタンプ、商品識別子、価格が適用された所在地、そして公表された方法が必要である。苦情を申し立てたり告発を公表したりする前には、法的助言を受けること。
結論
偽の値引きは作るのが容易だが、適切なデータがあれば検証も容易である。EUの30日ルールは、値引き表示を価格履歴との比較へと変えるため、監視はセールよりもかなり前から始めなければならない。固定した所在地から、ログアウトした状態で、日次で収集し、すべての観測について生の証拠を保存する。データが不十分な場合は判定を行わず、すべての調査結果を手作業で確認し、方法を公表する。そのように収集された証拠は、容易には退けられない。
出典と参考文献
- Directive (EU) 2019/2161、価格表示指令98/6/ECなどを改正するもの。
- 欧州連合司法裁判所、事件C-330/23、Aldi Südに関するプレスリリース、2024年9月26日。
- 米国連邦取引委員会、欺瞞的価格表示に対するガイド、16 CFR Part 233。