Shopify × Amazon Personalizeで実現するリアルタイムなレコメンド機能

Shopify × Amazon Personalizeで実現するリアルタイムなレコメンド機能

目次

    はじめに

    Amazon Personalizeとは

    Amazon Personalize は、AWS が提供するレコメンデーションサービスです。 ユーザーの閲覧・購入などの行動履歴(Interactions)や、商品属性(Items)、ユーザー属性(Users)といったデータをもとに学習し、ユーザーごとに最適化されたおすすめ商品やランキングをリアルタイムに生成できます。
    また、レコメンドの用途に応じて「おすすめ商品を提案する」「商品一覧の表示順を最適化する」など、目的に合わせたモデル(レシピ)を選択できる点が特徴です。


    今回のゴール

    Shopifyのトップページに、Amazon Personalizeを利用して、そのユーザー専用の「おすすめ商品」を表示します。

    shopify_aws_personalize_recommendation_1

    どんな課題を解決するか

    商品点数が多いECサイトでは、ユーザーがどの商品を選べばよいか迷ってしまい、購入まで至らずに離脱してしまうケースがあります。レコメンド機能を活用することで、ユーザーの興味・関心に合った商品を適切に提示できるため、こうした迷いを軽減し、スムーズな商品選択を支援できます。さらに、ユーザーの閲覧や購入といった行動に応じてリアルタイムにおすすめを更新することで、よりタイムリーで精度の高い提案が可能になります。

    全体構成(アーキテクチャ)


    全体の構成図

    Shopifyストア上のユーザー行動(閲覧)をトラッキングし、Shopifyの「App Proxy」機能を中継してAWSへセキュアに送信する仕組みを構築しました。 AWS側では、受信したデータをAmazon Personalizeへ連携し、リアルタイムでの学習を行います。
    最終的に、Personalizeが算出した推論結果(レコメンド商品ID)をShopifyの商品情報と紐付け、ユーザーごとに最適化されたおすすめ商品を画面上に表示しています。

    機能A:ユーザー行動をリアルタイムに「学習する」フロー

    shopify_aws_personalize_recommendation_2

    機能B:おすすめ商品を「提案する」フロー

    shopify_aws_personalize_recommendation_3

    実装のポイント

    今回の実装は、大きく以下の3つのステップで構成されています。

    1. 共通基盤の準備
    2. 機能A:ユーザー行動をリアルタイムに「学習する」
    3. 機能B:おすすめ商品を「提案する」


    1. 共通基盤の準備

    まずは、レコメンドエンジンの本体であるAmazon Personalizeの定義と、ShopifyとAWSをつなぐ通信経路(App Proxy)を整備します。

    Amazon Personalizeの定義

    1. データセットとスキーマの定義
    Personalizeがデータを理解するための設計図(スキーマ)を定義します。 今回はシンプルに、ユーザーの行動履歴(Interactions)を使用しています。

    • USER_ID: 誰が
    • ITEM_ID: 何を (Variant ID)
    • TIMESTAMP: いつ
    • EVENT_TYPE: どうした (product_viewed等)

    shopify_aws_personalize_recommendation_4

    2. 初期学習用データのインポート
    Personalizeが推論を行うためには、学習元となる「過去の行動データ」が必須です。 ストア導入直後はまだリアルタイムデータが蓄積されていないため、今回は初期学習用として 10万行以上のサンプルデータ(CSV) を生成し、インポートしました。
    データ構成はスキーマに合わせて以下のようにしています。

    • USER_ID:ランダム生成したユーザーID
    • ITEM_ID:Shopifyに実在する商品のVariant ID
    • TIMESTAMP:過去のUNIXタイムスタンプ
    • EVENT_TYPE:商品閲覧

    shopify_aws_personalize_recommendation_5

    3. レシピ(アルゴリズム)の選定
    aws-user-personalization-v2:ユーザーの閲覧や購入といった行動履歴をもとに、個々の興味・嗜好に合わせた商品を自動的にレコメンドするレシピです。
    過去の行動パターンから「次に関心を持ちそうな商品」を予測できるため、ECサイトにおけるパーソナライズ施策に適しています。 また、リアルタイムで行動ログを取り込むことで、直近の閲覧傾向や新商品への反応も素早く学習し、ユーザーごとに最適化されたおすすめを継続的に更新できる点が特徴です。

    4. キャンペーンとイベントトラッカー
    学習したモデルをAPIとして利用可能にするためのリソースです。

    • Campaign(キャンペーン): 作成したソリューションバージョン(学習済みモデル)をデプロイし、推論用のエンドポイント(ARN)を発行します。
    • Event Tracker(イベントトラッカー): リアルタイム学習用のデータ受信口です。

    App Proxy(通信のプロキシ設定)

    Shopifyの「App Proxy」機能を利用して、Shopifyドメイン配下のURLへのアクセスを、Amazon API Gatewayへと転送する設定を行います。
    これにより、フロントエンドからは自ドメイン(Shopify)内のパスにアクセスしているように見えるため、クロスドメイン問題(CORS)を解決でき、かつAWSのエンドポイント情報を隠蔽することができます。
    また、App Proxy経由のリクエストには、Shopify側で自動的にデジタル署名が付与されます。AWS側(Lambda)でこの署名を検証することで、「正当なShopifyストアからのリクエストであること」を認証でき、外部からの不正なAPIアクセスを防止できます。
    プロキシの設定は、Shopify Partners画面のDev Dashboardから行うことができます。

    shopify_aws_personalize_recommendation_6-1

     

    prefix、subpath、urlは以下のように設定します。

    項目 設定値 役割
    prefix apps プロキシのルートパス
    Subpath track アプリ固有のパス
    Proxy URL https://xxxx.execute-api... 転送先のAmazon API Gateway

    設定後、ストアにプロキシをインストールします。

    shopify_aws_personalize_recommendation_7

    リンクをコピーして、新規ウィンドウで開き、画面の手順に従ってインストールします。

    shopify_aws_personalize_recommendation_8

    ストアにプロキシがインストールされているか確認します。

    shopify_aws_personalize_recommendation_9-1

     



    2. 機能A:ユーザー行動をリアルタイムに「学習する」

    概要: ユーザーが商品を閲覧したデータを、カスタムピクセルで捕捉し、Shopifyの画面を経由してAWSへ送信します。

    フロントエンド: カスタムピクセルでの計測と postMessage 送信

    Shopifyの新しいトラッキング規格である「Web Pixels API」を利用したカスタムピクセルを作成し、ユーザーの行動を捕捉します。
    従来、Shopifyのトラッキング実装は theme.liquid 内にJavaScriptを直接記述し、HTML要素(DOM)やLiquid変数を解析してデータを取得する方法が一般的でした。しかし、この手法は「テーマのHTML構造が変わるとデータが取得できなくなる」という脆さがあります。
    例えば、デザイン改修でクラス名が変更されたり、タグの階層構造が変わったりするだけで、JavaScriptが要素を見失い、データ計測が停止してしまうリスクが常にありました。
    これに対し、Shopifyが提供する「カスタムピクセル」は、ストアのフロントエンド実装(HTML/CSS)とは独立して動作するJavaScript実行環境です。 Shopify側から標準化・整形されたイベントデータ(product_viewed 等)が直接提供されるため、テーマのデザイン変更やアップデートの影響を一切受けません。
    ただし、隔離された環境のため直接App Proxyへ通信するとCORSエラーになります。そのため、postMessage を使って親ウィンドウへデータを渡す設計にします。

    カスタムピクセルの仕組みについては以下を参照してください。
    カスタムピクセルについて

    
    const buildInteractionPayload = (shopifyEventType, event) => {
    // 1. ユーザーIDの特定
    // ログイン時は user_{id}、未ログイン時は guest_{clientId} に統一
    const userId = event.customer?.id
    ? `user_${event.customer.id}`
    : `guest_${event.clientId || "unknown"}`;

    // 2. イベントタイプに応じた商品IDの抽出
    let itemId = null;
    if (shopifyEventType === "product_viewed") {
    itemId = event.data?.productVariant?.id;
    }

    // 3. タイムスタンプの変換 (ISO文字列 -> Epoch秒)
    const timestamp = Math.floor(new Date(event.timestamp).getTime() / 1000);

    return {
    userId,
    itemId,
    eventType: shopifyEventType,
    timestamp,
    };
    };

    // メイン処理: 閲覧イベント(product_viewed)を購読
    analytics.subscribe("product_viewed", (event) => {
    const payload = buildInteractionPayload("product_viewed", event);

    // サンドボックスから親ウィンドウへデータを送信
    window.top.postMessage(
    {
    from: "custom_pixel",
    type: "interaction",
    payload,
    },
    "*"
    );
    });

    フロントエンド: メイン画面での中継(CORS・セキュリティ対策)

    Shopifyストアの商品ページ(親ウィンドウ)側の theme.liquid に、データの中継処理を実装します。
    カスタムピクセルから送られてきたメッセージを受け取り、App Proxyのパス(/apps/track/track)に対して送信します。これにより、Shopifyドメイン内での通信とみなされ、セキュアにAWSへデータを転送できます。

    window.addEventListener("message", async (event) => {
    // Pixel から来たものだけ処理
    if (event.data?.from !== "custom_pixel") return;

    const payload = event.data.payload;

    try {
    await fetch("/apps/track/track", {
    method: "POST",
    headers: {
    "Content-Type": "application/json"
    },
    body: JSON.stringify(payload),
    });
    } catch (err) {
    console.error("Theme send error", err);
    }
    });

    バックエンド: Lambdaでの受信と PutEvents

    API Gateway(POST /track)を経由してLambdaがデータを受信します。
    ここでは、Amazon Personalizeの PutEvents APIを叩き、イベントトラッカーへデータを投入します。これにより、ユーザーの最新の興味関心がリアルタイムにモデルへ反映されます。

    
    import { PersonalizeEventsClient, PutEventsCommand } from "@aws-sdk/client-personalize-events";

    const client = new PersonalizeEventsClient({ region: "ap-northeast-1" });

    export const handler = async (event) => {
    const { userId, itemId, eventType, timestamp } = event;

    // Personalizeへの送信パラメータ
    const params = {
    // 環境変数からEvent TrackerのIDを取得
    trackingId: process.env.PERSONALIZE_TRACKING_ID,
    userId: userId,
    sessionId: userId, // 今回は簡易的にuserIdをセッションIDとして利用
    eventList: [
    {
    itemId: itemId,
    eventType: eventType, // 例: 'product_viewed'
    sentAt: new Date(timestamp * 1000), // Unix TimestampをDate型に変換
    },
    ],
    };

    try {
    // データを送信(リアルタイム学習)
    const command = new PutEventsCommand(params);
    await client.send(command);
    } catch (err) {
    console.error("Failed to send PutEvents:", err);
    }
    };

     



    3. 機能B:おすすめ商品を「提案する」

    概要: ユーザーがページを開いたタイミングで、学習済みモデルから推論結果を取得し、Shopifyの商品情報と結合して画面に表示します。

    フロントエンド: 画面ロード時のID取得リクエスト

    ページ読み込み時に、ユーザーIDを特定してAWSへレコメンドをリクエストします。
    Shopifyにログインしている場合は customer.id を、未ログインの場合はクッキーからゲストIDを取得し、カスタムピクセル側と整合性を取ります。

    
    const getUserId = () => {
    // 1. ログイン時はLiquidで埋め込まれたIDを使用
    const loggedInCustomer = "{% if customer.id %}{{ customer.id }}{% endif %}";
    if (loggedInCustomer) {
    return `user_${loggedInCustomer}`;
    }

    // 2. 未ログイン時はブラウザのクッキー(_shopify_y)からユニークIDを取得
    const match = document.cookie.match(/_shopify_y=([^;]+)/);
    const clientId = match ? match[1] : 'unknown';

    return `guest_${clientId}`;
    };

    const userId = getUserId();

    // IDが取得できれば次のステップへ
    if (userId) {
    fetchRecommendations(userId);
    }

    バックエンド: Lambdaでの GetRecommendations 実行

    API Gateway(POST /recommend)を経由してLambdaがリクエストを受け取ります。
    Amazon Personalizeの GetRecommendations APIを使用し、そのユーザーに最適な商品のリスト(Variant IDの配列)を取得して返却します。

    
    // コマンドの作成
    const command = new GetRecommendationsCommand({
    campaignArn: process.env.PERSONALIZE_CAMPAIGN_ARN,
    userId: userId,
    numResults: 10
    });

    // 推論実行と結果返却
    const response = await client.send(command);
    return {
    statusCode: 200,
    body: JSON.stringify({
    // レコメンド(itemList) の結果を返す
    itemList: response.itemList
    }),
    };

    フロントエンド: 商品情報の取得とレンダリング

    AWSから返ってきたのは「商品IDのリスト」だけなので、これを使ってShopifyのAjax APIから「商品の詳細情報(画像、タイトル、価格)」を取得します。 取得したデータをAWSのレコメンド順序通りに並び替え、HTMLを生成して画面に表示します。

    
    const API_URL = '/apps/recommend/recommend'; 

    async function fetchRecommendations(currentUserId) {
    try {
    const response = await fetch(API_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ userId: currentUserId })
    });

    if (!response.ok) {
    throw new Error(`API呼び出しエラー: ${response.status}`);
    }

    const data = await response.json();
    // レスポンスから商品ID(Variant ID)の配列を抽出
    const recommendedItemIds = (data.itemList || []).map(item => item.itemId);

    if (recommendedItemIds.length > 0) {
    // Shopifyから商品情報を取得して描画する関数へ
    await fetchShopifyProductsAndRender(recommendedItemIds);
    } else {
    document.getElementById('reco-list').innerHTML = '<p>おすすめ商品がありません。</p>';
    }

    } catch (error) {
    console.error("Personalize連携エラー:", error);
    }
    }

    // Shopify商品データの取得と描画関数
    async function fetchShopifyProductsAndRender(itemIds) {
    // Shopifyの全商品データを取得
    // ※注: 本番環境で商品数が多い場合は、パフォーマンスを考慮し、ShopifyのSearch APIやSection Rendering APIの使用を推奨します
    const response = await fetch(`/products.json`);
    const data = await response.json();

    if (data.products && data.products.length > 0) {
    // AWSから返ってきた商品ID(Variant ID)と一致する商品を特定
    // ※文字列型に統一して比較
    const requestedIdStrings = itemIds.map(String);

    const orderedProducts = requestedIdStrings
    .map(idStr => {
    // 商品のバリエーションIDの中に、レコメンドされたIDが含まれているか探す
    return data.products.find(p =>
    p.variants.some(v => String(v.id) === idStr)
    );
    })
    .filter(p => !!p); // 見つかった商品だけを残す

    // HTMLを生成して描画
    if (orderedProducts.length > 0) {
    renderRecommendations(orderedProducts);
    }
    }
    }

    以上で、レコメンド機能に必要な一連の実装はすべて完了です。

    導入効果として期待できること

    今回は PutEvents によるリアルタイム学習を実装しているため、「たった今、興味を持った商品」に関連するアイテムを即座に提示できます。 個人の趣味嗜好に深く「刺さる」商品をタイミングよく提案することで、偶発的な商品発見を促し、カゴ落ちの防止や購入単価の向上が期待できます。
    また、一般的な高機能ツールは、毎月決まった高額な支払いや、売上に応じた手数料が発生することが多く、導入のハードルとなりがちです。 一方、今回の構成は 「スモールスタート」が可能な従量課金モデルです。 閲覧が少ない時期は費用を最小限に抑えられ、セールやイベントで訪問者が急増した際も、設備の増強作業なしで自動的に処理能力が拡張されます。「無駄な維持費がかからない」という仕組みは、利益率を重視するEC運営において大きな強みとなります。

    最後に

    ShopifyとAmazon Personalizeを連携させることで、ユーザー一人ひとりに寄り添った接客を自動化する仕組みを作ることができました。
    今回の構成はあくまで一例ですが、閲覧履歴だけでなく「カート放棄」や「購入」などのイベントを組み合わせることで、活用の幅は広がります。

    アジアクエスト株式会社では一緒に働いていただける方を募集しています。
    興味のある方は以下のURLを御覧ください。