価格.com API + Relaxer

http://apiblog.kakaku.com/KakakuItemSearchV1.0.html
価格.comでもAPIが公開されました。
以前YahooJapanAPIの時は、JaxMeなどでマッピングしたけど、
今度はrelaxerを使用してみようということで実験
Relaxer1.1はインストール済みで、
RELAX_HOMEが環境変数に登録してあり、%REALX_HOME%\binにPATHが通っているものとする。
商品検索API
を見てみると、
サンプルがあるので、それを元に、rngを生成してみます。
#kakakuResult.xml

<?xml version="1.0" encoding="utf-8" ?> 
<ProductInfo>
<NumOfResult>794</NumOfResult> 
<Item>
  <ProductID>00200614970</ProductID> 
  <ProductName>VAIO VGN-TX72B/B</ProductName> 
  <MakerName>SONY</MakerName> 
  <CategoryName>パソコン本体>Windowsノート</CategoryName> 
  <PvRanking>8</PvRanking> 
  <ImageUrl>http://img.kakaku.com/images/productimage/m/00200614970.jpg</ImageUrl> 
  <ItemPageUrl>http://kakaku.com/item/00200614970/</ItemPageUrl> 
  <BbsPageUrl>http://kakaku.com/bbs/Main.asp?PrdKey=00200614970</BbsPageUrl> 
  <ReviewPageUrl>http://kakaku.com/prdevaluate/evaluate.asp?PrdKey=00200614970</ReviewPageUrl> 
  <LowestPrice>169050</LowestPrice> 
  <NumOfBbs>122</NumOfBbs> 
</Item>
<Item>
  <ProductID>00200614970</ProductID> 
  <ProductName>VAIO VGN-TX72B/B</ProductName> 
  <MakerName>SONY</MakerName> 
  <ItemPageUrl>http://kakaku.com/item/00200614970/</ItemPageUrl> 
  <BbsPageUrl>http://kakaku.com/bbs/Main.asp?PrdKey=00200614970</BbsPageUrl> 
  <ReviewPageUrl>http://kakaku.com/prdevaluate/evaluate.asp?PrdKey=00200614970</ReviewPageUrl> 
  <LowestPrice>169050</LowestPrice> 
</Item>
</ProductInfo>
  • ここでは、ResultSetのmediumとminiのItemを混合して含めています。それぞれで別の解析をするのであれば、別々でもかまいません。

上記で作成したxmlのあるディレクトリで以下のように実行します。
>java -jar %relaxer_home%\lib\trang.jar kakakuResult.xml kakaku.result.rng
ここではtrangが同梱されていたので、trangを利用して、rngに変換しています。
コマンドを実行した結果、以下のようなファイルが出来ていると思います。

#kakaku.result.rng

<?xml version="1.0" encoding="UTF-8"?>
<grammar ns="" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
  <start>
    <element name="ProductInfo">
      <element name="NumOfResult">
        <data type="integer"/>
      </element>
      <oneOrMore>
        <element name="Item">
          <element name="ProductID">
            <data type="integer"/>
          </element>
          <element name="ProductName">
            <text/>
          </element>
          <element name="MakerName">
            <text/>
          </element>
          <optional>
            <element name="CategoryName">
              <text/>
            </element>
            <element name="PvRanking">
              <data type="integer"/>
            </element>
            <element name="ImageUrl">
              <data type="anyURI"/>
            </element>
          </optional>
          <element name="ItemPageUrl">
            <data type="anyURI"/>
          </element>
          <element name="BbsPageUrl">
            <data type="anyURI"/>
          </element>
          <element name="ReviewPageUrl">
            <data type="anyURI"/>
          </element>
          <element name="LowestPrice">
            <data type="integer"/>
          </element>
          <optional>
            <element name="NumOfBbs">
              <data type="integer"/>
            </element>
          </optional>
        </element>
      </oneOrMore>
    </element>
  </start>
</grammar>

注意点

  • 2006/10/10現在、HP上では、NumOfBbsがminiとmedium共通となっていますが、実際はmediumのみのようです。

さて、CategoryName、PvRanking、ImageUrlがまとめて、optionalと指定されていますが、このまま、relaxerを使用して出力すると、うまくいきませんでしたので、ここで手を加えます。
#修正後

<?xml version="1.0" encoding="UTF-8"?>
<grammar ns="" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
  <start>
    <element name="ProductInfo">
      <element name="NumOfResult">
        <data type="integer"/>
      </element>
      <oneOrMore>
        <element name="Item">
          <element name="ProductID">
            <data type="integer"/>
          </element>
          <element name="ProductName">
            <text/>
          </element>
          <element name="MakerName">
            <text/>
          </element>
          <optional>
            <element name="CategoryName">
              <text/>
            </element>
          </optional>
          <optional>
            <element name="PvRanking">
              <data type="integer"/>
            </element>
          </optional>
          <optional>
            <element name="ImageUrl">
              <data type="anyURI"/>
            </element>
          </optional>
          <element name="ItemPageUrl">
            <data type="anyURI"/>
          </element>
          <element name="BbsPageUrl">
            <data type="anyURI"/>
          </element>
          <element name="ReviewPageUrl">
            <data type="anyURI"/>
          </element>
          <element name="LowestPrice">
            <data type="integer"/>
          </element>
          <optional>
          <element name="NumOfBbs">
            <data type="integer"/>
          </element>
          </optional>
        </element>
      </oneOrMore>
    </element>
  </start>
</grammar>

CategoryName、PvRanking、ImageUrlを別々にoptionalとしました。
再びコマンドプロンプトで以下のように実行します。
>relaxer -java.pattern.visitor -java.package:hoge.mokkouyou.kakaku.xml.result -dir:src -dir.package kakaku.result.rng
ここでは、それぞれの引数についての説明は省略します。
Visitorパターンはとりあえず指定しましたが、なくてもいいと思います。

└─src
    └─hoge
        └─mokkouyou
            └─kakaku
                └─xml
                    └─result
                            IRNode.java
                            IRVisitable.java
                            IRVisitor.java
                            RStack.java
                            RVisitorBase.java
                            StartProductInfo.java
                            StartProductInfoItem.java
                            UJAXP.java
                            URelaxer.java
                            URVisitor.java

上記のようなファイルが生成されました。
前回はわざわざRequestを発行するクラスも作っていましたが、
今回は別に必要ないようですので、ここで実験します。(前回も必要なかった?)

StartProductInfo root= new StartProductInfo(new URL(
“http://api.kakaku.com/Ver1/ItemSearch.asp?Keyword=%83o%83C%83I& CategoryGroup=pc&ResultSet=medium&SortOrder=pricerank&PageNum=1”));
  • URL部分は実際には一行にしてください。-
  • Proxyの設定が必要な人は、

System.setProperty(“http.proxyHost”,”hostName”);
System.setProperty(“http.proxyPort”,”hostPort”);
で設定してください。
これで、rootはすでに、ドキュメント構造をもったオブジェクトとなっています。
必要に応じて、VOを作って、データのコピーをすればいいでしょう。
あとは、実際にURLを組み立てるUtilityなどがあると便利なので作っておきましょう。
#RequestParameterBuilder.java

package hoge.mokkouyou.kakaku.util;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
  public class RequestParameterBuilder {
 /* SORT */
 private static final String SORT_POPULARITYRANK = "popularityrank";
 private static final String SORT_DATERANK = "daterank";
 private static final String SORT_PRICERANK_DESC = "-pricerank";
 private static final String SORT_PRICERANK_ASC = "pricerank";
 
 /*Category*/
 private static final String CATEGORY_ALL = "ALL";
 private static final String CATEGORY_BRAND = "Brand";
 private static final String CATEGORY_SPORTS = "Sports";
 private static final String CATEGORY_GAME = "Game";
 private static final String CATEGORY_CAMERA = "Camera";
 private static final String CATEGORY_KADEN = "Kaden";
 private static final String CATEGORY_PC = "Pc";
 
 /* ResultSetValues */
 private static final String RESULT_SET_MEDIUM = "medium";
 private static final String RESULT_SET_MINI = "mini";
 
 /* keys */
 private static final String SORT_ORDER_KEY = "SortOrder";
 private static final String PAGE_NUM_KEY = "PageNum";
 private static final String CATEGORY_GROUP_KEY = "CategoryGroup";
 private static final String RESULT_SET_KEY = "ResultSet";
 private static final String KEYWORD_KEY = "Keyword";
 
 private static final String BASE_URL = "http://api.kakaku.com/Ver1/ItemSearch.asp?";
 
 private Map params = new HashMap();
 
 private int pageNum = 1;
 
 private String proxyHost;
 private String proxyPort;
 
 public RequestParameterBuilder() {
  init();
 }
 
 
 private void init() {
  setResultSetTypeMedium();
  setCategoryGroupTypeALL();
  setSortOrderKeywordRank();
  setPageNum(1);
 }
  
 public void setKeyWord(String keyword) {
  params.put(KEYWORD_KEY, keyword);
 }
 
 public void setResultSetTypeMini() {
  params.put(RESULT_SET_KEY, RESULT_SET_MINI);
 }
 
 public void setResultSetTypeMedium() {
  params.put(RESULT_SET_KEY, RESULT_SET_MEDIUM);
 }
 
 public void setCategoryGroupTypePC() {
  params.put(CATEGORY_GROUP_KEY, CATEGORY_PC);
 }
 
 public void setCategoryGroupTypeKaden() {
  params.put(CATEGORY_GROUP_KEY, CATEGORY_KADEN);
 }
 
 public void setCategoryGroupTypeCamera() {
  params.put(CATEGORY_GROUP_KEY, CATEGORY_CAMERA);
 }
 
 public void setCategoryGroupTypeGame() {
  params.put(CATEGORY_GROUP_KEY, CATEGORY_GAME);
 }
 
 public void setCategoryGroupTypeSports() {
  params.put(CATEGORY_GROUP_KEY, CATEGORY_SPORTS);
 }
 
 public void setCategoryGroupTypeBrand() {
  params.put(CATEGORY_GROUP_KEY, CATEGORY_BRAND);
 }
 
 public void setCategoryGroupTypeALL() {
  params.put(CATEGORY_GROUP_KEY, CATEGORY_ALL);
 }
 
 public void setSortOrderPriceAsc() {
  params.put(SORT_ORDER_KEY, SORT_PRICERANK_ASC);
 }
 
 public void setSortOrderPriceDesc() {
  params.put(SORT_ORDER_KEY, SORT_PRICERANK_DESC);
 }
 
 public void setSortOrderDateRank() {
  params.put(SORT_ORDER_KEY, SORT_DATERANK);
 }
 
 public void setSortOrderPopularityRank() {
  params.put(SORT_ORDER_KEY, SORT_POPULARITYRANK);
 }
 
 public void setSortOrderKeywordRank() {
  if(params.containsKey(SORT_ORDER_KEY)) {
   params.remove(SORT_ORDER_KEY);
  }
 }
 
 
 /**
  * ページ番号を指定する。<br>
  * ページは5件毎に表示されるので、そこは計算する。
  * @param num ページ番号
  */
 public void setPageNum(int num) {
  pageNum = num;
  setPageNum();
 }
   private void setPageNum() {
  params.put(PAGE_NUM_KEY, String.valueOf(pageNum));
 }
 
 public void nextPage() {
  pageNum++;
  setPageNum();
 }
 
 public void prevPage() {
  if(pageNum > 1) {
   pageNum--;
  }
  setPageNum();
 }
 
 public URL toURL() {
  URL url = null;
  try {
   if(proxyHost != null) {
    System.setProperty("http.proxyHost", proxyHost);
   }
   if(proxyPort != null) {
    System.setProperty("http.proxyPort", proxyPort);
   }
   StringBuilder sb = new StringBuilder(BASE_URL);
   
   for (Iterator iter = params.keySet().iterator(); iter.hasNext();) {
    String key = (String) iter.next();
    sb.append(key)
    .append("=")
    .append(URLEncoder.encode((String) params.get(key), "SJIS"))
    .append("&");
   }
   
   
   
   String substring = sb.substring(0, sb.length() - 1);
   url = new URL(substring);
  } catch (MalformedURLException e) {
   e.printStackTrace();
  } catch (UnsupportedEncodingException e) {
   e.printStackTrace();
  }
  return url;
 }
  
 public String getProxyHost() {
  return proxyHost;
 }
  
 public void setProxyHost(String proxyHost) {
  this.proxyHost = proxyHost;
 }
  
 public String getProxyPort() {
  return proxyPort;
 }
  
 public void setProxyPort(String proxyPort) {
  this.proxyPort = proxyPort;
 }
 
}

簡単ですが、こんな感じで十分ではないでしょうか。
デフォルトだと、ALLカテゴリで、keywordランキング順に、mediumで、1ページ目を取得します。
以下のように使用します。

RequestParameterBuilder builder = new RequestParameterBuilder();
builder.setKeyWord("バイオ");
builder.setCategoryGroupTypePC();
builder.setResultSetTypeMini();
   
builder.setProxyHost("host");
builder.setProxyPort("port");
   
URL url = builder.toURL();
StartProductInfo root = new StartProductInfo(url);

あとは、どういうロジックを組むかによって、
IRVisitor継承クラスを用意して、StartProductinfoとともに、URVisitorを使って、Visitorパターンでオブジェクト組み立てたり、表示がんばったりしてみたり・・・
といったところでしょうか。
なんだかとっても簡単ですね・・・
YahooJapan用APIはなんだったんだろう・・・