Browse API の item_summary/search は、eBay 上の商品をキーワードで検索し、一覧用のサマリ情報を取得できる APIです。
「特定の商品IDの詳細を取る(getItem)」のではなく、 「キーワードから商品一覧を探す」用途に特化しているのが特徴です。
たとえば次のような場面でよく使われます。
- 相場リサーチ用に商品一覧を取得したい
- カテゴリやブランド単位で商品を収集したい
- GAS やスプレッドシートを使って、自動で検索結果を蓄積したい
この記事では、eBay Browse API の keyword search(item_summary/search)を、Google Apps Script(GAS)から呼び出す方法を紹介します。
このサンプルコードの特徴は次のとおりです。
- ✅ キーワード(
q)と取得件数(limit)を指定するだけで検索可能 - ✅ セラーID・フィードバックスコア・カテゴリ・状態などを配列形式で返却
- ✅ トークン取得ロジックは getItem 記事と共通(client_credentials + CacheService)
- ✅
filter/sortを オプション引数として受け取れる構造
1. 使用する API
◆ Browse API / item_summary.search
- キーワード(
q)を指定して、商品一覧(itemSummaries)を取得する API です。 - 価格・送料・セラー・状態など、一覧表示に必要な情報をひとまとめに取得できます。
- 詳細な説明文や ItemSpecifics は省略されることが多く、あくまで「サマリ」として使う想定です。
- API Docs:item_summary/search Browse API
2. 事前準備
- eBay Developer Program に登録
- App を作成して Client ID / Client Secret を取得
- GAS のプロジェクト設定 →「スクリプトのプロパティ」に各パラメータを保存
※Google Apps Script → 歯車 → プロジェクトのプロパティ → スクリプトのプロパティ
| プロパティ名 | 値 / 説明 |
|---|---|
CLIENT_ID1 | eBay App の Client ID |
CLIENT_SECRET1 | eBay App の Client Secret |
MARKETPLACE_ID | 任意(既定: EBAY_US) |
※ MARKETPLACE_ID:EBAY_US / EBAY_GB / EBAY_DE / EBAY_AU …
getItem 記事(「eBay Browse API getItem を GAS で叩く方法」)で設定済みであれば、そのまま流用できます。
3. サンプルコード
下記コードを GAS にそのまま貼り付ければ実行できます。
1 商品 1 行、列は [0..15] 固定の 2次元配列として返します。
filter / sort は **第3引数 **“ で指定でき、不要な場合は省略可能です。
サンプルコード(クリックして開く)
/***********************
* eBay Browse API: keyword search(item_summary/search)
* - キーワード(q)で検索し、itemSummaries を取得
* - アプリ認証トークンは getItem と共通
* - 出力形式は getItem と同じ [0..15] を 1商品=1配列で返す
* → 返り値は「2次元配列(行:商品)」になります
***********************/
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_SEARCH_BASE = 'https://api.ebay.com/buy/browse/v1/item_summary/search';
/** 動作テスト(開発時用:シンプル版) */
function demo_searchByKeyword() {
const kw = "Canon EF 24mm F2.8 Lens"; // ← テストしたいキーワード
const limit = 5; // 取得件数(最大 200 まで指定可能)
const rows = ebayBrowseSearchByKeyword(kw, limit); // options なし(filter/sort なし)
//Logger.log(JSON.stringify(rows, null, 2)); // スプレッドシート貼り付け前に確認用
}
/** 動作テスト(filter / sort を指定する例) */
function demo_searchByKeywordWithFilter() {
const kw = "Canon EF";
const limit = 20;
// 例:日本出品の新品・中古のみ、価格の安い順
const options = {
filter: 'conditionIds:{1000|3000},itemLocationCountry:{JP}',
sort: 'price' // 昇順。降順は '-price'
};
const rows = ebayBrowseSearchByKeyword(kw, limit, options);
//Logger.log(JSON.stringify(rows, null, 2));
}
/**
* キーワード検索の入口関数
* @param {string} keyword - 検索キーワード(q)
* @param {number} [limit] - 最大件数(1〜200, 省略時 10)
* @param {Object} [options] - 追加パラメータ(例:{ filter, sort })
* @return {Array<Array>} - 各行が [0..15] 形式の2次元配列
*/
function ebayBrowseSearchByKeyword(keyword, limit, options) {
if (!CLIENT_ID || !CLIENT_SEC) {
throw new Error("CLIENT_ID1 / CLIENT_SECRET1 が未設定です(スクリプトのプロパティに保存してください)");
}
if (!keyword) {
throw new Error("検索キーワード(keyword)が空です");
}
const max = (typeof limit === "number" && limit > 0) ? limit : 10;
// 追加パラメータ(filter / sort など)は options 経由で受け取る
const extraParams = options || {}; // 例:{ filter: '...', sort: '...' }
// ① トークン(キャッシュ優先)
let token = getAppTokenCached();
// ② API 実行
let res = callSearchItems(keyword, max, token, extraParams);
// 401(期限切れ等)の場合は 1 回だけトークン再取得 → 再試行
if (res._httpCode === 401) {
token = refreshAppToken();
res = callSearchItems(keyword, max, token, extraParams);
}
// 200 以外は失敗としてログを残して終了
if (res._httpCode !== 200 || !res._json) {
Logger.log(`❌ search Error: ${res._httpCode}`);
Logger.log(res._rawText);
return [];
}
const json = res._json;
// eBay 側エラー
if ("errors" in json) {
Logger.log("❌ APIエラー: " + JSON.stringify(json.errors));
return [];
}
const items = Array.isArray(json.itemSummaries) ? json.itemSummaries : [];
if (!items.length) {
Logger.log("ℹ️ 該当する itemSummaries がありません");
return [];
}
// itemSummary 1件ごとに [0..15] 配列へ整形
const rows = items.map(displayItemSummary);
Logger.log(`✅ keyword="${keyword}" / 件数=${rows.length}`);
return rows;
}
/**
* itemSummary から、getItem と同じ [0..15] 配列を生成
* (description / itemSpecifics が無いことが多いので、ある範囲だけ埋める)
* @param {Object} data - itemSummary
* @return {Array} - [0..15]
*/
function displayItemSummary(data) {
// --- 基本情報の抽出 ---
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 || "";
// 在庫・販売ステータス(itemEndDate ベース簡易判定)
const status = "instock";
// セラー情報
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() : "";
// item_summary には通常 description は含まれない → shortDescription を代用 or 空
let description = data.shortDescription || "";
description = description
.replace(/<br\s*\/?>(?i)/g, "\n")
.replace(/<\/p>(?i)/g, "\n")
.replace(/<[^>]*>/g, "")
.trim();
// item_summary では localizedAspects がない/少ないことが多い
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(`Price: ${price} ${currency} + Shipping: ${shipping} ${shippingCurrency}`);
Logger.log(`Seller: ${seller} / Feedback: ${feedbackPercent}% (${feedbackScore})`);
Logger.log(`Item URL: ${itemUrl}`);
// --- getItem と同じ配列形式で返却([0..15]) ---
return [
title, // 0
price, // 1
shipping, // 2
status, // 3
seller, // 4
feedbackScore,// 5
categoryId, // 6
categoryName, // 7
condition, // 8
conditionId, // 9
itemUrl, //10
image, //11
description, //12
ListDate, //13
EndDate, //14
itemSpecifics //15
];
}
/**
* Browse API: item_summary/search 呼び出し(HTTP)
* @param {string} keyword - q=
* @param {number} limit - limit=
* @param {string} token - Bearer トークン
* @param {Object} [extraParams]- 追加パラメータ(例:{ filter, sort })
* @return {{_httpCode:number,_rawText:string,_json:Object|null}}
*/
function callSearchItems(keyword, limit, token, extraParams) {
// クエリパラメータを組み立て
const params = {
q: keyword,
limit: String(limit),
...(extraParams || {}) // filter, sort などをマージ
};
const qs = Object.keys(params)
.map(k => `${encodeURIComponent(k)}=${encodeURIComponent(params[k])}`)
.join("&");
const url = `${EBAY_BROWSE_SEARCH_BASE}?${qs}`;
const headers = {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
"X-EBAY-C-MARKETPLACE-ID": MARKETPLACE_ID,
"Accept-Language": "en-US",
};
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())
};
}
/** アプリトークン(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_searchByKeyword()
- 動作確認用のテスト関数です。
- キーワードと取得件数を決めて実行し、返ってきた配列(rows)をログやスプレッドシートに出力して確認できます。
filter/sortを使わない、もっともシンプルな呼び出し例です。
function demo_searchByKeyword() {
const kw = "Canon EF 24mm F2.8 Lens";
const limit = 5;
const rows = ebayBrowseSearchByKeyword(kw, limit); // options なし
}
◆ demo_searchByKeywordWithFilter()
filter/sortを **第3引数 **“ で指定する例です。- 実際の運用で「新品・中古のみ」「日本出品のみ」「価格順」などにしたいときは、こちらの形をベースに編集します。
function demo_searchByKeywordWithFilter() {
const kw = "Canon EF";
const limit = 20;
const options = {
// 新品(1000) + 中古(3000) かつ 日本出品
filter: 'conditionIds:{1000|3000},itemLocationCountry:{JP}',
// 価格の安い順
sort: 'price'
};
const rows = ebayBrowseSearchByKeyword(kw, limit, options);
}
◆ ebayBrowseSearchByKeyword(keyword, limit, options)
- keyword search の「入口関数」です。
- 処理の流れは次の通りです。
- スクリプトプロパティに
CLIENT_ID1/CLIENT_SECRET1が入っているかチェック - limit 未指定時は 10 件に自動的に初期値に戻す
optionsからfilter/sortなどの追加パラメータを受け取るgetAppTokenCached()でトークン取得(キャッシュ優先)callSearchItems()でitem_summary/searchを実行- HTTP ステータス 401 の場合はトークン再取得 → 1回だけ再試行
- 200 以外、または
errorsが含まれる場合はログ出力して空配列[]を返却 - 正常時は
itemSummariesをdisplayItemSummary()で [0..15] に整形
filter / sort をコードに反映するポイント はここです:
const extraParams = options || {}; // 例:{ filter: '...', sort: '...' }
...
let res = callSearchItems(keyword, max, token, extraParams);
...
res = callSearchItems(keyword, max, token, extraParams); // 401 再試行時も同じ extraParams を渡す
extraParams の中身は、そのまま callSearchItems() に渡され、クエリパラメータとして URL に反映されます。
◆ displayItemSummary(data)
- itemSummary 1件分の JSON を、 [0..15] 配列に変換します。
- 抜き出している主な項目は次の通りです。
| index | 内容 |
|---|---|
| 0 | title |
| 1 | price |
| 2 | shipping |
| 3 | status(ここでは "instock" 固定) |
| 4 | seller(セラー ID) |
| 5 | feedbackScore |
| 6 | categoryId |
| 7 | categoryPath |
| 8 | condition |
| 9 | conditionId |
| 10 | itemUrl |
| 11 | image(画像 URL) |
| 12 | description(shortDescription からタグ除去) |
| 13 | ListDate(itemCreationDate → JST) |
| 14 | EndDate(itemEndDate → JST) |
| 15 | itemSpecifics([name, value] の配列) |
※ item_summary では description や localizedAspects が返ってこない場合もあるため、その場合は空文字/空配列として扱っています。
◆ callSearchItems(keyword, limit, token, extraParams)
- Browse API の
item_summary/searchを HTTP 経由で呼び出す関数です。 extraParamsに渡されたfilter/sortなどが、そのままクエリパラメータとしてマージされます。
const params = {
q: keyword,
limit: String(limit),
...(extraParams || {}) // ← ここで filter / sort を統合
};
filter / sort の具体例:
- 新品のみ
options = { filter: 'conditionIds:{1000}' }- 新品 + 中古
options = { filter: 'conditionIds:{1000|3000}' }- 日本出品のみ
options = { filter: 'itemLocationCountry:{JP}' }- 価格 100〜200USD のみ
options = { filter: 'price:[100..200]' }- 中古 + 日本出品 + 価格 100〜200USD
options = { filter: 'conditionIds:{3000},itemLocationCountry:{JP},price:[100..200]' }
sort の例:
- 価格昇順
options = { sort: 'price' }- 価格降順
options = { sort: '-price' }
実際の呼び出しは、例えば次のようになります。
const options = {
filter: 'conditionIds:{3000},itemLocationCountry:{JP},price:[100..200]',
sort: '-price' // 高い順
};
const rows = ebayBrowseSearchByKeyword('Canon EF', 50, options);
◆ トークン管理・ユーティリティ
getAppTokenCached()/refreshAppToken()/toJST()/safeJson()は、getItem 記事と同じ構成です。- 実行ごとにトークン取得をやり直さず、CacheService で 25分だけ再利用することで、API 呼び出し回数とレイテンシを抑えています。
- 401 エラーのときだけ
refreshAppToken()を呼び出し、1回だけ再試行しています。
5. キーワード検索で取得できる情報
今回のサンプルでは、キーワード検索の結果から、主に次のような情報を 1 行にまとめています。
- 商品タイトル(title)
- 価格・通貨(price / currency)
- 送料(shippingCost)
- セラー ID・フィードバック(username / feedbackScore / feedbackPercentage)
- カテゴリ ID・カテゴリ名(categoryId / categoryPath)
- コンディション(condition / conditionId)
- 商品ページの URL(itemWebUrl)
- 画像 URL(image.imageUrl)
- 出品日・終了日(itemCreationDate / itemEndDate → JST)
この配列構造は、getItem 記事(「eBay Browse API getItem を GAS で叩く方法」)と揃えているため、
- 「最初は keyword search で一覧を取得」
- 「気になる ItemID を getItem で詳細取得」
といった流れにもスムーズに対応できます。
6. まとめ
eBay Browse API の item_summary/search を使うと、キーワードベースで商品一覧を取得し、リサーチ用のデータを一度に集めることができます。
今回のサンプルでは、
- キーワードと limit(+必要なら options)を渡すだけで検索
- 結果を
getItem記事(「eBay Browse API getItem を GAS で叩く方法」)と同じ配列に整形 - トークン取得は client_credentials + CacheService で自動管理
filter/sortは第3引数optionsとして渡し、コード側では共通処理で URL に統合
という形にしているため、既存のツールやシートにも組み込みやすい構造になっています。
あとは、filter / sort をもう少し作り込んで、カテゴリ ID やセラー ID で絞り込んだりすることで、
- 自分用のリサーチシート
- 特定カテゴリの価格ウォッチツール
- 日本人セラー限定の一覧
などに応用していくことができます。
ぜひ、getItem 記事(「eBay Browse API getItem を GAS で叩く方法」)と合わせて、GAS × eBay Browse API による自動リサーチの第一歩として活用してみてください。
