Amazon国別サイト横断検索のために - Apache Solr (その2)

前回で Solr が使用可能になったので、今回は検索で取得したデータを登録してみる。

対象データ

以下の条件で取得したデータ(2013-08-15時点で25件)を利用する。日本語も入れとかないとなあ、ということで Artist=カステラ としてみた。

http://ecs.amazonaws.jp/onca/xml
?Service=AWSECommerceService
&Version=2011-08-01
&Operation=ItemSearch
&AssociateTag=kahnn-22
&SearchIndex=Music
&Artist=カステラ
&Sort=titlerank
&Availability=Available
&ResponseGroup=ItemAttributes,Tracks
&ItemPage=1

スキーマ設計

Solrの方は、example ディレクトリを利用し、example/solr/collection1/conf/schema.xml を変更したもので試す。
まずは商品データにあったスキーマ設計が必要となる。最初から全部の要素を扱うのは大変なので、幾つか特徴的な要素を選んでみたのが以下の表になる。

Amazon商品データスキーマ
項目 フィールド名 indexed stored その他
ID(post時にASINの値をそのまま利用) id long true true required="true", uniqueKey
VERSION _version_ long true true
Timestamp timestamp date true true default="NOW"
ASIN asin string true true required="true"
EAN ean string true true
DetailPageURL(商品詳細ページのURL) detail_url string false true
Title title text_ja true true
Artist artist text_ja true true multiValued="true"
Label label text_ja true true
ReleaseDate release_date date true true
ListPrice/FormattedPrice(国別に整形された値段表示) formatted_price string false true
ListPrice/Amount(値段の数字(単位無視)) price long true true

この設計に従って schema.xml を以下のように記述した。fieldType についてはサンプルの通りで、比較用に text_cjk type の CopyField である text を用意している以外は、ほとんど上記設計のままとなっている。

<?xml version="1.0" encoding="UTF-8" ?>
<schema name="amazon_product_sample" version="1.5">

 <types>
  <fieldType name="string" class="solr.StrField"
             sortMissingLast="true" />
  <fieldType name="long" class="solr.TrieLongField"
             precisionStep="0" positionIncrementGap="0"/>
  <fieldType name="date" class="solr.TrieDateField"
             precisionStep="0" positionIncrementGap="0"/>
  <fieldType name="text_ja" class="solr.TextField"
             positionIncrementGap="100" autoGeneratePhraseQueries="false">
    <analyzer>
      <tokenizer class="solr.JapaneseTokenizerFactory" mode="search"/>
      <filter class="solr.JapaneseBaseFormFilterFactory"/>
      <filter class="solr.JapanesePartOfSpeechStopFilterFactory" tags="lang/stoptags_ja.txt" />
      <filter class="solr.CJKWidthFilterFactory"/>
      <filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_ja.txt" />
      <filter class="solr.JapaneseKatakanaStemFilterFactory" minimumLength="4"/>
      <filter class="solr.LowerCaseFilterFactory"/>
    </analyzer>
  </fieldType>
  <fieldType name="text_cjk" class="solr.TextField" positionIncrementGap="100">
    <analyzer>
      <tokenizer class="solr.StandardTokenizerFactory"/>
      <filter class="solr.CJKWidthFilterFactory"/>
      <filter class="solr.LowerCaseFilterFactory"/>
      <filter class="solr.CJKBigramFilterFactory"/>
    </analyzer>
  </fieldType>

 </types>

 <fields>
   <field name="id" type="string" indexed="true" stored="true" required="true" />
   <field name="_version_" type="long" indexed="true" stored="true" />
   <field name="timestamp" type="date" indexed="true" stored="true" default="NOW" />
   <field name="asin" type="string" indexed="true" stored="true" required="true" />
   <field name="ean" type="string" indexed="true" stored="true" />
   <field name="detail_url" type="string" indexed="false" stored="true" />
   <field name="title" type="text_ja" indexed="true" stored="true" />
   <field name="artist" type="text_ja" indexed="true" stored="true" />
   <field name="label" type="text_ja" indexed="true" stored="true" />
   <field name="release_date" type="date" indexed="true" stored="true" />
   <field name="formatted_price" type="string" indexed="false" stored="true" />
   <field name="price" type="long" indexed="true" stored="true" />
   <field name="text" type="text_cjk" indexed="true" stored="false" multiValued="true" />
 </fields>

 <uniqueKey>id</uniqueKey>

 <copyField source="title" dest="text" />
 <copyField source="artist" dest="text" />
 <copyField source="label" dest="text" />

</schema>
余談1
Solr4.4の schema.xml を見てると多言語対応周りが強化されていて 3.6より前の情報と随分異なる。これからさわる人にとっては随分と楽になっているという訳で、先人の貢献に感謝。
余談2
複数の CopyField としてフィールド名 "text" を使用しているが、この "text" フィールドが無いと、起動時にエラーが出る。example/solr/collection1/conf/solrconfig.xml では、"text" フィールドが有ることを前提にして各種設定が書かれており、これらを修正するくらいならば、schema で用意した方が楽。(まあ、本格的に使用する場合は、ちゃんと吟味すべきでしょうけど)

データ加工と登録

次に商品データを登録する必要があるが、post.jar を 使って登録する場合、Solr で追加する際の型式に合わせてやる必要がある。今回は xslt で所定の xml 型式に変換する事にした。
完成イメージは以下のようになる。文法については UpdateXmlMessages を参照。

<add>
  <doc>
    <field name="id">XXXXXXXXX</field>
    <field name="asin">XXXXXXXXX</field>
    <field name="detail_url">http://......</field>
    <field name="ean">XXXXXXXXXX</field>
    <field name="title">世界の娯楽</field>
    <field name="artist">カステラ</field>
    <field name="label">SME</field>
    <field name="release_date">1991-11-12</field>
    <field name="formatted_price">¥1,000</field>
    <field name="price">1000</field>
  </doc>
</add>

そのための xslt スクリプト(amazon-to-solr.xsl)は以下の通り。

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
  xmlns:aws="http://webservices.amazon.com/AWSECommerceService/2011-08-01"
  version="1.0">
<xsl:output method="xml" encoding="UTF-8"/>

<!-- Root Node -->
<xsl:template match="/">
  <add>
    <xsl:apply-templates select="aws:ItemSearchResponse/aws:Items/aws:Item"/>
  </add>
</xsl:template>

<xsl:template match="aws:ItemSearchResponse/aws:Items/aws:Item">
  <doc>
    <field name="id"><xsl:value-of select="aws:ASIN" /></field>
    <field name="asin"><xsl:value-of select="aws:ASIN" /></field>
    <field name="detail_url"><xsl:value-of select="aws:DetailPageURL" /></field>
    <field name="ean"><xsl:value-of select="aws:ItemAttributes/aws:EAN" /></field>
    <field name="title"><xsl:value-of select="aws:ItemAttributes/aws:Title" /></field>
    <field name="artist"><xsl:value-of select="aws:ItemAttributes/aws:Artist" /></field>
    <field name="label"><xsl:value-of select="aws:ItemAttributes/aws:Label" /></field>
    <field name="release_date"><xsl:value-of select="aws:ItemAttributes/aws:ReleaseDate" />T00:00:00Z</field>
    <xsl:apply-templates select="aws:ItemAttributes/aws:ListPrice" />
  </doc>
</xsl:template>

<xsl:template match="aws:ItemAttributes/aws:ListPrice">
    <xsl:element name="field">
      <xsl:attribute name="name">formatted_price</xsl:attribute>
      <xsl:value-of select="aws:FormattedPrice" />
    </xsl:element>
    <xsl:element name="field">
      <xsl:attribute name="name">price</xsl:attribute>
      <xsl:value-of select="aws:Amount" />
    </xsl:element>
</xsl:template>

</xsl:stylesheet>

Macには xsltproc が入っているので、以下のようにして変換可能。なお、amazon-data-1.xmlamazonから取得した商品xmlデータ。

$ xsltproc amazon-to-solr.xsl amazon-data-1.xml > solr-data-1.xml

後は post すればよい。

$ cd example/exampledocs/
$ java -jar post.jar ..../solr-data-*.xml

collection1 の Overviewで見ると、ドキュメントが25件登録された。これで準備OK。

フィールドの設定と解析状況については、Analysisから色々とキーワードを入力して試すことが出来る。text_cjk, text_ja の違いなどもわかりやすい。
実際の検索結果については、Queryから検索キーを入力して試すことが出来る。

検索による商品の確認

例えば、'artist:カステラ AND title:新世界' で検索したとする。なお、scoreとリリース日付でソートして、json型式で取得する。

$ curl 'http://localhost:8983/solr/collection1/select/' -d 'indent=on' \
       -d 'fl=score,asin,title,artist,price,label,release_date' -d 'wt=json' \
       -d 'sort=score+desc,release_date+desc' --data-urlencode 'q=artist:カステラ AND title:新世界'

{
  "responseHeader":{
    "status":0,
    "QTime":29,
    "params":{
      "sort":"score desc,release_date desc",
      "fl":"score,asin,title,artist,price,label,release_date",
      "indent":"on",
      "q":"artist:カステラ AND title:新世界",
      "wt":"json"}},
  "response":{"numFound":4,"start":0,"maxScore":2.792567,"docs":[
      {
        "asin":"B00005G4UJ",
        "title":"新世界",
        "artist":"カステラ",
        "label":"エピックレコードジャパン",
        "release_date":"1998-03-21T00:00:00Z",
        "price":2039,
        "score":2.792567},
      {
        "asin":"B000064QIH",
        "title":"新世界",
        "artist":"カステラ",
        "label":"ソニーレコード",
        "release_date":"1993-11-21T00:00:00Z",
        "price":2854,
        "score":2.792567},
      {
        "asin":"B00005G4UF",
        "title":"世界の娯楽",
        "artist":"カステラ",
        "label":"エピックレコードジャパン",
        "release_date":"1998-03-21T00:00:00Z",
        "price":2039,
        "score":1.0660849},
      {
        "asin":"B000UUPTKG",
        "title":"世界の娯楽",
        "artist":"カステラ",
        "label":"株式会社ソニー・ミュージックレコーズ",
        "release_date":"1989-09-01T00:00:00Z",
        "score":1.0660849}]
  }}

これで4つヒットしたわけだけど、タイトルとしては2つ。ここまで取得できれば、title と artist でマージするのは簡単だろう。その上で、「新世界」については asin=B00005G4UJ の 2039円 の方をお薦めするとかができそうである。同様に、「世界の娯楽」については price が設定されている*1 asin= B00005G4UF を薦めれば良い。
他にも色々と試してみたが、Amazonからの検索データをそのまま使うのではなく、キャッシュも兼ねた Solr に一度格納してから最適化するというのは結構使えそうに感じた。

参考

Solrはドキュメントも結構そろっているのだけど、やりたいことに辿り着くのが意外と難しくて、最初は慣れないこともあって苦労した。この書籍は対象が1.4と古いものの、概要や基本的な考え方を抑えるのに便利だと思う。正直、最初にこれを読んでから取り組めば良かった。(買って読んだのは設定をいじりだしてから)

4.4 では色々と変更も多いので、新版がでると良いんですけどね。

*1:設定があるかどうかだけなら、検索条件に "AND +price:*" を追加しても良い

Amazon国別サイト横断検索のために - Apache Solr (その1)

前回Amazon Product Advertising API については一通りさわったわけだけど、取得したデータのキャッシュと、キーワードの翻訳、類似商品のマージ等をやっていく必要があることもわかった。
この辺りをできるだけ楽に、ということで、キャッシュと商品検索補助のために Apache Solr を調査してみることにした。

セットアップ&動作確認

まずは、Apache Solrのインストールを実施する。
Downloadリンクから辿って、アーカイブファイルをダウンロード、展開すればOK。
2013-08-17時点での最新版は 4.4 だったので、それをそのまま利用する。

まずは、チュートリアル(docs/tutorial.html)に沿って、動作を確認する。

exampleを起動:

$ cd solr-4.4.0/example/
$ java -jar start.jar 
0    [main] INFO  org.eclipse.jetty.server.Server  ? jetty-8.1.10.v20130312
....

ブラウザからアクセスして起動を確認:

http://localhost:8983/solr/

一通りメニューから確認したところ、問題は無さそう。

サンプルデータの利用と確認

次に、サンプルデータを登録する。

$ cd exampledocs/
$ java -jar post.jar *.xml
SimplePostTool version 1.5
Posting files to base url http://localhost:8983/solr/update using content-type application/xml..
POSTing file gb18030-example.xml
POSTing file hd.xml
POSTing file ipod_other.xml
POSTing file ipod_video.xml
POSTing file manufacturers.xml
POSTing file mem.xml
POSTing file money.xml
POSTing file monitor.xml
POSTing file monitor2.xml
POSTing file mp500.xml
POSTing file sd500.xml
POSTing file solr.xml
POSTing file utf8-example.xml
POSTing file vidcard.xml
14 files indexed.
COMMITting Solr index changes to http://localhost:8983/solr/update..
Time spent: 0:00:00.528

管理画面から、"collection1" を選択して出てくる "Query"タブを選択して、"q"テキストボックスに"solr"を入力して検索すると、結果が1件ヒットする。
"wt"で取得結果のフォーマットを選べて、json が見やすいが、xml の方が細かな情報を拾ってくれるので見比べると面白い。

動作確認は直接URLを指定した方が早い。チュートリアルを見ると、「コア名/select」に続けて "q"パラメータに検索式を、"wt"に結果のフォーマット(xml等)を指定する。

http://localhost:8983/solr/collection1/select?q=solr&wt=xml

検索式の基本ルール

検索式の指定方法の基本は以下の通り。

   [対象フィールド名:]キーワード

フィールドが省略された場合は、検索可能なフィールド全てを対象に検索を行う模様。以前は、defaultSearchField で対象を指定していたが、今は廃止されたらしい。
キーワードには範囲指定が可能で、例えば「400まで」は "[* TO 400]" になる。
複数の検索式を指定する場合は、AND/OR/NOTで論理式を組み立てられる。指定しない場合は OR 指定と同義。また、"+"をキーワードの前に付ける事で必須であることを指定可能。例えば、Aは存在する事が必須(must)で、Bは存在しなくても良い(may)、という検索式は以下のようになる、

  +A B

細かなところは、SolrQuerySyntaxを参照して辿っていけばよい。(Query Parser Syntaxが基本)

後は、"fl"パラメータにフィールド名を指定する事で、取得するフィールドを選択したり、"sort"パラメータで並べ替えを行ったり出来る。
他にも色々とあるが、まあ、こんなところで。

次は Amazonから取得したデータに合わせてスキーマを定義し、データを登録してみる。

Amazon国別サイト横断検索のために - Amazon Product Advertising API (その3)

前回Amazon Product Advertising API のリクエストパラメータが明確になったので、色々と試してみた。やってみてわかるところもあったので検討課題も含めて記録しておく。

1.検索結果のチューニング

音楽(Music)をターゲットにして、ItemSearchで検索してみる。
Artistに「janis joplin」を指定する。
結果の制御としては、取得ページ数(3)を指定、ソート条件としてtitleの昇順、取得内容として、「ItemAttributes,Tracks」を指定してみた。

パラメータをまとめると、以下のようになる。

http://ecs.amazonaws.jp/onca/xml
?Service=AWSECommerceService
&Version=2011-08-01
&Operation=ItemSearch
&AssociateTag=kahnn-22
&SearchIndex=Music
&Artist=janis+joplin
&Sort=titlerank
&ItemPage=3
&ResponseGroup=ItemAttributes,Tracks

これにより、ItemSearchResponse から Request 等が無くなる代わりに Item情報が詳細(Track付)になった。また、ItemPageを変更することで最大10ページ分(タイトル昇順で先頭から100件)までの商品情報が取得可能になる。ちなみに11ページ目を指定すると、エラーコードとしてAWS.ParameterOutOfRangeが、メッセージとして「ItemPage として指定した値は無効です。有効な値は、1から10までのものです。」が返される。結構親切。

ざっと見たところ、結果の中で重要となりそうなのは、以下の要素と思われる。(商品カテゴリによって変わるはず)
詳細は、Response Elements 参照。

Item/Items主要要素
要素 説明
Items/TotalResults 検索結果件数 206
Items/TotalPages 検索結果ページ数(1ページ10件で計算) 21
Items/Item/ASIN Amazon Standard Identification Number」の略で、Amazonグループが取り扱う、書籍以外の商品を識別する10けたの英数字番号。 B0000025OG
Items/Item/ItemAttributes/Artist アーティスト Janis Joplin
Items/Item/ItemAttributes/Binding 商品のカテゴリを表す CD
Items/Item/ItemAttributes/Format フォーマット(ここでは輸入品かどうか) Import
Items/Item/ItemAttributes/Label レーベル SONY
Items/Item/ItemAttributes/ListPrice このタグの中に価格が入っている。CurrencyCode で通貨種別、Amountで値段を表す。表示だけならFormattedPriceで表示用の値段が取得できる。
Items/Item/ItemAttributes/Manufacturer メーカー SONY
Items/Item/ItemAttributes/Publisher 出版元 SONY
Items/Item/ItemAttributes/ReleaseDate 販売開始日 1990-10-25
Items/Item/ItemAttributes/Title 商品タイトル Farewell Song
Items/Item/ItemAttributes/EAN European Article Number, which is a number that uniquely identifies an item.(JANに同じ。基本は13ケタだが8ケタもあり) 0074643756921
Items/Item/ItemAttributes/UPC Universal Product Code, which is a 12 digit number, 6 of which represents an item's manufacturer. These numbers are translated into a bar code that is printed on an item or its packaging.(こちらは主にアメリカ、カナダ) 074643756921
Items/Item/ItemAttributes/SKU Stock Keeping Unit (SKU) is a number that uniquely identifies an item.(在庫管理用IDだが、UPCと同じになっていたり、そうでなかったり) 074643756921

2.商品とASINの割り当て

「同じ商品(CD)」が、ASIN違いで複数返ってくることがある。どの国の版か、再販か、販売元はどこか、といった違いによって同じ内容のCDであっても、別物として返されるのが注意点。より良い品質のためには、TitleやArtistを使ってまとめる必要がありそう。

ASINの割り当てルールは明確ではないけど、Amazon Standard Identification Numberを見ると、同じ商品に対するASINは複数ありうるけど、あるASINが指し示す商品は World Wide にユニークらしい。
実際に「どうぶつの森(ASIN=B0002FQD8G)」で試してみると、以下のように全て同じ商品(日本語版)を指している事がわかる。各国のローカル版は別商品(別ASIN)となっている。

ASINの特定までいけば商品比較は簡単だけど、実際の使い勝手を考えると、やはり、それ以外の条件でまとめられるようにすべきかな。

3.書籍検索(ISBNの取り扱い)

書籍を検索してみる。ソート条件として発行日が新しい順にする。

パラメータをまとめると、以下のようになる。

http://ecs.amazonaws.jp/onca/xml
?Service=AWSECommerceService
&Version=2011-08-01
&Operation=ItemSearch
&AssociateTag=kahnn-22
&SearchIndex=Books
&Author=江戸川乱歩
&Title=人間椅子
&Sort=daterank
&ItemPage=1
&ResponseGroup=ItemAttributes

検索結果を見ると、以下の特徴があった。Musicとはまた違う要素がある。ProductGroupを見て細かくハンドリングすべきだと思われる。

  • 検索結果には、紙の書籍だけでなくオーディオブック(ASIN=B003YBG1XW)やKindle版(ASIN=4394301076)も含まれる。そもそも短編集の中の一編とかはどう扱うべきか、という問題もありそう。
  • 紙の書籍には ISBN(10ケタ)が含まれる。ASINには ISBNと同じコード(4041053285)が入っており、要素自体が無くなることはなさそう。
  • ただし、紙の書籍であってもFormatが"古書"の場合は ISBN は存在せず、ASINだけがふられている。
  • 上記のパラメータ指定だと、中古品は出てこない。在庫が無い場合は、ListPrice 要素が出てこない。

4.電化製品検索(色違い)

色違いなどのバリエーションがある商品を検索してみる。
Title にコンパクトデジカメ「PENTAX Optio LS465」を指定して、ソート条件として発売日が新しい順にする。

パラメータをまとめると、以下のようになる。

http://ecs.amazonaws.jp/onca/xml
?Service=AWSECommerceService
&Version=2011-08-01
&Operation=ItemSearch
&AssociateTag=kahnn-22
&SearchIndex=Electronics
&Title=PENTAX+Optio+LS465
&Sort=-releasedate
&ItemPage=1
&ResponseGroup=ItemAttributes

検索結果を見ると、以下の特徴があった。

  • ルビーピンクのモデルしか引っかからない。本来は3色あるはず。
  • カメラ本体以外にアクセサリ等が引っかかる。
  • Title に説明(メーカ名から色まで)が入っている。色違いは ASINだけでなく Title も異なることになりそう。
  • Model, MPN, PartNumber あたり(OPTIOLS465PK)が、メーカの製品コードと思われる。(これは、色が違えば異なる。黒の場合はOPTIOLS465BKとなる)

このままだと、色違いモデルが取れないので調べてみると、ItemLookupオペレーションで 、ItemId にParentASINを指定し、ResponseGroup に VariationMatrix を指定すると、バリエーションがとれるらしい。

パラメータは以下のようになる。ItemId=B00C1A5A6G は、取得した ParentASIN の値。

http://ecs.amazonaws.jp/onca/xml
?Service=AWSECommerceService
&Version=2011-08-01
&Operation=ItemLookup
&AssociateTag=kahnn-22
&ItemId=B00C1A5A6G
&IdType=ASIN
&ResponseGroup=Large,VariationMatrix

取得した結果には、Items/Item/Variations が追加され、バリエーション数(TotalVariations)、Color等のバリエーション種別(VariationDimension)、バリエーション数分の商品(Items/Item/Variations/Item)が入っている。個々の商品のASINとVariationAttributeを見れば、色違いの製品個々をハンドリングすることができる。以下のようになる。

<ItemLookupResponse>
  <Items>
    <Item>
      <!-- ItemLookupで検索した結果の商品情報
           ParentASIN を指定した結果、ASIN とParentASIN に同じIDが入っている。
        -->
      <ASIN>B00C1A5A6G</ASIN>
      <ParentASIN>B00C1A5A6G</ParentASIN>
      .....
      <!-- ItemLookupで検索した結果のバリエーション情報 -->
      <Variations>
        <!-- バリエーションの数や属性 -->
        <TotalVariations>3</TotalVariations>
        <VariationDimensions>
          <VariationDimension>Color</VariationDimension>
        </VariationDimensions>
        <!-- バリエーション−1 (ParentASINが同じことに注意) -->
        <Item>
          <ASIN>B00855ZPEI</ASIN>
          <ParentASIN>B00C1A5A6G</ParentASIN>
          <ItemAttributes></ItemAttributes>
          <VariationAttributes>
            <VariationAttribute>
              <Name>Color</Name><Value>アメジストパープル</Value>
            </VariationAttribute>
          </VariationAttributes>
        </Item>
        <!-- バリエーション−2 (ParentASINが同じことに注意) -->
        <Item>
          <ASIN>B00855ZMFA</ASIN>
          <ParentASIN>B00C1A5A6G</ParentASIN>
          <ItemAttributes></ItemAttributes>
          <VariationAttributes>
            <VariationAttribute>
              <Name>Color</Name><Value>サファイヤブラック</Value>
            </VariationAttribute>
          </VariationAttributes>
        </Item>
        <!-- バリエーション−3 は省略 -->

      </Variations>
      .....
    </Item>
  </Items>
</ItemLookupResponse>

5.ゲームソフト(中古の取り扱いと価格)

中古品を含む情報を検索してみる。ただし、在庫があるものだけを取得する。ソート条件として安い商品からにする。ResponseGroup に OfferFull を追加しておく。

パラメータをまとめると、以下のようになる。

http://ecs.amazonaws.jp/onca/xml
?Service=AWSECommerceService
&Version=2011-08-01
&Operation=ItemSearch
&AssociateTag=kahnn-22
&SearchIndex=VideoGames
&Brand=任天堂
&Title=とびだせ+どうぶつの森
&Sort=price
&ItemPage=1
&Condition=All
&Availability=Available
&ResponseGroup=ItemAttributes,OfferFull

Titleだけ指定した場合の検索結果を見ると、攻略本や関連グッズ、同梱版も引っかかる。Brandまで指定すれば、だいぶ減る。
Offer は、新品と中古それぞれで最も安い価格の情報が取得できる。もし、Amazon出品の商品だけにしたい場合は、MerchantId=Amazon を指定する。その場合、OfferSummary は他の Merchant も含めて取得できるが、Offers では Amazon の出品物のみが返る。

6.ここまでのまとめ

ここまでで、アクセス方法や結果の取り扱いについては一通り抑えたはず。
仕様を割り切れば、各国版Amazon間の横断検索は割と簡単にできそう。手間仕事なだけ。どこまで使い勝手を上げるかで、作業量が大きく変わる。

ポイントとなるのは、API のアクセス数制限への対処と、結果の精度向上。取得したデータのキャッシュと、キーワードの翻訳、類似商品のマージ等をやっていく必要がある。
そうすると、類似検索を含むテキスト処理も必要になりそうなので、今度はそちらの方を確認してみる。

Amazon国別サイト横断検索のために - Amazon Product Advertising API (その2)

前回Amazon Product Advertising API が利用可能になったのだけれど、主に利用可能なリクエストパラメータについてまとめとく。

Amazon Web サービス入門(Product Advertising API)」が整理されていて良いのだけど、少し古い部分があるのと、自分で使いやすい形にまとめたいので、「Developer Guide (API Version 2011-08-01)」を元にして整理する。

基本パラメータ

オペレーションに関係なく指定するパラメータ群で、検索条件等のパラメータに依らず決まる。

基本パラメータ
項目 説明 参考URL
ベースURL パラメータでは無くてアクセス先URLのベース部分(endpoint)。URLによってAmazonの国別サイト(現在9か国)を切り替えられる 日本の場合:http://ecs.amazonaws.jp/onca/xml Anatomy of a REST Request
Service 利用サービス名を指定。基本的には固定 Service=AWSECommerceService Required Parameters
AssociateTag Amazonアソシエイト用のIDを指定 AssociateTag=XXXXX-22 Required Parameters
AWSAccessKeyId 利用者のAccess Key IDを指定 AWSAccessKeyId=XXXXXXXXXXXXXXXXXXXXX Required Parameters
Version APIバージョンを指定 Version=2011-08-01 General, Optional Parameters
Operation 実行する処理を指定する。主なものは (ItemLookup:ASINなどで商品を検索), (ItemSearch:商品名や著者名でキーワード検索), (SimilarityLookup:関連商品の検索) がある。 Operation=ItemSearch Operations

ここまでをまとめると以下のようになる。

http://ecs.amazonaws.jp/onca/xml
?Service=AWSECommerceService
&AssociateTag=XXXXX-22
&AWSAccessKeyId=XXXXXXXXXXXXXXXXXXXXX
&Version=2011-08-01
&Operation=ItemSearch

これ以外にも、Common Request Parametersを見ると、結果として取得するデータのコンテンツタイプを指定するためのContentType(Styleと併用か)等がある。

Operation=ItemSearch のパラメータ

キーワードなどを指定して商品検索を行う。今回はこれを主に使うことになるはず。使えるパラメータの説明は「ItemSearch」を見ればよいけど、主なものをまとめておく。

検索条件指定パラメータ

検索条件指定パラメータ
項目 説明 参考URL
SearchIndex (必須)検索対象のカテゴリを指定する。All, Books, Electronics, Software などが指定可能で、指定した値(カテゴリ)によって追加パラメータで使用可能なものが変化する。また、指定可能な内容がlocaleによっても変化するのでやっかい。 SearchIndex=Books
Search Index and ItemSearch Parameter Combinations
Search Index Support by Locale
Common ItemSearch Parameters
BrowseNode カテゴリ内の詳細なジャンルをブラウズノードIDで指定する。カテゴリトップレベルのIDからして国毎に異なる。BrowseNodeLookupオペレーションを利用することで、ブラウズノードIDの構成と値を取得可能だが、今回の目的には過剰か。 BrowseNode=465610 Browse Node IDs
Keywords 検索キーワードを url encode して指定する。キーワードの検索対象は商品のタイトルだけでは無く説明文やアーティスト名などが様々なものが検索対象となる。全てのSearchIndex指定で利用可能なので、これが基本パラメータとなる。 Keywords=harry+potter Search Index and ItemSearch Parameter Combinations
Title タイトルを対象にして検索する。Keywords と異なり、あくまで title だけが対象となる。 Title=harry+potter Search Index and ItemSearch Parameter Combinations
MerchantId 出品者を表すID。デフォルトでは全ての出品者の商品が対象となるが、「Amazon」を指定することでAmazon出品の商品のみが扱われる。 MerchantId=Amazon ItemSearch
Condition 対象商品の状態を表すパラメータ。デフォルトは"New"で新品のみ。中古品を入れたい場合は"All"を指定する。 Condition=All ItemSearch
Availability 在庫の存在する商品のみを対象とする場合に指定する。MerchantId や Conditionと組み合わせて使用する必要がある。(Condition=Newではダメ) Availability=Available
ItemSearch
Availability Parameter

他に色々と検索に使えそうなものがあり、例えば以下のとおり。(利用可能なカテゴリに注意する必要はある)

Manufacturer=任天堂
Brand=任天堂
Publisher=講談社
Author=諸星大二郎

また、Books でしか使えないが Power Search というパラメータ指定方法もある。

検索結果制御パラメータ

検索結果制御パラメータ
項目 説明 参考URL
ItemPage 検索結果のページ数を指定する。現在のAPI仕様では最大で10ページまでの指定しかできない様子。 ItemPage=ページ番号
Paging Through Results
Maximum Number Of Returned Pages
Sort 結果のソート条件を指定する。指定可能な値は商品カテゴリー毎に異なる。Books の場合、(daterank:発売日順(降順)), (titlerank:アルファベット昇順)など。locale毎に異なることに注意する。 Sort=pricerank
Sorting Results
ItemSearch Sort Values By Locale
ResponseGroup 取得対象情報を<グループ1>,...,<グループn>の形式で指定する。デフォルトは Small 指定と同じ。 ResponseGroup=Request,Images,Reviews Response Groups

結果の内容は必要なものに絞り込む方が良いわけで、ResponseGroupで取得対象を適切に指定する必要がありそう。主なものは以下の通り。

  • Request:リクエスト情報
  • ItemAttributes:商品の詳細情報
  • Images:商品の画像
  • Similarities:関連商品
  • BrowseNodes:ブラウズノード
  • Small:「ASIN」や「Title」の他に「Manufacturer」や「Role」など「ItemAttributes」を指定した時に取得できる情報の一部を取得。
  • Medium:「Small」に加えて画像や販売に関する情報を追加して取得できるように設定されたグループ。
  • Large:「Medium」に加えてレビューなど関連情報を取得できるように設定されたグループ。
  • OfferSummary -or- OfferFull:商品販売情報


あとは、色々と組み合わせで最適なものを選択することになる。相変わらず長くなったので、実際に触ってみての覚書は別に分けることにする。

Amazon国別サイト横断検索のために - Amazon Product Advertising API (その1)

各国のAmazonサイトを横断検索してみるサービスが幾つかあって、ちょっと調べただけでも「TAKEWARI」や「World Private Trading」とかが見つかる。
ただ、実際に使ってみるとこちらの意図通りに動いてくれるとは限らなくて、自分なりに使いやすいものを作ってみようかということで、必要そうな技術について調査してみることにした。
まずは、商品情報を検索する必要があるという事で、Product Advertising API の調査から。

Amazon Product Advertising API(リンク作成用API)について

以下に引用するが、要はプログラムから利用可能な Webサービスを提供してくれているという事。それでドンドンとAmazon経由で商品を売ってほしいと。

Product Advertising API は、Amazon の商品情報や関連コンテンツをプログラムを通してアクセスできるサービスを提供することで、Web 開発者の皆様が、ご自分の Web サイトでAmazon の商品を紹介することによる紹介料の獲得を可能とします。

内容と利用方法は、Product Advertising API から順番にドキュメントを読みながら確認していけば良いわけだけど、ちょっとわかりにくい。記録しておかないと忘れそう。

1.Product Advertising API アカウント作成

まずは amazon.com でアカウントを作成する必要がある。トップページから、「アカウント作成」をクリックして手続開始。

ステップ1. Amazon.com のアカウントでログイン

Amazon.com のアカウントを作成する必要があるけど、これはそもそも持っていたので問題なし。

ステップ2. アカウント情報の入力 (登録済みアカウント)

アカウント登録済みならば住所とかは大丈夫だが、以下は追加で入力が必要。

  • ライセンス契約への了承
  • Website or Application Description

サイト説明は適当に以下のような感じで入力。

 In my Web site, I will introduce a review for electrical products.
 And, we plan to introduce by using Product Advertising API.

これで、作成が成功するはず。Manage Your Account をクリックして確認する。

2.Getting Set Up with Product Advertising API

開発者ガイド(日本語参考訳)から、左側のナビゲーションからプログラミングガイドを順番にやっていくことで、APIが利用可能になる。
ただ、読みにくいし API Version も古いので、.com の方を見た方が良いと思う。そんな難しいことは書いてない。

Step 1: Become an Associate

Amazon アソシエイト・プログラムへの参加手続きをおこなう。ここで欲しいのが Associate tag で、XXXXX-22 とかの良く見る形式。これは、既に登録しているのでパス。

Step 2: Get an AWS Access Key ID

AWS Access Key ID と Secret Access Key で構成された鍵ペアの取得をおこなう。Amazon Web サービスのインフラストラクチャを利用しているので、(所謂)AWSのコンソールから辿って取得できる。「ここ」のページで、Access Key ID と Secret Access Key を表示できるので、copy して保存すればOK。
他にも幾つかアクセス証明書があるけど、とりあえず、このキーペアがあれば試せる。

Step 3: Get the Tools You Need

公式のサポート言語としては、JavaC#PerlPHP がサポートされていて、そのためのツールキットがダウンロードできる。ただ、REST (using HTTP GET/POST) が使えるので、他の言語でも利用は可能。実際のところ、Python でも python-amazon-product-api を使う事で容易に使用できる模様。

ただ、とりあえず色々と試してみるだけならば、Product Advertising API Signed Requests Helper のページが便利。先ほど取得した AWS Access Key ID と Secret Access Key を先頭に入力して、Unsigned URL のところに実行したい(プレーンな)URLを入力してから、「Display Signed URL」をクリックすると「Signed URL」に実際に実行可能なURLが出てくるので、後はそれ使ってブラウザ等で結果取得ができる。

Step 4: Set Up Your Development Environment

これは省略。実際に組む時に見ればよいし、たいした手間は無い。

3.Product Advertising API Signed Requests Helper の利用

何はともあれ、Product Advertising API Signed Requests Helper で試せるので、色々とやってみる。

まず、典型的には、以下のような URL を利用する。

http://ecs.amazonaws.jp/onca/xml  #=> 日本Amazonを利用。jp->com で米国など。
?Service=AWSECommerceService      #=> サービス指定で固定
&Version=2011-08-01               #=> APIバージョン (2013-08-11現在)
&Operation=ItemSearch             #=> 商品検索の実行
&SearchIndex=Books                #=> 対象のカテゴリ
&Keywords=harry+potter            #=> 検索キー
&AssociateTag=XXXXX-22            #=> アソシエイトタグ(トラッキングID)

実際に Signed URL を表示すると、以下のようになる。AWSAccessKeyId, Timestamp, Signature が付加されているのがわかる。

http://ecs.amazonaws.jp/onca/xml?AWSAccessKeyId=YYYYYYYYYYYYYYYYYYYYYYYYY&AssociateTag=XXXXX-22&Keywords=harry%20potter&Operation=ItemSearch&SearchIndex=Books&Service=AWSECommerceService&Timestamp=2013-08-10T05%3A00%3A20.000Z&Version=2011-08-01&Signature=gIR2DTFh3cCb3meUgO3GL%2FvPY9JrrUX7kF3pTdom25Y%3D

このURLでアクセスすると、以下のような XML (詳細省略) が取得される。検索パラメータ、カテゴリによってXMLの構成は異なる結果となる。正確なところは WSDL を読めば良いのだろうけど、やる気は起きないので、ざっと概略のみ。

<ItemSearchResponse>
  <!-- 要求内容を返す -->
  <OperationRequest>
    <HTTPHeaders>
      <Header>
    </HTTPHeaders>
    <RequestId/>
    <Arguments>
      <Argument/> # 複数
    </Arguments>
    <RequestProcessingTime>0.1764530000000000</RequestProcessingTime>
  </OperationRequest>

  <!-- 検索結果を返す -->
  <Items>
    <!-- 要求の解釈結果 -->
    <Request>
      <IsValid>True</IsValid>
      <ItemSearchRequest>
        <Keywords>harry potter</Keywords>
        <ResponseGroup>Small</ResponseGroup>
        <SearchIndex>Books</SearchIndex>
      </ItemSearchRequest>
    </Request>

    <!-- 件数情報 -->
    <TotalResults>413</TotalResults>
    <TotalPages>42</TotalPages>
    <MoreSearchResultsUrl>後続取得のためのURL</MoreSearchResultsUrl>

    <!-- 商品アイテム情報×取得数
      (取得できたアイテム数だけ続く、ただし、ページサイズ(例えば10)まで)
       -->
    <Item>
      <ASIN>059035342X</ASIN>
      <DetailPageURL/>

      <!-- 各種操作をおこなうためのURLリスト -->
      <ItemLinks>
        <ItemLink>
          <Description>Add To Wishlist</Description> # ここで何の操作かわかる
          <URL>http://www.amazon.co.jp/gp/registry/wishlist/...
          </URL>
        </ItemLink>
        ...
        他には、Tell A Friend, All Customer Reviews, All Offers といったところ
      </ItemLinks>

      <!-- 商品の属性情報(ここはカテゴリで結構違う) -->
      <ItemAttributes>
        <Author>J. K. Rowling</Author>
        <Creator Role="イラスト">Mary GrandPre</Creator>
        <Manufacturer>Scholastic Paperbacks</Manufacturer>
        <ProductGroup>Book</ProductGroup>
        <Title>Harry Potter and the Sorcerer's Stone</Title>
      </ItemAttributes>
    </Item>

    <!-- 以下続く... -->

  </Items>
</ItemSearchResponse>

後は、マニュアル見ながらURLのパラメータを変化させて挙動を確認していく。長くなったので、ここで分割する。

日本が去った後に 〜 台湾アイデンティティー

台湾アイデンティティーを観てきた。
日本敗戦後の「日本語世代台湾人」の戦後を個人史にスポットライトをあてて追っていく映画で、「ポレポレ東中野」で今日が初日だった。

日本統治時代そのものについての是非ではなく、失われていく「日本語世代」の戦後をひたすら追っていく。
撮影の殆どは台湾で行われているものの、映画はほとんど日本語のみで進み、海外が舞台である事を感じさせない。途中からは、これは分割統治が実行されていた場合の「日本」を舞台とした映画なのだと解釈した。実際、出演者たちは、戦前・戦中を「あのとき私は全くの日本人でした」と語る。その言葉に是非はない。でも、その「日本」はいなくなった。兄弟皆で戦場に命を捧げた対象はサヨナラしていなくなった。見捨てたと思われても仕方のない事だ。でも、その事を「運命でした」と言う。そこには、かつてあったであろう激情は無く、深い諦念と、そして激動の戦後を生き抜いてきた事への誇りを感じる。*1

国が変わる。個人のアイデンティティは国とイコールではないが、まったく別とも言い切れない。出演者たちにとって、日本は否定しようがないんだろう。今回の出演者たちにとっては日本こそが青春だった。
一方で、国民党は碌なものではなかった、と語られる。実際に白色テロにより父を殺された人も出てくる。台湾に生まれ育ちながら、統治の主体が変わる事で振り回される人々。

監督は出演者にアイデンティティを問う「あなたは何人ですか?」。答えは揺れる。でも殆どの人は「台湾人」だと答える。生きている間、人は翻弄される。でも、統治の主体に関わらず、生まれ育ったところがルーツということ、あるいは、「日本国民」を全うできなくて残った唯一のものだったのか。

監督の質問がジャカルタに住むウマル・ハルトノ(宮原永治)さんに対してだけ違う「その時が来たら、宮原さんは何人として死んでいくんでしょうか」。彼はこの問いに他の人とは違う答えをする。アイデンティティが最終的には死んだときに定まるとして、この答えは自分で勝ち取った国こそが祖国ということなんだろうか。その人が主体的に思って勝ち得たものこそがアイデンティティ。手段は武力に限るわけではないが、そうでなければ得られなかった時代。

実際のところ、かつての日本人に「戦後育ちのあなた方にはわからないでしょう」と言われるぐらいには台湾を、そして「日本」を知らなさすぎる事をわからせてくれる映画でした。

P.S.
映画に出演されていた呉正男さんを映画館の入り口でお見かけした。柔和な、それでいてしっかりとした印象の方だった。いつまでもお元気で暮らしていただきたいと思う。

*1:そういう人が出演してくれたとも言える。これは前作の台湾人生とも少し違うように思える。

IEEE1888 SDKをMacで使う: Pythonプログラミング篇

前回Javaによるプログラム開発でしたが、今回は同様のプログラムを python で作ってみます。

いろいろと前置き

SOAPのためにどのライブラリを使うかが問題だったわけです。前回から随分と時間があいたのも、ここが引っかかったからです。最初は、PyPIに対応していて使いやすそうなものということで、sudsを使用する事にしたのですが、どうしても上手く動きません。
実際に使ってみるとfactoryを使っているにも関わらず、誤ったNameSpace指定でリクエストを送信(transportがdataRQと同じ"http://soap.fiap.org/"になる)してしまい、そもそもdatabindingのレベルでfaultを返されてしまうのです。ざっとドキュメントをみた限りでは、簡単に終わるなと思っていたのに、これです。bug report など見て対応すればよいのでしょうが、面倒なので、別のモノを探す事にしました。

PyPI対応で他に簡単に使えそうなものは、PySimpleSOAPrpclibあたりでしょうか。
最初は、PySimpleSOAP を使おうかと思って試したのですが、SoapClient(wsdl=WSDL) で WSDL を読み込んで解析する段階で RuntimeError をはいてabort。使えません。
rpclibの方はと言えば、clientのサポートはsudsなどにお任せで、(多分)今回の目的には使えなそうです。(そもそも、インストールの段階でclangが必要だったりとか、もう。ちなみに、clangは、Xcodeをインストールしたうえで、Xcode > Preferences... > Downloads > Components > Command Line Tools で、コマンドラインツール一式をインストールできます。以前は何もしなくてもインストールされたはずですが、コマンドラインとか使わないんですかね皆さん)

そんなわけで、どうしたものかと思っているうちに忙しくなってしまい、放置していました。今回、しばらくぶりに余裕ができたので、sudsのドキュメントを眺めていたらPluginsの記述を見つけ、これならやれそうだと言う事で、試したら上手くいったのです。そうとわかって検索すれば、pluginを使ったメッセージの修正はわりと行われているようで、ある意味、bad knowhow として確立しているようです。

環境構築

まずは easy_install で pip インストール後に、pipを使ってお手軽にインストールします。これだけです。

$ sudo easy_install pip
$ sudo pip install suds
....

ちなみに、suds-0.4がインストールされますが、(beta版ではありますが)0.4.1が配布されてますので、それを使うのもありです。その場合は、ソースをおとして、setup.py を動かせばOKです。

コード

前回と同等のプログラムですが、以下のようになります。WSDLを取得して、Clientオブジェクトを生成し、そのfactoryを使用してリクエストを組み立てます。処理本体は非常にシンプルで、コンパイルもいりませんから、必要に応じて適当に書き換えて利用すれば良いです。
問題のpluginを使った workaround は、class BugfixMessagePlugin の部分です。コメントにあるように、組み立てられた soap envelope の transport 要素に付けられた namespace を訂正しています。

#!/usr/bin/env python
# -*- coding:utf-8 -*-

import logging
from suds.client import Client
from suds.plugin import MessagePlugin
from suds.sax.date import UTC

#############################################################
# 設定データ
WSDL_URL = 'http://192.168.11.12/axis2/services/FIAPStorage?wsdl'
POINT_ID = 'http://test.kahnn/ps/point_int_100'
POINT_VALUE = 302
POINT_TIME = UTC()

#############################################################
# デバッグ用設定
#logging.basicConfig(level=logging.INFO)
#logging.getLogger('suds.client').setLevel(logging.DEBUG)
#logging.getLogger('suds.wsdl').setLevel(logging.DEBUG)
#logging.getLogger('suds.xsd.schema').setLevel(logging.DEBUG)

#############################################################
# バグ修正用Plugin設定
class BugfixMessagePlugin(MessagePlugin):
    def marshalled(self, context):
        #print 'PLUGIN (message): marshalled: ctx=%s' % context.__dict__
        #print 'PRE  MODIFY: ', context.envelope
        # transport要素の namespace が http://soap.fiap.org/ となってしまうのを
        # http://gutp.jp/fiap/2009/11/ へ修正する。
        tp = context.envelope.childAtPath('Body/dataRQ/transport')
        tp.setPrefix(tp.findPrefix('http://gutp.jp/fiap/2009/11/'))
        #print 'POST MODIFY: ', context.envelope

myplugins = (
    BugfixMessagePlugin(),)

#############################################################
# 処理本体
client = Client(WSDL_URL, cache=None, plugins=myplugins)
#print client

val = client.factory.create('{http://gutp.jp/fiap/2009/11/}value')
val.value = POINT_VALUE
val._time = POINT_TIME

point = client.factory.create('{http://gutp.jp/fiap/2009/11/}point')
point._id = POINT_ID
point.value.append(val)

body = client.factory.create('{http://gutp.jp/fiap/2009/11/}body')
body.point.append(point)

tp = client.factory.create('{http://gutp.jp/fiap/2009/11/}transport')
tp.body = body
#print tp

try:
    result = client.service.data(tp)
    print(result)
except Exception as e:
    print e.message

実行

それでは実行してみます。

$ python ./sample_fiapapp.py
(transport){
   header = 
      (header){
         OK = ""
      }
 }

例のごとく、「Sensors in this FIAPStorage」の画面で確認しますが、別に面白くもないので省略します。
ちょっとしたテストやユーティリティを作る際は、python のようなスクリプト言語の方が楽ですね。
(まあ、今回はそれ以前で引っかかりまくりだったのですけど。。。)

最後に

ここまで IEEE1888 SDK を使って遊んできたのですが、規格自体はシンプルで特別なことは何もないです。データを取り扱うための共通基盤は用意したので、あとは好きに使ってくれというところでしょうか。全ては使う側のアイディア次第なので、これからの応用に期待といったところですかね。