eBay Browse API getItem を GAS で叩く方法(サンプルコード付き)

eBay API

getItem は、特定の商品ID(ItemID)を使って、eBay上の商品詳細データを取得するAPI です。タイトル、価格、画像URL、配送情報、カテゴリ、状態、ItemSpecifics(商品属性)など、1点の商品に関する多くの情報をまとめて取ることができます。

この記事では、eBay Browse API の getItem エンドポイントを、Google Apps Script(GAS)から呼び出す方法を紹介します。

このサンプルコードでは、実行前にアクセストークンの取得(有効性の確認)を行い、その後 getItem を呼び出してデータを取得するという流れになっています。

  • ✅ 数値の ItemID を渡すだけで実行可能
  • ✅ 結果を 元コード互換の配列([0〜15])で返却
  • ItemSpecifics(商品属性)は [name, value] 形式の配列で取得
  • calcStatus() で終了日時を元に簡易販売状態を付与

1. 使用する API

◆ Browse API / getItem

  • 商品 ID(ItemID)をキーに 商品詳細を取得します。
  • API Docs: getItem Browse API

2. 事前準備

  1. eBay Developer Program に登録
  2. App を作成して Client ID / Client Secret を取得
  3. GAS のプロジェクト設定 → 「スクリプトのプロパティ」に各パラメータを保存
    ※Google Apps Script → 歯車 → プロジェクトのプロパティ → スクリプトのプロパティ
プロパティ名値 / 説明
CLIENT_ID1Client ID
CLIENT_SECRET1Client Secret
MARKETPLACE_ID任意(既定: EBAY_US

MARKETPLACE_ID:EBAY_US / EBAY_GB / EBAY_DE / EBAY_AU …


3. サンプルコード(最終版)

下記コードを GAS にそのまま貼り付ければ実行できます

サンプルコード(クリックして開く)
/***********************
 * eBay Browse API: getItem(元の出力形式に準拠・最終版)
 * - 数値の ItemID を渡すと v1 形式に自動変換(例: "177..." → "v1|177...|0")
 * - client_credentials(アプリ認証)でアクセストークン取得&CacheServiceで短期キャッシュ
 * - 401(期限切れ)時はトークンを取り直して1回だけ再試行
 * - MARKETPLACE(例: EBAY_US)をヘッダで指定
 * - 出力は配列 [0..15] で、あなたの元コードと同じ順番
 ***********************/

const CLIENT_ID  = PropertiesService.getScriptProperties().getProperty('CLIENT_ID1');
const CLIENT_SEC = PropertiesService.getScriptProperties().getProperty('CLIENT_SECRET1');
const MARKETPLACE_ID =
  PropertiesService.getScriptProperties().getProperty('MARKETPLACE_ID') || 'EBAY_US';

const EBAY_OAUTH_TOKEN_URL = 'https://api.ebay.com/identity/v1/oauth2/token';
const EBAY_BROWSE_BASE     = 'https://api.ebay.com/buy/browse/v1/item/';

/** 動作テスト(開発時用) */
function demo_getItem() {
  const legacyItemId = "177533256340";    // ← テストしたい ItemID に変更
  const data = ebayBrowseGetItem(legacyItemId);
  //Logger.log(data); // 返却配列([0..15])を確認したい場合に有効化
}

/**
 * getItem 呼び出しの入口
 * @param {string} itemId - 数値ID または "v1|<id>|0"
 * @return {Array|null}   - あなたの元配列形式で返す
 */
function ebayBrowseGetItem(itemId) {

  if (!CLIENT_ID || !CLIENT_SEC) {
    throw new Error("CLIENT_ID1 / CLIENT_SECRET1 が未設定です(スクリプトのプロパティに保存してください)");
  }

  // 数値IDで渡された場合、Browse API 推奨の "v1|<id>|0" 形式へ変換
  const browseId = itemId.startsWith("v1|") ? itemId : `v1|${itemId}|0`;

  // ① トークン(キャッシュ優先)
  let token = getAppTokenCached();

  // ② API 実行
  let res = callGetItem(browseId, token);

  // 401(期限切れ等)の場合は 1 回だけトークン再取得 → 再試行
  if (res._httpCode === 401) {
    token = refreshAppToken();
    res = callGetItem(browseId, token);
  }

  // 200 以外は失敗としてログを残して終了
  if (res._httpCode !== 200 || !res._json) {
    Logger.log(`❌ getItem Error: ${res._httpCode}`);
    Logger.log(res._rawText);
    return null;
  }

  // 元コード互換の整形&ログ出力
  return displayItemData(res._json);
}

/**
 * 元の displayItemData() と互換の配列を返しつつ、
 * ブログに載せやすいログを出力する処理。
 */
function displayItemData(data) {

  // eBay 側エラー
  if ("errors" in data) {
    Logger.log("❌ APIエラー: " + JSON.stringify(data.errors));
    return null;
  }

  // --- 基本情報の抽出 ---
  const title = data.title || "";
  const image = data.image?.imageUrl || "";
  const price = data.price?.value || "";
  const currency = data.price?.currency || "";

  // 送料(最初のオプションを採用)
  const shippingOpt = data.shippingOptions?.[0] || {};
  const shipping = shippingOpt.shippingCost?.value || "";
  const shippingCurrency = shippingOpt.shippingCost?.currency || "";

  // ★ 在庫・販売ステータス(復活版):終了日時で簡易判定
  const status = calcStatus(data.itemEndDate); // "instock" / "outofstock"

  // セラー情報
  const seller = data.seller?.username || "";
  const feedbackPercent = data.seller?.feedbackPercentage || "";
  const feedbackScore   = data.seller?.feedbackScore || "";

  // カテゴリ・コンディション
  const categoryId   = data.categoryId || "";
  const categoryName = data.categoryPath || "";
  const condition    = data.condition || "";
  const conditionId  = data.conditionId || "";

  // URL・所在地
  const itemUrl = data.itemWebUrl || "";
  const loc = data.itemLocation;
  const location = loc ? `${loc.postalCode || ""} ${loc.country || ""}`.trim() : "";

  // 説明文(HTML タグを排除してプレーン化)
  let description = (data.description || "")
    .replace(/<br\s*\/?>/gi, "\n")
    .replace(/<\/p>/gi, "\n")
    .replace(/<[^>]*>/g, "")
    .trim();

  // ItemSpecifics(localizedAspects)→ [name, value] の配列へ整形
  let itemSpecifics = [];
  if (Array.isArray(data.localizedAspects)) {
    itemSpecifics = data.localizedAspects.map(sp => {
      const name = sp.name || "";
      const value = Array.isArray(sp.value) ? sp.value.join(", ") : (sp.value || "");
      return [name, value];
    });
  }

  // 日付系(JST 文字列)
  const ListDate = toJST(data.itemCreationDate) || "";
  const EndDate  = toJST(data.itemEndDate) || "";

  // --- ログ出力 ---
  Logger.log(`Title: ${title}`);
  Logger.log(`Image URL: ${image}`);
  Logger.log(`Price: ${price} ${currency}`);
  Logger.log(`Shipping: ${shipping} ${shippingCurrency}`);
  Logger.log(`Status: ${status}`);
  Logger.log(`Seller ID: ${seller}`);
  Logger.log(`Feedback: ${feedbackPercent}% (${feedbackScore})`);
  Logger.log(`Category: ${categoryName} (ID: ${categoryId})`);
  Logger.log(`Ccondition: ${condition} (ID: ${conditionId})`);
  Logger.log(`Item URL: ${itemUrl}`);
  Logger.log(`Location: ${location}`);
  //Logger.log(`Description:\n${description}`); // ← 長くなる場合は必要に応じて有効化
  Logger.log(`ListDate: ${ListDate}`);
  Logger.log(`EndDate : ${EndDate}`);
  Logger.log(`ItemSpecifics : ${itemSpecifics}`);

  // --- 呼び出し元と同じ配列形式で返却([0..15]) ---
  return [
    title,        // 0
    price,        // 1
    shipping,     // 2
    status,       // 3  ← calcStatus の結果
    seller,       // 4
    feedbackScore,// 5
    categoryId,   // 6
    categoryName, // 7
    condition,    // 8
    conditionId,  // 9
    itemUrl,      //10
    image,        //11
    description,  //12
    ListDate,     //13
    EndDate,      //14
    itemSpecifics //15 例: [["Brand","Canon"],["Model","EOS M6"], ...]
  ];
}

/********************************************
 * ↓ 以降:HTTP呼び出し・トークン管理・ユーティリティ
 ********************************************/

/** Browse getItem を実行(HTTP) */
function callGetItem(browseItemId, token) {
  const url = EBAY_BROWSE_BASE + encodeURIComponent(browseItemId);

  // マーケット指定で価格/送料の文脈が変わります(例:EBAY_US)
  const headers = {
    Authorization: `Bearer ${token}`,
    "Content-Type": "application/json",
    "X-EBAY-C-MARKETPLACE-ID": MARKETPLACE_ID,
    "Accept-Language": "en-US", // 表示言語のヒント(任意)
    // "X-EBAY-C-ENDUSERCTX": "contextualLocation=country=JP", // 配送見積もりの文脈を与えたい場合など
  };

  const opt = { method: "get", headers, muteHttpExceptions: true };
  const resp = UrlFetchApp.fetch(url, opt);
  const code = resp.getResponseCode();

  return {
    _httpCode: code,
    _rawText: resp.getContentText(),
    _json: safeJson(resp.getContentText())
  };
}

/** 在庫・販売ステータスの簡易判定
 *  - itemEndDate が現在時刻より過去 → "outofstock"
 *  - それ以外 → "instock"
 *  - 注意:実在庫と完全一致ではなく「終了日時ベースの目安」
 */
function calcStatus(itemEndDate) {
  if (!itemEndDate) return "instock";
  const end = new Date(itemEndDate);
  const now = new Date();
  return end <= now ? "outofstock" : "instock";
}

/** アプリトークン(client_credentials)をキャッシュ優先で取得 */
function getAppTokenCached() {
  const cache = CacheService.getScriptCache();
  const c = cache.get("EBAY_APP_TOKEN");
  if (c) return c;

  const t = refreshAppToken();
  if (t) cache.put("EBAY_APP_TOKEN", t, 60 * 25); // 25分キャッシュ(有効30分想定)
  return t;
}

/** 強制的に新しいトークンを発行(401時など) */
function refreshAppToken() {
  const basic = Utilities.base64Encode(`${CLIENT_ID}:${CLIENT_SEC}`);

  const payload =
    "grant_type=client_credentials&scope=" +
    encodeURIComponent("https://api.ebay.com/oauth/api_scope"); // Browseの読み取りはこれで十分

  const resp = UrlFetchApp.fetch(EBAY_OAUTH_TOKEN_URL, {
    method: "post",
    headers: {
      Authorization: `Basic ${basic}`,
      "Content-Type": "application/x-www-form-urlencoded",
    },
    payload: payload,
    muteHttpExceptions: true,
  });

  if (resp.getResponseCode() !== 200) {
    Logger.log("Token error");
    Logger.log(resp.getContentText());
    return null;
  }
  return JSON.parse(resp.getContentText()).access_token;
}

/** ISO8601 → JST "yyyy/mm/dd" 文字列に整形(表示用の簡易版) */
function toJST(iso) {
  if (!iso) return "";
  const d = new Date(iso);                         // UTC ベース
  const j = new Date(d.getTime() + 9 * 3600 * 1000); // JST = UTC+9
  const y = j.getFullYear();
  const m = String(j.getMonth() + 1).padStart(2, "0");
  const dd = String(j.getDate()).padStart(2, "0");
  return `${y}/${m}/${dd}`;
}

/** JSON を安全にパース(失敗時 null) */
function safeJson(txt) {
  try { return JSON.parse(txt); } catch (e) { return null; }
}

4. コード概要

◆ demo_getItem()

  • 動作確認用のテスト関数
  • 数字の ItemID を渡せば OK
function demo_getItem() {
  const legacyItemId = "177533250000";
  const data = ebayBrowseGetItem(legacyItemId);
}

◆ ebayBrowseGetItem(itemId)

  • getItem の実行入口
  • 数値 ItemID を v1|<id>|0 に自動変換
  • トークン取得 → API → JSON → 整形 → 配列返却

◆ トークン管理

  • 実行前にアクセストークンを取得し、期限内であれば再利用します
  • 401(期限切れなど)の場合は、アクセストークンを再取得して再試行します

※技術メモ(必要な人向け) eBay API が用意する「client_credentials」という方式でアクセストークンを取得しています。 (ユーザーログイン不要/アプリ単体で取得可能)


◆ displayItemData(data)

  • レスポンス JSON を整形し、元コード互換の配列 [0..15] で返却
  • 同時にログを出力し、ブログ記載用にも使える

取り出し項目(抜粋)

index内容
0title
1price
2shipping
3status
4seller
5feedbackScore
6categoryId
7categoryName
8condition
9conditionId
10itemUrl
11image
12description
13ListDate
14EndDate
15itemSpecifics

5. 販売状態 status 判定

itemEndDate を使って、簡易的な販売状態を付与します。
※eBay の実在庫・出品状態と必ずしも一致するわけではありませんが、傾向を掴む用途には有効です。

状態条件
instock終了日時 > 現在
outofstock終了日時 ≤ 現在

6. ItemSpecifics の扱い

localizedAspects

[ [ name, value ], ... ]

形式に整形して返しています。

例)Brand を取得

const specs = data[15]; // itemSpecifics
for (const [name, value] of specs) {
  if (name === "Brand") Logger.log(value);
}

8. まとめ

eBay の商品データは、Google Apps Script(GAS)から手軽に取得できます。ItemID を渡すだけで、タイトルや価格、配送情報、カテゴリ、状態、ItemSpecifics など幅広い情報を取得でき、既存コードとの互換性を保った配列形式で扱えるため、後続の処理にも流用しやすい点が大きな魅力です。

また、トークンは自動で取得・キャッシュされ、有効期限切れの際も自動再試行が行われるため、日次や定期的なバッチ処理にも安心して組み込めます。取得したデータをスプレッドシートへ連携し、価格調整や在庫チェックなどの用途に応用すれば、業務自動化の可能性がさらに広がります。

ぜひ、GAS と eBay API を組み合わせて、日々の運用を柔軟に効率化してみてください。