[ トップページ ]
Web とインターネット:Web サーバと Web サービス・管理

楽天の商品 API を Python で使用するしくみをつくった

楽天に出店しているが,その商品ページの一括登録や一括編集のために商品 API をつかうようにした. 月に 1 万円はらうと CSV ファイルと商品 API による商品管理ができるようになるが,CSV ファイルは Yahoo! にくらべると不便が多いので,API をつかうようにした. 楽天が提供する API のなかで最初につかったのは SOAP による在庫 API だが,商品 API はそれとはまったくちがう REST と XML による API だ. ようやくそれが Python からつかえるようになったので,つかいかたをここにまとめる.

楽天が出店者に用意している基本的の Web インタフェースはよわい. ふつうはそれをおぎなうために楽天が用意した追加のツールや他社の有料ツールをつかうのだろうが,いろいろありすぎるし,透明度がさがってしまう. そこで在庫管理に関しては在庫 API をつかうようにしたが,商品ページの一括登録や一括編集のために商品 API もつかうようにした.

商品 API 以外は無料でつかえるが,商品 API や CSV ファイルによる商品管理機能をつかうには月に 1 万円くらい,はらわなければならない. まだ売り上げがすくないなかで月 1 万円の出費は痛いが,一括編集機能なしではすまなくなったので,やむをえない.

在庫 API は SOAP による API だった. API のドキュメントはおなじようなみかけなので商品 API も SOAP でよびだそうとしたが,WSDL がみつからない. 楽天に質問してみると,SOAP ではつかえない,REST API だという. さらに楽天に質問したり,ネットで Python のプログラムをさがしたりして,やっとつかいかたがわかった.

楽天の API はばらばらし仕様書もなっていない. SOAP をつかったものと REST をつかったものとがあり,REST によるもののなかにもデータを XML で書くものと JSON で書くものがある. 仕様もでたらめで,SOAP を使用した在庫 API はこわれたデータ構造やでたらめな項目名をつかうようになっている. 仕様書は一応あるが,まともに書かれていないので,それをみてもよくわからない. つかっているのが SOAP なのか REST なのか,データ形式が XML なのか JSON なのかということも書いてない. これで商品 API に関しては月 1 万円もとるというのだから,あきれる.

それはともかく,商品 API のなかでこれまでにつかったものを例でしめす. XML は本来は要素の順番が自由なはずだが,この API は仕様書の順序どおりでないとエラーになる. エラーメッセージのなかには,きわめて不親切でどこに問題があるのかわからないことがある. そうときにとりうる方法は,いまのところ試行錯誤しかない.

要求のヘッダ情報

REST で要求する際のヘッダは共通している.

headers = {
    'Authorization' : b"ESA " + base64.b64encode(b"" + b':' + b""),
    'Content-Type': 'application/xml; charset=utf-8',
}

商品情報の取得 (get)

まず,商品情報の取得だ.これには REST の method=get を使用する.

import requests, base64

response = requests.get(
    'https://api.rms.rakuten.co.jp/es/1.0/item/get',
    params={'itemUrl': '<itemUrl>'},
    headers = headers,
)

response.text で XML による応答内容がわかる. 応答にはたいてい <systemStatus>OK</systemStatus> がふくまれるが,これはうまくいったという意味ではない. <code>N000</code> がふくまれていればうまくいったということだが,<code>Cxxx</code> がふくまれる場合は失敗したということだ.

商品の登録 (insert)

商品情報は複雑なので,つぎのはあくまで一例だ. REST の method=post を使用する. ここでは商品 URL を確保するために最低限の情報だけ登録し,商品情報はあとで編集する方針にしている.

import requests, base64

def gen_insert_request(
        itemUrl, itemNumber, itemName, itemPrice, genreId = <Genre ID>,
        verticalName, horizontalName, # 項目選択肢別在庫の縦軸名と横軸名
        categoryId,
        isDepot = 0, # 倉庫商品でない
):
    request = \
        '<?xml version="1.0" encoding="UTF-8"?>\n' \
        '<request>\n' \
        '  <itemInsertRequest>\n' \
        '    <item>\n' \
       f'      <itemUrl>{itemUrl}</itemUrl>\n' \
       f'      <itemNumber>{itemNumber}</itemNumber>\n' \
       f'      <itemName>{itemName}</itemName>\n' \
       f'      <itemPrice>{itemPrice}</itemPrice>\n' \
       f'      <genreId>{genreId}</genreId>\n' \
       f'      <catalogId/>\n' \
       f'      <catalogIdExemptionReason>3</catalogIdExemptionReason>\n' \
       f'      <isDepot>{isDepot}</isDepot>\n' \
        '      <itemInventory>\n' \
        '        <inventoryType>2</inventoryType>\n' \
       f'        <verticalName>{verticalName}</verticalName>\n' \
       f'        <horizontalName>{horizontalName}</horizontalName>\n' \
        '      </itemInventory>\n' \
        '      <categories>\n' \
        '        <categoryInfo>\n' \
       f'          <categoryId>{categoryId}</categoryId>\n' \
        '          <isPluralItemPage>false</isPluralItemPage>\n' \
        '        </categoryInfo>\n' \
        '      </categories>\n' \
        '    </item>\n' \
        '  </itemInsertRequest>\n' \
        '</request>\n'

    return request

request = gen_insert_request(
    itemUrl = <item URL>,
    itemNumber = <item number>,
    itemName = <item name>,
    itemPrice = <price>,
    categoryId = <category ID>
)

response = requests.post(
    'https://api.rms.rakuten.co.jp/es/1.0/item/insert',
    data = request.encode('utf-8'),
    headers = headers,
)

商品内容の編集 (update)

商品名もここできめるようにしている.

import requests, base64

まず 2 枚の画像をふくむ画像リストをつくる. ここでは itemName という変数に商品名,imageUrl1, imageUrl2 に画像の URL がはいっているとする.

imageArray = \
    ( '<image>\n',
     f'  <imageUrl>{imageUrl1}</imageUrl>\n',
     f'  <imageAlt>{itemName}</imageAlt>\n',
      '</image>\n',
      '<image>\n',
     f'  <imageUrl>{imageUrl2}</imageUrl>\n',
     f'  <imageAlt>{itemName}</imageAlt>\n',
      '</image>')

images = '          ' + '          '.join(imageArray)

これを使用して XML による update 要求をつくる.

def gen_update_request(
        itemUrl, itemName, itemPrice, genreId = <genreId>,
        descriptionForPC = '', descriptionForSmartPhone = '',
        catchCopyForPC = '', catchCopyForMobile = '',
        descriptionBySalesMethod = '',
        isDepot = 1, # 倉庫商品

        InventoryCount = 100,
        normalDeliveryDateId = 10, backorderDeliveryDateId = 12,

        categoryId = 100,
        itemWeight = 15,
):
    horizontalName = '<horizontal name>'
    verticalName = '<vertical name>'
    optionNameHorizontal1 = '<horizontal option name 1>'
    optionNameVertical1 = '<vertical option name 1>'
    inventoryCount1 = InventoryCount
    optionNameHorizontal2 = '<horizontal option name 2>'
    optionNameVertical2 = '<vertical option name 2>'
    inventoryCount2 = InventoryCount

   request = \
        '<?xml version="1.0" encoding="UTF-8"?>\n' \
        '<request>\n' \
        '  <itemUpdateRequest>\n' \
        '    <item>\n' \
       f'      <itemUrl>{itemUrl}</itemUrl>\n' \
       f'      <itemNumber>{itemUrl}</itemNumber>\n' \
       f'      <itemName>{itemName}</itemName>\n' \
       f'      <itemPrice>{itemPrice}</itemPrice>\n' \
       f'      <genreId>{genreId}</genreId>\n' \
       f'      <catalogId/>\n' \
       f'      <catalogIdExemptionReason>3</catalogIdExemptionReason>\n' \
       f'      <images>\n{images}\n' \
        '      </images>\n' \
       f'      <descriptionForPC>{descriptionForPC}</descriptionForPC>\n' \
       f'      <descriptionForSmartPhone>{descriptionForSmartPhone}</descriptionForSmartPhone>\n' \
        '      <tagIds>\n' \
        '        <tagId>...</tagId>\n' \
        '        <tagId>...</tagId>\n' \
        '      </tagIds>\n' \
       f'      <catchCopyForPC>{catchCopyForPC} &lt;br /&gt;</catchCopyForPC>\n' \
       f'      <catchCopyForMobile>{catchCopyForMobile}</catchCopyForMobile>\n' \
        '      <itemInventory>\n' \
        '        <inventoryType>2</inventoryType>\n' \
        '        <inventories>\n' \
        '          <inventory>\n' \
       f'            <inventoryCount>{inventoryCount1}</inventoryCount>\n' \
       f'            <optionNameVertical>{optionNameVertical1}</optionNameVertical>\n' \
       f'            <optionNameHorizontal>{optionNameHorizontal1}</optionNameHorizontal>\n' \
        '            <isBackorderAvailable>false</isBackorderAvailable>\n' \
       f'            <normalDeliveryDateId>{normalDeliveryDateId}</normalDeliveryDateId>\n' \
       f'            <backorderDeliveryDateId>{backorderDeliveryDateId}</backorderDeliveryDateId>\n' \
        '            <isBackorder>true</isBackorder>\n' \
        '            <isRestoreInventoryFlag>true</isRestoreInventoryFlag>\n' \
        '          </inventory>\n' \
        '          <inventory>\n' \
       f'            <inventoryCount>{inventoryCount2}</inventoryCount>\n' \
       f'            <optionNameVertical>{optionNameVertical2}</optionNameVertical>\n' \
       f'            <optionNameHorizontal>{optionNameHorizontal2}</optionNameHorizontal>\n' \
        '            <isBackorderAvailable>false</isBackorderAvailable>\n' \
       f'            <normalDeliveryDateId>{normalDeliveryDateId}</normalDeliveryDateId>\n' \
       f'            <backorderDeliveryDateId>{backorderDeliveryDateId}</backorderDeliveryDateId>\n' \
        '            <isBackorder>true</isBackorder>\n' \
        '            <isRestoreInventoryFlag>true</isRestoreInventoryFlag>\n' \
        '          </inventory>\n' \
        '        </inventories>\n' \
       f'        <verticalName>{verticalName}</verticalName>\n' \
       f'        <horizontalName>{horizontalName}</horizontalName>\n' \
        '      </itemInventory>\n' \
        '    </item>\n' \
        '  </itemUpdateRequest>\n' \
        '</request>\n'

    return request

request = gen_update_request(
    itemUrl = itemUrl,
    itemName = itemName,
    itemPrice = <item price>,
    descriptionForPC = descriptionForPC,
    descriptionForSmartPhone = descriptionForSmartPhone,
    catchCopyForPC = catchCopyForPC,
    catchCopyForMobile = catchCopyForMobile,
    descriptionBySalesMethod = 'descriptionBySalesMethod',
    categoryId = 100,
)

get_response = requests.post(
    'https://api.rms.rakuten.co.jp/es/1.0/item/update',
    data = request.encode('utf-8'),
    headers = headers,
)
キーワード: RMS

トラックバック

このエントリーのトラックバックURL:
https://www.kanadas.com/mt/mt-tb.cgi/7560

コメントを投稿

Google でブログを検索:

メインページアーカイブページも見てください.
Creative Commons License
このブログはつぎのライセンスで保護されています. クリエイティブ・コモンズ・ライセンス.
Powered by Movable Type