TECH JOURNAL

eBay Trading API で大量出品を自動化する — AddFixedPriceItem バッチ処理の設計

ShareXB!
eBay Trading API で大量出品を自動化する — AddFixedPriceItem バッチ処理の設計
目次

はじめに

eBay への大量出品を自動化する際には Trading API の AddFixedPriceItem を使う。ただし eBay の API 設計は Amazon SP-API や Shopify API と比べて独自の制約が多く、最初は戸惑う点が多い。

ここでは Trading API を使った一括出品処理の実装パターンと、よくあるハマりどころを解説する。

Trading API の認証

eBay Trading API は SOAP ベースの XML API。REST API(Browse API、Inventory API など)とは別系統で、XML ボディのリクエストを送る。

// eBay Trading API 呼び出しの基本
async function callTradingApi(callName: string, xmlBody: string): Promise<string> {
  const response = await fetch("https://api.ebay.com/ws/api.dll", {
    method: "POST",
    headers: {
      "X-EBAY-API-CALL-NAME": callName,
      "X-EBAY-API-SITEID": "0",     // 0: eBay US, 146: eBay AU など
      "X-EBAY-API-COMPATIBILITY-LEVEL": "1155",
      "X-EBAY-API-APP-NAME": APP_ID,
      "X-EBAY-API-DEV-NAME": DEV_ID,
      "X-EBAY-API-CERT-NAME": CERT_ID,
      Authorization: `Bearer ${userToken}`,
      "Content-Type": "text/xml",
    },
    body: xmlBody,
  })
  return response.text()
}

AddFixedPriceItem の基本構造

function buildAddFixedPriceItemXml(item: EbayItem): string {
  return `<?xml version="1.0" encoding="utf-8"?>
<AddFixedPriceItemRequest xmlns="urn:ebay:apis:eBLBaseComponents">
  <RequesterCredentials>
    <eBayAuthToken>${USER_TOKEN}</eBayAuthToken>
  </RequesterCredentials>
  <Item>
    <Title>${escapeXml(item.title)}</Title>
    <Description>${escapeXml(item.description)}</Description>
    <PrimaryCategory>
      <CategoryID>${item.categoryId}</CategoryID>
    </PrimaryCategory>
    <StartPrice>${item.price}</StartPrice>
    <Quantity>${item.quantity}</Quantity>
    <ListingDuration>GTC</ListingDuration>
    <ListingType>FixedPriceItem</ListingType>
    <Country>JP</Country>
    <Currency>USD</Currency>
    <ConditionID>${item.conditionId}</ConditionID>
    <ItemSpecifics>
      ${item.specifics.map((s) => `
        <NameValueList>
          <Name>${escapeXml(s.name)}</Name>
          <Value>${escapeXml(s.value)}</Value>
        </NameValueList>
      `).join("")}
    </ItemSpecifics>
    <SKU>${escapeXml(item.sku)}</SKU>
    <DispatchTimeMax>3</DispatchTimeMax>
    <ShippingDetails>
      <ShippingType>Flat</ShippingType>
      <ShippingServiceOptions>
        <ShippingService>JP_EMS</ShippingService>
        <ShippingServiceCost>15.00</ShippingServiceCost>
      </ShippingServiceOptions>
    </ShippingDetails>
    <ReturnPolicy>
      <ReturnsAcceptedOption>ReturnsAccepted</ReturnsAcceptedOption>
      <RefundOption>MoneyBack</RefundOption>
      <ReturnsWithinOption>Days_30</ReturnsWithinOption>
    </ReturnPolicy>
  </Item>
</AddFixedPriceItemRequest>`
}

バッチ処理の設計

Trading API の呼び出し上限は 1 日あたりのコール数制限(アプリごとに異なる)がある。大量出品を効率よくさばくためのバッチ設計。

async function bulkListItems(items: EbayItem[]) {
  const results: Array<{ sku: string; itemId?: string; error?: string }> = []
  const CONCURRENCY = 3 // 同時実行数は少なめに
  const DELAY_BETWEEN_BATCHES = 1000 // 1 秒インターバル
 
  for (let i = 0; i < items.length; i += CONCURRENCY) {
    const batch = items.slice(i, i + CONCURRENCY)
    const batchResults = await Promise.allSettled(
      batch.map(async (item) => {
        const xml = buildAddFixedPriceItemXml(item)
        const response = await callTradingApi("AddFixedPriceItem", xml)
        const itemId = parseItemId(response)
        return { sku: item.sku, itemId }
      })
    )
 
    for (const result of batchResults) {
      if (result.status === "fulfilled") {
        results.push(result.value)
      } else {
        results.push({ sku: "unknown", error: String(result.reason) })
      }
    }
 
    await new Promise((resolve) => setTimeout(resolve, DELAY_BETWEEN_BATCHES))
  }
 
  return results
}

カテゴリ ID の取得

eBay のカテゴリ ID は GetCategories で取得する。ただし最大 3 万を超えるカテゴリがあり、毎回リクエストするとコールを消費する。カテゴリツリーはローカルにキャッシュして定期更新する運用にする。

// カテゴリツリーの日次キャッシュ
async function getCategoryTree(): Promise<Map<string, string>> {
  const cached = await redis.get("ebay:categories")
  if (cached) return new Map(JSON.parse(cached))
 
  const xml = buildGetCategoriesXml()
  const response = await callTradingApi("GetCategories", xml)
  const tree = parseCategoryTree(response)
 
  await redis.setex("ebay:categories", 86400, JSON.stringify(Array.from(tree)))
  return tree
}

ConditionID の対応表

eBay の商品コンディションは数値 ID で指定する。日本から出品する場合に多い値。

IDコンディション
1000New
1500New other
2000Manufacturer refurbished
2500Seller refurbished
3000Used
7000For parts or not working

カテゴリによって指定できる ConditionID が異なる。GetCategoryFeatures で確認できる。

XML エスケープを忘れない

商品タイトルや説明文に &、<、> が含まれると XML が壊れる。必ず escapeXml を挟む。

function escapeXml(str: string): string {
  return str
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&apos;")
}

まとめ

eBay Trading API は XML-SOAP 形式で古くさく見えるが、フィールドの粒度が細かく在庫管理・出品管理の制御が細かくできる。バッチ処理設計の要点は、並列数を絞ること・カテゴリツリーをキャッシュすること・XML エスケープを徹底すること。

ShareXB!

この記事を書いた人

渡部 誠也

執行役員 / CTO

独立系 SIer で Web・組み込み・基幹システムの開発を経験し、2017 年に illustrious へ。CTO としてシステム開発事業を立ち上げ、要件定義からコーディングまで一貫して担う。EC に特化した Web アプリケーションを数多く手がける。

ECの業務やシステムについて、
ご相談ください。

いまの運用で困っていること、実現したいことから、一緒に整理します。