ラベル WebAPI の投稿を表示しています。 すべての投稿を表示
ラベル WebAPI の投稿を表示しています。 すべての投稿を表示

2012-04-29

【電脳卸】全てのカテゴリをパースする。

電脳卸のWebAPIで「全てのカテゴリを返す」XMLファイルがあるので、それをパースしてみたよ。
PHPの組み込み関数にもXMLをパースする関数があるけど、自分で実装したくなったので自分で書いてみたよ。

APIから返されるXMLの構造が微妙に難しかったので、再帰関数とスタックでやってみた。

APIのURLは→「http://webservice.d-064.com/3.x/category.xml」です。

/**
  電脳卸用全てのカテゴリをパースして配列して、シリアライズ後保存する。
  パース結果の配列は以下のような仕様で出力される
  
  結果配列仕様:
    array( 'カテゴリID 1' => array( 'name' => 'カテゴリ名', 'parent' => '親カテゴリID' ) );
    array( 'カテゴリID 2' => array( 'name' => 'カテゴリ名', 'parent' => '親カテゴリID' ) );
    array( 'カテゴリID 3' => array( 'name' => 'カテゴリ名', 'parent' => '親カテゴリID' ) );
    ...
    array( 'カテゴリID n' => array( 'name' => 'カテゴリ名', 'parent' => '親カテゴリID' ) );
    ※一番上のカテゴリは親カテゴリIDが「-1」となっている。
  
  商品のカテゴリIDから親カテゴリ全てを取得するには、    
    function get_category_tree( $category_id, &$category_tree )
    {
      global $global_category_array;
      
      $id = $category_id;
      $name   = $global_category_array[ $id ][ 'name' ];
      $parent = $global_category_array[ $id ][ 'parent' ];
      
      $category_tree[ $id ] = 
        array( 'name'   => $name, 'parent' => $parent );
      
      if( $parent == '-1' ) {
        return ;
      } else {
        get_category_tree( $parent, $category_tree );
      }
    }
    
    $global_category_array = unserialize( file_get_contents( './category_array.dat' ) );
    $tree = array();
    get_category_tree( '1269', $tree );
    var_dump( $tree );
  といったプログラムで返せる。
*/
define( 'CATEGORY_XML_URL',         'http://webservice.d-064.com/3.x/category.xml' );
define( 'CATEGORY_XML_FILENAME',    dirname( __FILE__ ) . DIRECTORY_SEPARATOR . 'category.xml' );
define( 'CATEGORY_ARRAY_FILENAME',  dirname( __FILE__ ) . DIRECTORY_SEPARATOR . 'category_array.dat' );
define( 'CATEGORY_SRC_ENCODING',    'UTF-8' );
define( 'CATEGORY_DIST_ENCODING',   'SJIS' );
date_default_timezone_set( 'Asia/Tokyo' );

/*
  個々のカテゴリデータを取得する
  @return array [ 'category_id', 'category_depth', 'category_name' ]
*/
function get_id_depth_name()
{
  global $xmlp;
  return array(
    preg_replace( '/<[^>]+>(.*?)<\/[^>]+>/', '$1', trim( fgets( $xmlp ) ) ),        // カテゴリID
    preg_replace( '/<[^>]+>(.*?)<\/[^>]+>/', '$1', trim( fgets( $xmlp ) ) ),        // カテゴリ深さ
    preg_replace( '/<[^>]+>(.*?)<\/[^>]+>/', '$1', trim( fgets( $xmlp ) ) ) );      // カテゴリ名
}
 /**
  カテゴリXMLをパースする。再帰関数。
  @param $parent 親カテゴリID
*/
function parse( $parent = -1 ) 
{
  global $xmlp;
  global $global_category_array;
  global $global_parent_ids;        // 親IDのスタック
  
  $line = trim( fgets( $xmlp ) );
  switch( true ) {
    // 空行
    case ($line == ''):
      parse( $parent );
      break;
    
    // XML定義
    case preg_match( '/<\?xml(?:[^>]+)>/', $line ):
      parse( $parent );
      break;
    
    // 一番外側のタグ
    case ($line == ''):
      parse( $parent );
      break;
    case ($line == ''):
      fclose( $xmlp );
      return;
    
    // 1つのカテゴリ
    case ($line == ''):
      list( $id, $depth, $name ) = get_id_depth_name();
      $name = mb_convert_encoding( $name, CATEGORY_DIST_ENCODING, CATEGORY_SRC_ENCODING );
      // 親IDを退避
      $parent = $global_parent_ids[ 0 ];
      // スタックに自分を積む
      array_unshift( $global_parent_ids, $id );
      
      $global_category_array[ $id ][ 'name' ]    = $name;
      $global_category_array[ $id ][ 'parent' ]  = $parent;
      
      parse( $parent );
      break;
    case ($line == ''):
      // 自分のIDを捨てる
      array_shift( $global_parent_ids );
      parse( $global_parent_ids[ 0 ] );
      break;
      
    // サブカテゴリ
    case ($line == ''):
      parse( $global_parent_ids[ 0 ] );
      break;
    case ($line == ''):
      parse( $global_parent_ids[ 0 ] );
      break;
    
    // ファイルが終わる前に「/Categorys」が来るはずなので使わないかも。
    case feof( $xmlp ):
      fclose( $xmlp );
      return;
      
    // 何かあったときのための保険。そのまま終了。
    default:
      die( '[' . $line . '] Exception.' );
  }
}


// XMLファイルダウンロードと保存
file_put_contents( CATEGORY_XML_FILENAME, file_get_contents( CATEGORY_XML_URL ) );

// XMLファイルのポインタ取得
$xmlp = fopen( CATEGORY_XML_FILENAME, 'r' );
if( !$xmlp ) {
  die( 'XML File Dont Open.' );
}

// 親IDスタックと結果配列準備
$global_parent_ids      = array( -1 );
$global_category_array  = array();

// パース本体
parse();

// 結果配列をシリアライズして保存
file_put_contents( CATEGORY_ARRAY_FILENAME, serialize( $global_category_array ) );

exit( 0 );

こんな感じ。

2011-03-03

【AmazonAWS】商品に付いたBrowseNodesのパース方法

AmazonAWSのItemLookupのResponseGroupにBrowseNodesを入れると、そのItemが属してるカテゴリを引っ張ってこれる。
でもこのXMLが結構複雑でなかなか手をつけられないでいたのだけど、思いのほか簡単な方法で何とかなったのでそのときのメモ。ちなみにそのときのサイトは「多賀城屋書店」というサイトで携帯サイトだった。

今回は例として「涼宮ハルヒの驚愕」を使って解説してみる。



AWSのBrowseNodesは下の例のような<BrowseNode></BrowseNode>が「複数」あることと、一番上にある階層のカテゴリがXML入れ子の一番深いところにあるのが特徴。
下の例だと「本→Featured Categories→文庫総合→ライトノベル→男性向け→角川スニーカー文庫」というならびになる。


--XML例はじめ--

<BrowseNode>
  <BrowseNodeId>2220015051</BrowseNodeId>
  <Name>角川スニーカー文庫</Name>
  <Ancestors>
    <BrowseNode>
      <BrowseNodeId>2189055051</BrowseNodeId>
      <Name>男性向け</Name>
      <Ancestors>
        <BrowseNode>
          <BrowseNodeId>2189052051</BrowseNodeId>
          <Name>ライトノベル</Name>
          <Ancestors>
            <BrowseNode>
              <BrowseNodeId>2189048051</BrowseNodeId>
              <Name>文庫総合</Name>
              <Ancestors>
                <BrowseNode>
                  <BrowseNodeId>202188011</BrowseNodeId>
                  <Name>Featured Categories</Name>
                  <Ancestors>
                    <BrowseNode>
                      <BrowseNodeId>465392</BrowseNodeId>
                      <Name>本</Name>
                    </BrowseNode>
                  </Ancestors>
                </BrowseNode>
              </Ancestors>
            </BrowseNode>
          </Ancestors>
        </BrowseNode>
      </Ancestors>
    </BrowseNode>
  </Ancestors>
</BrowseNode>

--XML例終わり--


タグの解説をすると、
・<BrowseNodeId>はカテゴリIDで数字のデータ
・<Name>はカテゴリ名
・<Ancestors>は、現在のカテゴリより「階層が上」のカテゴリ
という感じ。

実際にXMLをパースするときはおそらくDOMやらを使うんだろうけど、携帯サイトでそんなことやったら結構な負荷だし自分で実装するのもめんどくさい。
そこで、正規表現で切り出そうと考えた。

上のXMLをよく見ると、「</BrowseNode>」がはじめて出てくるのが「一番上の階層の閉じタグ」、ということに気づいた。
であれば、
「/<BrowseNode>(.*?)</BrowseNode>/」
というような正規表現パターンで「階層の上半分を取ってこれる(上の例の青い字の部分を取り出せる)。

ここからもう一度正規表現で<BrowseNodeId>と<Name>を切り出せばOK。
結構簡単なことだったんだなぁ、と思ったw


---注意----
BrowseNodeには「Children」という商品が属してるカテゴリよりも下位の階層のカテゴリも返してくれる。
今回はそこまで必要じゃなかったので割愛。

2010-12-22

【Amazon】AWSを使ってすばやい表示をするサイトを作る

表題の通り、AmazonのWebAPIであるAWS(名前がころころ変わるからこれでいいのか分からないw)を使ってすばやい表示のサイトを作った時のメモ。

まず、ページを表示するたびにAPIへ接続しデータを取得するようなサイトは表示が「遅い」し、ページビュー数が増えてくるとAPI提供側から規制が掛かったりする。
そこで、すばやくするためにはどうしたらよいかを考えた。

考えた結果、以下の2つを目標とすることにした。
・表示を早くする
・APIへのアクセスを低減する


という2つの目的を同時に実現するために以下のことをした。

---
1.自サイトを置いているサーバにAPIの結果をキャッシュとして保持し、それをファイルに保存しておく

2.APIへアクセスする時のURLはほぼ一意(ユニーク)なので、URLをmd5やsha1等でハッシュした結果をキャッシュのファイル名とした

3.APIのデータはXMLで返されるので、キャッシュに保存するのは「XMLをパースした結果のシリアライズされたもの」とする。読み込み時にアンシリアライズすればデータ取得が容易になる
---

1.で保存したキャッシュが「存在するならばAPIへはアクセスしない」というプログラムにすれば「APIアクセス低減」と「表示の高速化」が実現する。
しかし、APIで返されるデータは日々更新されるので、いつかは古いデータとなってしまう。これを回避するには「キャッシュを更新」する必要がある。
しかし、更新間隔を狭めて頻繁にAPIにアクセスするようなプログラムにすると結局「遅い」し「規制に掛かる」可能性がある。
この問題には以下のように対処した

---
1.個々の商品詳細データは「ほぼ更新されない」という「前提にした」(実際は価格などが更新される)ので、キャッシュの更新はしないこととした

2.商品一覧データは売れ筋が上に来る(APIで異なる指定が可能だが)ので、サイト構成上重要だから1週間で更新されるようにした

3.更新自体はプログラムがキャッシュへアクセスするときに「ファイル作成日付を確認し」1週間より前なら「APIへアクセスしデータを取得する」、とした
---

これでもAPIが規制に掛かってしまってエラーが返されたら、それは保存せずに従前のキャッシュを返すようにもした。
これで表示に大体1ページ数秒掛かっていたのが、1秒以内で表示されるようになった。


---
データベースが使えるならそちらのほうがプログラムが簡単かもしれない。データベースに全てのデータを突っ込んでおけばメンドウはないかもしれないし。
自分はデータベースを使用する方がメンドウだ、と作ったときは感じたのでそうしたけどね。

あと、この方法は小規模のそれほどページビューが多くないサイトを想定してます。具体的には1日1万ページビューくらい。
なので、もっと大規模なサイトならそれにあわせたものにしないとだめでしょう。

ZenBack

WebMoney ぷちカンパ