アソシエイトのツールバーで作成した画像+テキストリンクをPA-APIを使い変換する

2024年1月5日金曜日

Blog Blogger PA-API アフェリエイト アマゾンアソシエイト

t f B! P L
title-image

11月にアマゾンからアソシエイトツールバーで作成した「画像リンク」、「テキストと画像リンク」が廃止されるとのメールを受け、とりあえずスクラッチパッドでASINを入れてHTMLを取得する方法を試してみました。

しかしさすがにこれまで作成したリンク一つ一つからASINを抜き出しスクラッチパッドに入れるのは骨がおれるため、PythonのSDKを使い既存のリンクからASINを抜き出し、PA-APIから画像とURLを取得してHTMLを吐き出すまでのスクリプトを書いてみました。

PA-API(商品情報API)とは

PA-API(商品情報API)とは、アマゾンに掲載されている商品の名称、URL、画像、価格など様々な情報を取得できるAPIです。使用するには認証キーの取得が必要なこと、アマゾンアソシエイトで一定の売上をあげている必要があります。認証キーの取得、スクラッチパッドの基本的な使い方は前回の記事をご参考ください。

動作環境とPythonのライブラリ

Linux(Ubuntu)にPython3.11.6です。WindowsでもMacOSでもおそらくそのままかほぼそのまま動作するのではと思います。

HTMLの解析のためにBeautifulSoupを使っているため、BeautifulSoupが必要です。その他はPA-APIのSDKと標準ライブラリのみ使っています。

$ pip install beautifulsoup4

SDKのダウンロード

PA-API開発者ガイドのページからダウンロードします。PHP、Java、Node.js、PythonのSDKが公開されています。今回はPyhonのSDKをダウンロードしました。SDKにはサンプルコードも含まれています。

以下、APIのバージョンは2024年1月4日時点の5.0を前提に解説していきます。

必要な情報

認証キー(アクセスキー、シークレットキー)、アソシエイトIDが必要です。また、ASINをもとに商品情報を取得しますが、これは以前にアソシエイトツールバーで作成したHTMLタグをクリップボードにコピーして、クリップボードからASINを読み取るようにします。

サンプルコードを確認

ダウンロードしたpaapi5-python-sdk-example.zipを解凍してみます。__MACOSXと、paapi5-python-sdk-exampleという2つのディレクトリができました。Linuxなので__MACOSXは無視するとして、paapi5-python-sdk-exampleの中にはいろいろ入っていますが、今回直接使うのは、sample_get_items_api.pyです。

.
├── __MACOSX
│   └── paapi5-python-sdk-example
├── paapi5-python-sdk-example
│   ├── COPYING.txt
│   ├── LICENSE.txt
│   ├── NOTICE.txt
│   ├── README.md
│   ├── paapi5_python_sdk/
│   ├── requirements.txt
│   ├── sample_get_browse_nodes_api.py
│   ├── sample_get_items_api.py ←これを使う
│   ├── sample_get_variations_api.py
│   ├── sample_request_with_conn_pool_settings.py
│   ├── sample_search_items_api.py
│   ├── setup.cfg
│   ├── setup.py
│   ├── test/
│   ├── test-requirements.txt
│   └── tox.ini
└── paapi5-python-sdk-example.zip

sample_get_items_api.pyの中のget_items()関数を使えば商品情報を取得できそうです。サンプルコードは、少し修正しました。

def get_items(akey, skey, ptag, asin):  #① 引数追加。オリジナルは引数なし get_items()
    """ Following are your credentials """
    """ Please add your access key here """
    access_key = akey #①

    """ Please add your secret key here """
    secret_key = skey #①

    """ Please add your partner tag (store/tracking id) here """
    partner_tag = ptag #①

    """ PAAPI host and region to which you want to send request """
    """ For more details refer: https://webservices.amazon.com/paapi5/documentation/common-request-parameters.html#host-and-region"""
    host = "webservices.amazon.co.jp" #② webservices.amazon.comから日本(co.jp)に変更
    region = "us-wast-2" #③us-east-1から変更

    """ API declaration """
    default_api = DefaultApi(
        access_key=access_key, secret_key=secret_key, host=host, region=region
    )

    """ Request initialization"""

    """ Choose item id(s) """
    item_ids = [asin] #①
    
    ########    省略    ########
    
    """ Forming request """

    try:
        get_items_request = GetItemsRequest(
            partner_tag=partner_tag,
            partner_type=PartnerType.ASSOCIATES,
            marketplace="www.amazon.co.jp", #② webservices.amazon.comから日本(co.jp)に変更
            condition=Condition.NEW,
            item_ids=item_ids,
            resources=get_items_resource,
        )

    ########    省略    ########

    return response.items_result    except Exception as exception:
            print("Exception :", exception, file=sys.stderr)
    #                                       ~~~~~~~~~~~~~~~ ④printが多くうるさいのでstderrに出力
    
    return response.items_result # ⑤戻り値を追加

########    ファイル最後の関数呼び出しはコメントアウト    ########
# get_items()
# get_items_with_http_info()
# get_items_async()
    
    
  1. 関数の中にアクセスキーなどの認証情報を直接書き込むようになっているため、引数で渡すように変更しました。
  2. 問い合わせ先がアメリカのアマゾン(amazon.com)になっていたのでamazon.co.jpに変更。
  3. reagionも変更必要です。日本はなぜかus-wast-2になるようです。
  4. 途中、printで標準出力に色々書き出すようになっていましたが、最終的に整形したHTMLのみ出力したいので力技ですが、すべてのprint文に標準エラー出力に書き出すように変更。(sysモジュールのインポート必要です)
  5. 取得した情報はresponse.items_resultに入っていそうです。別の関数から戻り値を使いたいのでreturn文を最後に追加しました。

既存のリンクをクリップボードから読み、HTMLを出力するスクリプト

既存のリンクは、iframeタグのsrc属性の中にasins=**********という形でASINが埋め込まれています。

<iframe frameborder="0" height="240" marginheight="0" marginwidth="0" sandbox="allow-popups allow-scripts allow-modals allow-forms allow-same-origin" scrolling="no"
  src="//rcm-fe.amazon-adsystem.com/e/cm?lt1=_blank&amp;bc1=000000&amp;IS2=1&amp;bg1=FFFFFF&amp;fc1=000000&amp;lc1=0000FF&amp;t=アソシエイトID&amp;language=ja_JP&amp;o=9&amp;p=8&amp;l=as4&amp;m=amazon&amp;f=ifr&amp;ref=as_ss_li_til&amp;asins=B005RFSIUW&amp;linkId=f741756446a26bffc9ce95581b04542f" style="width:120px;height:240px;" title="アマゾン 絶縁ナットドライバー" width="120"></iframe>

標準入力からiframeタグを読み込み、ASINを取り出し、先程のget_itemsで商品情報を取得、商品情報、URL、画像のURLをHTMLテンプレートに流し込み標準出力に出す感じです。iframeタグを複数続けて設置していることもあるため、複数タグを選択しても動くようにしました。

iframeタグ無しでも、ASINを指定して同じHTMLを出せるようにコマンドラインオプション無しだと標準入力から読み込み、-a --asinオプションでASINを指定するオプションも付けました。

import get_items_api as paap
from bs4 import BeautifulSoup
import re
import json
import sys
import argparse

### Amazon associates credidentials
access_key = "アクセスキー"
secret_key = "シークレットキー"
partner_tag = "アソシエイトID"

### 雛形
# {0} アソシエイトID
# {1} URL
# {2} タイトル
# {3} イメージURL
template = """\
<div class="paapi5-pa-ad-unit"><div class="paapi5-pa-product-container"><div class="paapi5-pa-product-image"><div class="paapi5-pa-product-image-wrapper"><a class="paapi5-pa-product-image-link" href="{1}" title="{2}" target="_blank"><img class="paapi5-pa-product-image-source" src="{3}" alt="{2}" width={4} height={5}></a></div></div><div class="paapi5-pa-product-details"><div class="paapi5-pa-product-title"><a class="paap5-pa-product-title-link" href="{1}" title="{2}" target="_blank">{2}</a></div><div class="paapi5-pa-product-list-price"><span class="paapi5-pa-product-list-price-value"></span></div><div class="paapi5-pa-product-prime-icon"><span class="icon-prime-all"></span><a class="paap5-pa-product-title-link" href="{1}" title="{2}" target="_blank"><span class="buy-on-amazon">Amazonで買う</span></a></div></div></div></div>\
"""

### 認証情報とASINを受取り、HTMLを返す関数
def create_html(access_key, secret_key, partner_tag, asin, template):
    response = paap.get_items(access_key, secret_key, partner_tag, asin)
    url = response.items[0].detail_page_url
    title = response.items[0].item_info.title.display_value
    img = response.items[0].images.primary.medium
    print(template.format(partner_tag, url, title, img.url, img.width, img.height))

### コマンドラインオプション
parser = argparse.ArgumentParser()
parser.add_argument("-a", "--asin", help="与えられたASINに対するHTMLを返す", action="store")
args = parser.parse_args()
if args.asin:
    asin = args.asin
    print("found asin: {0}".format(asin), file=sys.stderr)
    create_html(access_key, secret_key, partner_tag, asin, template)
else:
    soup = BeautifulSoup(sys.stdin.read(), "lxml")
    asin_tags = soup.find_all("iframe", src=re.compile("asin"))
    for asin_tag in asin_tags:
        m = re.search(r"asins=(\w+)", str(asin_tag["src"]))
        asin = m.group(1)
        print("found asin: {0}".format(asin), file=sys.stderr)
        create_html(access_key, secret_key, partner_tag, asin, template)

プログラマーでは無いので拙いコードですが、とりあえず即席でなんとか動かすことができました。書いてみてBeautifulSoupを使うほどでもなく、単なる文字列置換でよかった気もします。また、sample_get_items_api.pyの中のget_items()関数は途中でいろいろprintするのでこのあたりは削除して書き直したほうがよかったかもしれません。

使い方

既存のテキストと画像タグの置き換え

iframeタグ全体を(あっても可)選択し、クリップボードにコピーします。

xselでクリップボードの内容をパイプでこのスクリプトに渡します。標準エラー出力は表示しないようにして、端末の標準出力に出てきたHTMLタグをコピーしてブログに貼り付ける使い方です。

$ xsel -bo | python3 create_link.py 2>/dev/null 

ASINからHTMLタグを生成

$ python3 create_link.py -a ASIN番号 2>/dev/null

出力されたHTML

スクラッチパッドのHTMLを参考にしました。やたらとdivタグにラップされていていちいち長いクラス名が付いていてもっとスッキリさせたいですが、同じくスクラッチパッドで出力されたCSSもほぼそのままで使いたいので、面倒なのでスクラッチパッドとほぼ同じにしています。

<div class="paapi5-pa-ad-unit"><div class="paapi5-pa-product-container"><div class="paapi5-pa-product-image"><div class="paapi5-pa-product-image-wrapper"><a class="paapi5-pa-product-image-link" href="https://www.amazon.co.jp/dp/B005RFSIUW?tag=アソシエイトID&linkCode=ogi&th=1&psc=1" title="クニペックス KNIPEX 9803-10 絶縁ナットドライバー 1000V" target="_blank"><img class="paapi5-pa-product-image-source" src="https://m.media-amazon.com/images/I/31QfMSIO87L._SL160_.jpg" alt="クニペックス KNIPEX 9803-10 絶縁ナットドライバー 1000V" width=160 height=160></a></div></div><div class="paapi5-pa-product-details"><div class="paapi5-pa-product-title"><a class="paap5-pa-product-title-link" href="https://www.amazon.co.jp/dp/B005RFSIUW?tag=アソシエイトID&linkCode=ogi&th=1&psc=1" title="クニペックス KNIPEX 9803-10 絶縁ナットドライバー 1000V" target="_blank">クニペックス KNIPEX 9803-10 絶縁ナットドライバー 1000V</a></div><div class="paapi5-pa-product-list-price"><span class="paapi5-pa-product-list-price-value"></span></div><div class="paapi5-pa-product-prime-icon"><span class="icon-prime-all"></span><a class="paap5-pa-product-title-link" href="https://www.amazon.co.jp/dp/B005RFSIUW?tag=アソシエイトID&linkCode=ogi&th=1&psc=1" title="クニペックス KNIPEX 9803-10 絶縁ナットドライバー 1000V" target="_blank"><span class="buy-on-amazon">Amazonで買う</span></a></div></div></div></div>

CSSもスクラッチパッドほぼそのままです。

.paapi5-pa-ad-unit {
    width: 232px;
    height: auto;
    border: 1px solid #eee;
    margin:2px;
    position: relative;
    overflow: hidden;
    padding: 22px 20px;
    line-height: 1.1em;
    margin: auto;
}
.paapi5-pa-ad-unit * {
    box-sizing: content-box;
    box-shadow: none;
    font-family: Arial, Helvetica, sans-serif;
    margin: 0;
    outline: 0;
    padding: 0;
}
.paapi5-pa-ad-unit a {
    box-shadow: none !important;
}
.paapi5-pa-ad-unit a:hover {
    color: #c45500;
}
.paapi5-pa-product-container {
    width: 180px;
    height: 210px;
}
/* fixed width and height of product image to 150px */
.paapi5-pa-product-image {
    display: table;
    width: 150px;
    height: 150px;
    margin: 0 auto;
    text-align: center;
}
.paapi5-pa-product-image-wrapper {
    display: table-cell;
    vertical-align: middle;
}
.paapi5-pa-product-image-link {
    position: relative;
    display: inline-block;
    vertical-align: middle;
}
.paapi5-pa-product-image-source {
    max-width: 150px;
    max-height: 150px;
    vertical-align: bottom;
}
.paapi5-pa-percent-off {
    display: block;
    width: 32px;
    height: 25px;
    padding-top: 8px;
    position: absolute;
    top: -16px;
    right: -16px;
    color: #ffffff;
    font-size: 12px;
    text-align: center;
    -webkit-border-radius: 50%;
    -moz-border-radius: 50%;
    -ms-border-radius: 50%;
    border-radius: 50%;
    background-color: #a50200;
    background-image: -webkit-linear-gradient(top, #cb0400, #a50200);
    background-image: linear-gradient(to bottom, #cb0400, #a50200);
}
.paapi5-pa-ad-unit.hide-percent-off-badge .paapi5-pa-percent-off {
    display: none;
}
.paapi5-pa-product-details {
    display: inline-block;
    max-width: 100%;
    margin-top: 11px;
    text-align: center;
    width: 100%;
}
.paapi5-pa-ad-unit .paapi5-pa-product-title a {
    display: block;
    width: 100%;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    font-size: 13px;
    color: #0066c0;
    text-decoration: none;
    margin-bottom: 3px;
}
.paapi5-pa-ad-unit .paapi5-pa-product-title a:hover {
    text-decoration: underline;
    color: #c45500;
}
.paapi5-pa-ad-unit.no-truncate .paapi5-pa-product-title a {
    text-overflow: initial;
    white-space: initial;
}
.paapi5-pa-product-offer-price {
    font-size: 13px;
    color: #111111;
}
.paapi5-pa-product-offer-price-value {
    color: #AB1700;
    font-weight: bold;
    font-size: 1.1em;
    margin-right: 3px;
}
.paapi5-pa-product-list-price {
    font-size: 13px;
    color: #565656;
}
.paapi5-pa-product-list-price-value {
    text-decoration: line-through;
    font-size: 0.99em;
}
.paapi5-pa-product-prime-icon .icon-prime-all {
    background: url("https://images-na.ssl-images-amazon.com/images/G/01/AUIClients/AmazonUIBaseCSS-sprite_2x_weblab_AUI_100106_T1-4e9f4ae74b1b576e5f55de370aae7aedaedf390d._V2_.png") no-repeat;
    display: inline-block;
    margin-top: -1px;
    vertical-align: middle;
    background-position: -192px -911px;
    background-size: 560px 938px;
    width: 52px;
    height: 15px;
}
.paapi5-pa-product-offer-price,
.paapi5-pa-product-list-price,
.paapi5-pa-product-prime-icon {
    display: inline-block;
    margin-right: 3px;
}
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
    .paapi5-pa-ad-unit .paapi5-pa-product-prime-icon .icon-prime-all {
        background: url("https://images-na.ssl-images-amazon.com/images/G/01/AUIClients/AmazonUIBaseCSS-sprite_2x_weblab_AUI_100106_T1-4e9f4ae74b1b576e5f55de370aae7aedaedf390d._V2_.png") no-repeat;
        display: inline-block;
        margin-top: -1px;
        vertical-align: middle;
        background-position: -192px -911px;
        background-size: 560px 938px;
        width: 52px;
        height: 15px;
    }
}
/* css for mobile devices when device width is less than 441px*/
@media  screen and (max-width: 440px) {
    .paapi5-pa-ad-unit {
        float: none;
        width: 100%;
    }
    .paapi5-pa-product-container {
        margin: 0 auto;
        width: 100%;
    }
    .paapi5-pa-product-details {
        text-align: center;
        margin-top: 11px;
    }
}
.paapi5-pa-product-prime-icon .buy-on-amazon {
  border-radius: 5px;
  background: #232F3E;
  color: white;
  font-size: 13px;
  border: none;
  padding: 3px;
  font-weight: bold;
}

作成したリンク

こんな感じです。

Bloggerでは手動埋め込みしかできなさそう

さて、ここまでできたらレンタルサーバーを使っている場合などはアクセスの度にPA-APIを使いリンクを生成すればよいのですが、Bloggerでは無理なので上のリンクをコピペ手動で貼り付け、結局力技で更新していきました。

アマゾンアソシエイトPA-APIの規約を読むと、結果をキャッシュする場合、24時間以内にAPIから情報を更新すること、価格情報は1時間以内に更新すること、と書かれているためこれを永久的に貼り付けておくのは微妙です。

特に価格情報は頻繁に変わるので作成したリンクには載せないようにしました。商品へのURLは、アソシエイトツールバーのテキストリンクと同じものが帰ってきてるようで、アソシエイトツールバーのテキストリンクはコピペで更新しなくても良いため問題無いのでは、と思っています。

画像は微妙ですが、リンク先のURLを見ると頻繁に更新されるようなものでは無さそうなので画像も入れて様子見してみます。

Javascriptのfetchでクライアント側から取得すればよいのでは!?と思いやってみましたが、同一オリジンポリシーとかいうのがあってダメみたいです。

まとめ

SDKを使うことで動かなくなったアマゾンアソシエイトツールバーで作成した「テキストと画像リンク」をPA-APIで取得したリンクに変換することができました。

Bloggerでは、結局1個1個手動でタグを置き換える必要がありますが、SDKを使ったスクリプトを書くことで作業自体はだいぶ楽になりました。

2024/1/20追記:Netlify functions (FaaS) を使い、動的に商品情報を取得する方法にも挑戦してみました。詳細は、下の関連記事を参照ください。

関連記事

Blog Archive

QooQ