買い物客がGoogleで商品を検索すると、Shopping結果はその瞬間の市場のスナップショットになる。どの販売店がそれを扱っているか、いくらで、どんな配送条件で、その買い物客がいる場所から見てどう表示されるか。Eコマースチームにとって、このスナップショットは通常なら何十回ものクロールが必要になる質問に答えてくれる。誰が自分たちより安く売っているのか、どの商品で、どの市場で、どれだけの差があるのか。
Google Shopping APIは、このスナップショットをスケジュールに沿って照会できる構造化データに変換する。本ガイドでは、チームがこれを何に使っているか、数値を信頼できるものにするための収集方法、そして検索結果を価格ビューに変換する小さなコード例を扱う。これはprice intelligenceというより広い実践の一部であり、Shoppingデータはその中でも特に効率の良い入力源のひとつである。
Shoppingデータが小売店クロールと異なる理由
競合の価格を監視する従来の方法は、各小売店の商品ページをクロールすることだ。これは機能するが、スケール性に乏しい。小売店ごとに独自のコレクター、独自のパーサー、独自の商品マッチングが必要になる。Shopping結果はこのモデルを逆転させる。
- 1回のクエリで多数の販売店をカバーできる。 1回の検索で、Googleがその商品についてリストしている全ての小売店のオファーが返ってくる。
- 商品はすでにグループ化されている。 同一商品のオファーがまとめて表示されるため、複数の店舗間で商品をマッチングする手間の多くが省ける。
- 買い物客の視点が示される。 結果には、検索の市場と言語が反映され、どの販売店がどの順序で表示されるかも含まれる。これはクリックを実際に左右する要因である。
トレードオフはカバレッジにある。Shopping結果はそこにリストされている小売店を示すものであり、市場に存在する全ての販売店ではない。また表示されるのはリスト価格であり、クーポン適用後やチェックアウト時の価格とは異なる場合がある。カタログ全体の追跡には、やはり小売店のクロールが必要だ。重要な商品について市場全体を俯瞰するなら、Shoppingデータの方が速く、安価であることが多い。両者を組み合わせる方法については、リアルタイムの競合価格フィードの構築に関するガイドで扱っている。
チームがこれを何に使っているか
| 質問 | データが示すもの |
|---|---|
| 自社の価格は競争力があるか? | 各商品における自社の価格順位、最安値の販売店とのギャップ |
| 誰が自社より安く売っているか? | 自社より下に表示される販売店、その頻度と対象商品 |
| この商品はどれだけ競合が多いか? | 販売店の数と価格のばらつきの広さ |
| 競合はプロモーションを実施しているか? | ある販売店の商品群で急激に起きる価格下落 |
| 市場間でどう違うか? | 各国における同一商品の販売店と価格 |
| 再販業者が価格ポリシーに違反していないか? | 認定販売店が最低広告価格を下回って出品していないか |
最後の用途は、価格設定と同じくらいブランド保護に関わるものだ。メーカーはShoppingデータを使って、全ての再販業者をクロールすることなく、未認可の販売店や最低価格違反を見つける。
適切な収集方法
Shopping結果は誰が検索しているかに依存するため、量よりも一貫した収集方法の方が重要になる。
- 場所と言語を固定する。 結果は国によって、しばしば都市によっても変わる。毎回同じ場所と言語の設定を使わなければ、実行間の比較で市場が混在してしまう。
- 安定したクエリを使う。 毎回同じ商品用語、理想的には商品名と型番で検索し、同じ出品が返ってくるようにする。
- 市場に合ったペースで収集する。 オンライン価格に関する調査によると、週単位の収集で大手小売店における通常の価格変動の大半を捉えられる一方、動きの速いカテゴリーやプロモーションには日次のチェックが必要だ。その根拠については、オンライン価格がどれだけ速く変化するかに関する調査で扱っている。
- 生の結果を保存する。 各実行の完全なレスポンスを、実行時刻と設定とともに保存し、後から発見事項を再確認できるようにする。
- 比較する前に価格を正規化する。 通貨、小数点形式、税込みかどうかは市場によって異なる。パース処理については、地域ごとの価格・数値・日付の正規化に関するガイドで扱っている。
自社サーバーから大規模にGoogle検索を実行すると、すぐにブロックや不整合な結果に直面する。そのため、ほとんどのチームはこれ専用に構築されたAPIを使っている。ShifterのSERP APIは、設定した場所と言語で、Google Shopping結果を構造化されたJSONとして返すため、作業は収集ではなく分析から始められる。
結果から価格ビューへ
各出品の商品、販売店、価格がシンプルなレコードにマッピングされれば、わずか数行のコードで、価格設定チームが実際に必要とするビューが生成できる。以下のモジュールは、1回の検索のリクエストパラメータを構築し、ある実行のオファーを商品ごとのサマリーに変換する。最安値の販売店、中央値の価格、ばらつきの広さ、そして自社店舗の順位だ。
from collections import defaultdict
from statistics import median
def shopping_params(query, location, country, language="en", api_key="YOUR_API_KEY"):
"""One Google Shopping search, as seen by a shopper in `location`."""
return {"engine": "google_shopping", "api_key": api_key, "q": query,
"location": location, "gl": country, "hl": language}
def market_view(offers, my_seller):
"""offers: [{"product": key, "seller": name, "price": float}] from one search run.
For each product: the cheapest seller, the median price, how wide the spread
is, and where `my_seller` ranks on price.
"""
by_product = defaultdict(list)
for o in offers:
by_product[o["product"]].append(o)
report = {}
for product, rows in by_product.items():
rows.sort(key=lambda o: o["price"])
prices = [o["price"] for o in rows]
mine = next((i for i, o in enumerate(rows) if o["seller"] == my_seller), None)
report[product] = {
"sellers": len(rows),
"cheapest": f"{rows[0]['seller']} {prices[0]:.2f}",
"median": round(median(prices), 2),
"spread_pct": round(100 * (prices[-1] - prices[0]) / prices[0], 1),
"my_rank": None if mine is None else mine + 1,
"gap_to_cheapest_pct": None if mine is None
else round(100 * (rows[mine]["price"] - prices[0]) / prices[0], 1),
}
return report
いくつかの例示的なオファーで実行する:
from shopping import market_view
offers = [ # illustrative results from one search run, mapped into the offer shape
{"product": "air fryer 6qt", "seller": "Store A", "price": 89.99},
{"product": "air fryer 6qt", "seller": "Our Store", "price": 99.00},
{"product": "air fryer 6qt", "seller": "Store C", "price": 119.95},
{"product": "blender 1.5l", "seller": "Our Store", "price": 54.00},
{"product": "blender 1.5l", "seller": "Store B", "price": 59.99},
]
for product, view in market_view(offers, my_seller="Our Store").items():
print(product, view)
air fryer 6qt {'sellers': 3, 'cheapest': 'Store A 89.99', 'median': 99.0, 'spread_pct': 33.3, 'my_rank': 2, 'gap_to_cheapest_pct': 10.0}
blender 1.5l {'sellers': 2, 'cheapest': 'Our Store 54.00', 'median': 57.0, 'spread_pct': 11.1, 'my_rank': 1, 'gap_to_cheapest_pct': 0.0}
air fryerは競合が多く、ばらつきも大きい商品で、Our Storeは最安値より10%高い2位に位置している。blenderは静かな商品で、Our Storeはすでに最安値だ。同じレポートを毎日実行すれば、自社より下に新しい販売店が現れたり、ばらつきが急に広がったりといった変化こそが、対応すべきシグナルとなる。
シグナルを意思決定に変える
- 可視性のある場所で価格を調整する。 すでに1位または2位にランクされている商品は価格で勝っており、マージンを回復できる余地があるかもしれない。最下位にランクされている商品は、価格変更が最も多くのトラフィックを動かす場所だ。
- 最安値だけでなく、ばらつきにも注目する。 ばらつきが大きいということは、通常は価格設定の余地がある市場を意味する。狭いということは、わずかな差が重要になるコモディティ市場を意味する。
- プロモーションと値下げを区別する。 1週間で元に戻る競合の値下げはプロモーションであり、維持されるものは新しい価格設定である。この区別の方法については、競合のプロモーションと割引サイクルの追跡に関するガイドで示している。
- 拡大する前に市場を比較する。 新しい国における販売店の数とその価格から、その市場に出品する前にどれだけ競合が多いかがわかる。
留意すべき限界
- Google Shoppingに全ての販売店が出品しているわけではない。 データは可視化された市場を示すものであり、市場全体ではない。
- 表示価格が常に最終価格とは限らない。 配送料、税金、クーポン、会員価格によって買い物客が実際に支払う金額は変わる。
- 全く同じ検索でも結果は変動する。 1回の実行の順序ではなく、複数回の実行にわたって保持されるパターンに基づいて行動すべきだ。
- パーソナライゼーションは限定的だが実在する。 実行間の比較を保つため、中立的な設定と固定した場所を使う。
よくある質問
Google Shopping APIとは何か?
Google Shopping検索を代わりに実行し、その結果を構造化データとして返すサービスである。商品、それを提供する販売店、価格を、指定した場所と言語から見た形で返す。
EコマースチームはGoogle Shoppingデータで何ができるか?
多数の販売店にわたる競合の価格を一度に追跡し、自社の価格がどの順位にあるかを確認し、プロモーションや新規販売店を発見し、拡大前に市場を比較し、再販業者を最低広告価格と照合できる。
Google Shopping結果はどのくらいの頻度で収集すべきか?
多くのカテゴリーでは週次で十分だが、変化の速いカテゴリー、プロモーション時期、ピークシーズンには日次が必要になる。実行間で比較できるよう、場所、言語、クエリは固定しておく。
Google Shoppingは全ての販売店の価格を表示するか?
いいえ。そこに商品をリストしている小売店のみを表示し、表示されるのはリスト価格である。これは配送料、クーポン、チェックアウト時の価格とは異なる場合がある。完全なカバレッジが必要な場合は、小売店への直接クロールと組み合わせること。
まとめ
Google Shopping結果は、商品市場を最も効率的に把握できる手段のひとつである。多数の販売店がすでにグループ化され、現地の買い物客が見るままの形で表示される。場所、言語、クエリを固定して収集し、生の結果を保存し、価格を正規化し、各実行を順位、ばらつき、最安値とのギャップに要約する。これらのわずかな数値を時系列で観察することで、Eコマースチームはどこで価格を動かし、どこは動かさずにおくべきかがわかる。より広範なEコマースデータのニーズについては、Eコマースソリューションを参照のこと。