目次
はじめに
楽天市場で大量の商品を管理していると、商品情報の一括更新を API で自動化したくなる。しかし RMS の商品 API はいくつかの「罠」があり、ナイーブに実装するとすぐにデータ破損が起きる。
実際の受託案件で踏んだハマりどころを、対処コード付きで解説する。
ハマりどころ 1: 文字コードの混在
RMS Web API の商品系 API は、フィールドによって UTF-8 と Shift_JIS が混在していることがある(特に旧 API との後方互換性のために残っているフィールド)。JSON API は基本 UTF-8 だが、CSV 出力や旧エンドポイントを使う場合は注意が必要。
対処として、入稿前にすべての文字列を Shift_JIS で表現可能かチェックする関数を挟む。
// Shift_JIS に変換できない文字を検出する
import iconv from "iconv-lite"
function containsUnsupportedChars(text: string): boolean {
const encoded = iconv.encode(text, "Shift_JIS")
const decoded = iconv.decode(encoded, "Shift_JIS")
return decoded !== text
}
// 入稿前のサニタイズ
function sanitizeForRms(text: string): string {
// ① 絵文字・特殊 Unicode を除去
const noEmoji = text.replace(/[\u{1F000}-\u{1FFFF}]/gu, "")
// ② 全角←→半角の統一(必要に応じて)
return noEmoji
}商品名に顧客が入力した絵文字が混入してくるケースが多い。入力時点で弾くか、API 入稿前にサニタイズするかを設計段階で決めておく。
ハマりどころ 2: 商品名 127 文字制限(バイト数)
楽天の商品名は 127 文字(全角は 1 文字 = 2 バイト換算で 64 文字が上限目安)という制限がある。この「127」がバイト数なのか文字数なのかで混乱しやすい。
実際の制限は 127 バイト(Shift_JIS 換算)。全角文字はほとんど 2 バイトなので、全角 63 文字 + 半角 1 文字がギリギリのライン。
const ELLIPSIS = "…"
const ELLIPSIS_BYTES = iconv.encode(ELLIPSIS, "Shift_JIS").length // Shift_JIS で 2 バイト
const MAX_BYTES = 127
function truncateToRmsItemName(name: string): string {
const encoded = iconv.encode(name, "Shift_JIS")
if (encoded.length <= MAX_BYTES) return name
// 省略記号分のバイトを先に確保してから切り詰める
const limit = MAX_BYTES - ELLIPSIS_BYTES
let byteCount = 0
let charCount = 0
for (const char of name) {
const charBytes = iconv.encode(char, "Shift_JIS").length
if (byteCount + charBytes > limit) break
byteCount += charBytes
charCount++
}
return name.slice(0, charCount) + ELLIPSIS
}自動で末尾に「…」を付けるかどうかはビジネス判断。商品名が短くなると検索ヒット率に影響するので、別途 SEO チームと調整が必要な場合もある。
ハマりどころ 3: SKU 数の上限
楽天の 1 商品(アイテム)に登録できる SKU 数は上限がある。現状の制限は 1 商品あたり最大 10,000 SKU だが、実際には数百 SKU を超えたあたりから管理画面の動作が重くなる。
大量 SKU を持つ商品は、以下のいずれかの方針を取る。
- 商品を分割: サイズ別・カラー別に複数の楽天アイテムに分割
- SKU を整理: 販売終了 SKU を API で非表示化(
inventoryのshowFlagを0に設定)
// 販売終了 SKU を非表示にする
async function hideDiscontinuedSku(itemUrl: string, skuId: string) {
return callRmsApi("item/2.0/update", {
itemUrl,
skuInfo: {
skuId,
inventory: {
showFlag: 0, // 0: 非表示
orderFlag: 0,
},
},
})
}ハマりどころ 4: 在庫 0 と非表示の違い
在庫を 0 に設定すると「在庫なし」として表示され続ける(楽天の在庫なし表示が出る)。「この SKU は取り扱わない」という場合は showFlag: 0 で非表示にする必要がある。
混同すると、取り扱い終了した商品が「在庫なし」として延々と表示され続け、顧客体験が悪化する。
一括更新時のバッチサイズ
item/2.0/update は 1 リクエストで 1 商品しか更新できない。大量商品を更新する場合は適切なバッチ間隔が必要。
async function bulkUpdateItems(items: RmsItem[]) {
const BATCH_SIZE = 10
const DELAY_MS = 200 // 200ms インターバルでスロットリング
for (let i = 0; i < items.length; i += BATCH_SIZE) {
const batch = items.slice(i, i + BATCH_SIZE)
await Promise.all(batch.map((item) => updateItem(item)))
if (i + BATCH_SIZE < items.length) {
await new Promise((resolve) => setTimeout(resolve, DELAY_MS))
}
}
}まとめ
| 問題 | 対策 |
|---|---|
| 文字化け | Shift_JIS エンコード可能か事前チェック |
| 商品名が長すぎる | 127 バイト(Shift_JIS)でバイト数ベースで切り詰め |
| SKU 数肥大化 | 販売終了 SKU を showFlag: 0 で非表示化 |
| 在庫 0 と非表示の混同 | 取り扱い終了は showFlag: 0、一時欠品は在庫 0 |
| レートリミット | バッチサイズ 10 件 + 200ms インターバル |



