2010年09月



セブンネットショッピングが商品検索のAPIを公開していたので試してみました。APIの使用は登録が必要になります。APIのページはこちら。APIの使い方はAmazonのものと非常によく似ていて(というかパクリ!?)少しややこしいです。

やることは他のAPIと同じです。要はリクエストURL作ってfile_get_contentsしてsimplexml_load_stringすればいいわけです。単純ですね。早速ですが以下が実装してみたコードになります。リクエストパラメータをパイプで区切って署名してエンコードという流れが若干メンドクサイです。

[php]
$secret_key = "取得したシークレットキー";
$timestamp = gmdate("Y-m-d\TH:i:s\Z");

//署名を作成
$params = Array();
$params["ApiUserId"] = "取得したAPIユーザーID";
$params["Version"] = "2010-08-01";
$params["Timestamp"] = $timestamp;
$params["ResponseFormat"] = "XML";
$params["KeywordIn"] = $_GET['keyword']; //フォームから入力された値を入れる
ksort($params);

//リクエストパラメータをパイプ「|」で繋ぐ
$str = "GET|" . "http://api.7netshopping.jp/ws/affiliate/rest/SearchProduct";
//リクエストパラメータを「key=value」の組に分解
foreach ($params as $key => $value) {
$str .= "|" . $key . "=" . $value;
}
//できた文字列をUTF-8でURLエンコードする
$enc_str = rawurlencode($str);
//API秘密鍵を用いてHMAC-SHA256で署名
$hash = hash_hmac('sha256', $enc_str, $secret_key, true);
//できた署名をBase64でエンコードする
$signature = base64_encode($hash); //←できた署名情報

//リクエストURL組み立て
$url = "http://api.7netshopping.jp/ws/affiliate/rest/SearchProduct?";
$url .= "ApiUserId=" . $params["ApiUserId"] . "&";
$url .= "Version=" . $params["Version"] . "&";
$url .= "Timestamp=" . $params['Timestamp'] . "&";
$url .= "ResponseFormat=" . $params["ResponseFormat"] . "&";
$url .= "KeywordIn=" . $params["KeywordIn"] . "&";
$url .= "&Signature=" . $signature;

$xml = file_get_contents($url);
$parsed_xml = simplexml_load_string($xml);
[/php]この$parsed_xmlにXMLがオブジェクトとして入っているので後は取り出すだけとなります。ただ、注意点として元のXMLデータには名前空間が付いてますので普通に取り出すだけではエラーになります。以下は実際のXMLデータになります。「aff」というのが名前空間です。

[xml]

1




1102702921
PHP逆引きレシピ すぐに美味しいサンプル&テクニック261


http://www.7netshopping.jp/relay/affiliate/entranceProcess.do?url=http%3A%2F%2Fwww.7netshopping.jp%2Fbooks%2Fdetail%2F-%2Faccd%2F1102702921%2Fsubno%2F1&affid=1776987777778236&site=0&link=7

鈴木憲治/著 安藤建一/著 山田直明/著 八木照朗/著 山本義之/著 河合勝彦/著
翔泳社
978-4-7981-1986-1
2730
2730
26
1
2009-04-06T15:00:00Z
2020-12-30T15:00:00Z


http://livedoor.blogimg.jp/tetu1225/imgs/2/f/2faaaa8a.png

2009-05-30T15:00:00Z
当日~2日で発送


いまさら聞けない・誰も教えてくれない本当に必要な知識と技を一冊に凝縮。困ったときにすぐ引ける!実践で即役立つテクニックと開発ノウハウ、つまづきやすい・ハマりやすいポイントを徹底解説。Windows/Mac対応 PHP5.3対応。




[/xml]この名前空間はxmlns:aff="http://api.7netshopping.jp/affiliate/2010-08-01"と定義されているので、childrenメソッドを使って次のように取得しないといけません。各タグに名前空間が付いてしまっているのでかなり冗長なコードになってます(簡単に書く方法ある!?)。このコードで商品画像、商品名、販売価格が取得できます。

[php]
$product = $parsed_xml->children('http://api.7netshopping.jp/affiliate/2010-08-01')->Products->children('http://api.7netshopping.jp/affiliate/2010-08-01')->Product->children('http://api.7netshopping.jp/affiliate/2010-08-01');

echo '
';
echo $product->ProductName . "
";
echo $product->SalesPrice . "円
";
[/php]余談ですが、リクエストURLに必須なタイムスタンプを取得する際に、gmdate関数だと実際の(日本の)時間とはずれていたのでdate関数を使っていました。これがいけなかったらしく、認証できなかったのですがマニュアルにはちゃんと「UTCで表現」と書いてあったので日本時間とずれてても問題ないです。ずれてても気にしないようにしましょう。

ちなみにリクエストは1秒間に1回までとなってます。これを超えるとXMLデータの取得に制限がかかるため、file_get_contentsのところでコケます。個人的には1時間に3600リクエスト以内としてほしかったです。。。

このエントリーをはてなブックマークに追加

PHPでファイルやフォルダをZip化する時に使うZipArchiveでのマルチバイト文字(ここでは日本語)の取り扱いで少しはまったのでメモがてらに記事を残しておきます。例えばLinux環境(UTF-8)でZipファイルを作成し、そのファイルをWindows上で解凍すると、マルチバイト文字がファイル名やフォルダ名に入っていると文字化けをおこします。その対策になります。

通常、新規でZipファイルを作るときは以下のように書きます。こうすることでarchive.zipという圧縮ファイルに作成され、解凍するとimagesというフォルダが現れ、その中にimage1.jpgファイルが入ります。

[php]
$zip = new ZipArchive();
$zip->open("./archive.zip", ZIPARCHIVE:CREATE); //Zipファイルのファイル名
$zip->addEmptyDir("images"); //解凍したときのフォルダ名
$zip->addFile("image1.jpg", "images/image1.jpg"); //image1.jpgを「images」フォルダ配下に入れる
$zip->close();
[/php]しかしこのコード、英数字だけだったら問題ないのですが、マルチバイト文字が入ってくると、環境によっては文字化けを起こす場合があります。なので、日本語を含むマルチバイト文字を使うときには文字コードをエンコーディングしてあげないといけません。私の環境ではコードはUTF-8で書いてますので、これをSJISにエンコードします。

[php]
$zip = new ZipArchive();
$zip->open("./archive.zip", ZIPARCHIVE:CREATE); //Zipファイルのファイル名
$zip->addEmptyDir(mb_convert_encoding("画像", "SJIS", "UTF-8")); //解凍したときのフォルダ名
$zip->addFile("image1.jpg", mb_convert_encoding("画像", "SJIS", UTF-8") . "/image1.jpg"); //image1.jpgを「画像」フォルダ配下に入れる
$zip->close();
[/php]こうすることで圧縮されたファイルをWindows上で展開した時、フォルダ名にマルチバイト文字が入っていても正しく表示されます。UTF-8以外の文字コードを使っている時は適宜読み替えてください。

このエントリーをはてなブックマークに追加

インストール

PHPのデバッグのためのライブラリであるXdebug、なにやら便利そうなのでいまさらながら入れてみました。公式のインストール手順はコチラ(英語)です。まずはソースの取得をして解凍します。通常ですとこの後./configureですが、phpizeというコマンド(PHP 拡張モジュールのビルド環境を準備するためのコマンド)を使います。その後makemake installという流れです。


# wget http://www.xdebug.org/files/xdebug-2.1.0.tgz
# tar xzvf xdebug-2.1.0.tgz
# phpize
# ./configure --enable-xdebug
# make
# make install


make install後、php.iniに以下を追加します。

extension=xdebug.so


追加が終わったらApacheを再起動します。

/etc/rc.d/init.d/httpd restart


実行してみる

ためしにエラーが発生するPHPのコードを作ってブラウザで表示させてみました。定義されてない変数を表示する簡単なコードです。
[php]
echo $variable;
?>
[/php]結果はこれ。


var_dump()の結果も見やすくして表示してくれるようです。配列に値を入れてそれをvar_dumpしてみるだけのコードです。通常ですと見にくく素っ気無い結果が表示されますよね。
[php]
$books = Array();
$books[0] = "PHP6 AND MYSQL5";
$books[1] = "PHP and MySQL Web Development";
$books[2] = "Head Rush Ajax";
var_dump($books);
?>
[/php]Xdebugだとこのように割りと見やすく配列の中身を表示してくれました。


このエントリーをはてなブックマークに追加

以前、Cassandraをインストールしたのですが、今回はThriftとPHPを使ってCassandraとの連携をしてみす。まずはThriftのインストールから。環境はLinuxのCentOS 5.4を使用しています。インストールの準備としてライブラリをインストールします。Thriftをインストールするが参考になりました。


# yum install automake libtool flex bison pkgconfig gcc-c++ boost-devel libevent-devel zlib-devel python-devel ruby-devel


これができたらいよいよThriftのインストールに入ります。makeで結構時間がかかりました。なんかエラーがいくつか出てたけどとりあえず無視。コマンドの実行はスーパーユーザーで。


# cd /usr/local
# wget ftp://ftp.riken.jp/net/apache/incubator/thrift/0.2.0-incubating/thrift-0.2.0-incubating.tar.gz
# tar zxvf thrift-0.2.0-incubating.tar.gz
# cd thrift-0.2.0
# ./configure
# make
# make install


CassandraにアクセスするためのPHPプログラムを置いておくディレクトリをホームディレクトリに作成します(まぁ、phpコマンドが使えれば実際はどこでもいい)。

# mkdir ~/thrift


PHPからthriftにアクセスするためのコード類が入っているディレクトリを今作ったディレクトリにコピーする。

# cp -R /usr/local/thrift-0.2.0/lib/php/src ~/thrift


thriftのインターフェース(.thrift)をCassandraのディレクトリからコピーした後、thriftコマンドを実行すると、gen-phpディレクトリ(Cassandra用のthriftクライアント)が生成される。生成したthriftクライアントは~/thrift/src/packagesの中に入れておく。

# cd ~/thrift
# cp /usr/local/apache-cassandra-0.6.5/interface/cassandra.thrift ~/thrift/cassandra.thrift
# thrift --gen php cassandra.thrift
# mkdir ~/thrift/src/packages
# mv gen-php src/packages


環境の構築はこれでOK。とりあえず次の条件でデータをインサートしてみました。コードのinclude部分は各々の設定に変更する必要があります。

・Keyspace ⇒ bbs
・Column Family ⇒ bbs1
・Row ⇒ 1
・Column ⇒ email
・Value ⇒ foobar@example.com
[php]
//必要ファイルのインクルード(環境によって変える)
$GLOBALS['THRIFT_ROOT'] = '../src';
require_once $GLOBALS['THRIFT_ROOT'].'/packages/cassandra/Cassandra.php';
require_once $GLOBALS['THRIFT_ROOT'].'/packages/cassandra/cassandra_types.php';

require_once $GLOBALS['THRIFT_ROOT'].'/transport/TSocket.php';
require_once $GLOBALS['THRIFT_ROOT'].'/protocol/TBinaryProtocol.php';
require_once $GLOBALS['THRIFT_ROOT'].'/transport/TFramedTransport.php';
require_once $GLOBALS['THRIFT_ROOT'].'/transport/TBufferedTransport.php';

//おまじないというか設定というか
$socket = new TSocket('localhost', 9160);
$transport = new TBufferedTransport($socket, 1024, 1024);
$protocol = new TBinaryProtocolAccelerated($transport);
//Cassandraクライアントの作成
$client = new CassandraClient($protocol);
//接続開始
$transport->open();

//キースペースの設定
$keyspace = 'bbs';
//行(Row)の設定
$keyUserId = "1";

//もろもろの設定
$columnPath = new cassandra_ColumnPath();
$columnPath->column_family = 'bbs1';
$columnPath->super_column = null;
$columnPath->column = 'email';

//タイムスタンプの設定
$timestamp = time();
//一貫性レベルの設定(ZEROは読み込み時:使用不可、書き込み時:一貫性非保障を意味する)
$consistency_level = cassandra_ConsistencyLevel::ZERO;

//valueの設定
$value = "foobar@example.com";
//データのインサート
$client->insert($keyspace, $keyUserId, $columnPath, $value, $timestamp, $consistency_level);
?>
[/php]ちゃんと挿入されたかどうかをコマンドラインから確認するとちゃんと入っていることがわかります。

cassandra> get bbs.bbs1['1']
=> (column=656d61696c, value=foobar@example.com, timestamp=1283608254)
Returned 1 results.


ちなみに上記のようにPHPで挿入した部分をコマンドベースで実現しようとすると次のようになります。

cassandra> set bbs.bbs1['1']['email']='foobar@example.com'
Value inserted.


本文中にもリンク入れましたが以下のサイトを参考にしました。
■今からはじめるCassandra入門
http://labs.unoh.net/2010/08/cassandra.html
■Thriftをインストールする
http://nakayama.ddo.jp/wordpress/?p=525
■Thriftインストールメモ
http://d.hatena.ne.jp/nokuno/20090726/1248603111

このエントリーをはてなブックマークに追加


Twitterも一部のシステムで使っていることで有名な非RDBMSのCassandraをLinux(CentOS)に入れてみました。まずはダウンロードと解凍をしてとりあえず起動です。


# wget ftp://ftp.riken.jp/net/apache//cassandra/0.6.5/apache-cassandra-0.6.5-bin.tar.gz
# tar xzvf apache-cassandra-0.6.5-bin.tar.gz
# cd apache-cassandra-0.6.5/bin
# ./cassandra -f


トラブル1

以下のエラーが発生。どうやら8080ポートが使われてる様子。

エラー: エージェントが例外をスローしました。 : java.rmi.server.ExportException: Port already in use: 8080; nested exception is:
java.net.BindException: Address already in use


8080ポートはJMXが使用しているようなので、Cassandraで使用するポートを変更します。修正するファイルは.apache-cassandra-0.6.5/bin/cassandra.in.shです。以下のように9081に修正しました。


JVM_OPTS=" \
-ea \
-Xms1G \
-Xmx1G \
-XX:+UseParNewGC \
-XX:+UseConcMarkSweepGC \
-XX:+CMSParallelRemarkEnabled \
-XX:SurvivorRatio=8 \
-XX:MaxTenuringThreshold=1 \
-XX:+HeapDumpOnOutOfMemoryError \
-Dcom.sun.management.jmxremote.port=9081 \
-Dcom.sun.management.jmxremote.ssl=false \
-Dcom.sun.management.jmxremote.authenticate=false"


トラブル2

これでやっとこさ起動だー!と思ったら今度は違うエラーが。。。

java.net.BindException: Address already in use
at sun.nio.ch.Net.bind(Native Method)
at sun.nio.ch.ServerSocketChannelImpl.bind(ServerSocketChannelImpl.java:137)
at sun.nio.ch.ServerSocketAdaptor.bind(ServerSocketAdaptor.java:77)
at sun.nio.ch.ServerSocketAdaptor.bind(ServerSocketAdaptor.java:70)
at org.apache.cassandra.net.MessagingService.listen(MessagingService.java:150)
at org.apache.cassandra.service.StorageService.initServer(StorageService.java:335)
at org.apache.cassandra.thrift.CassandraDaemon.setup(CassandraDaemon.java:118)
at org.apache.cassandra.thrift.CassandraDaemon.main(CassandraDaemon.java:214)
Exception encountered during startup.
java.net.BindException: Address already in use
at sun.nio.ch.Net.bind(Native Method)
at sun.nio.ch.ServerSocketChannelImpl.bind(ServerSocketChannelImpl.java:137)
at sun.nio.ch.ServerSocketAdaptor.bind(ServerSocketAdaptor.java:77)
at sun.nio.ch.ServerSocketAdaptor.bind(ServerSocketAdaptor.java:70)
at org.apache.cassandra.net.MessagingService.listen(MessagingService.java:150)
at org.apache.cassandra.service.StorageService.initServer(StorageService.java:335)
at org.apache.cassandra.thrift.CassandraDaemon.setup(CassandraDaemon.java:118)
at org.apache.cassandra.thrift.CassandraDaemon.main(CassandraDaemon.java:214)


調べてみるとどうやら同じプログラムを二重に起動してたりすると出るエラーのよう。そういえばさっきから起動コマンド打ちまくってたからそれか!と思ってps -ef | grep cassandraでプロセスを調べてみたらいやがるいやがる!丁重ににkillさせていただきました!そして再度起動。


# ./cassandra -f


データの挿入、取り出し


違うウィンドウを起動してデータを入れてみました。


# ./cassandra-cli --host localhost --port9160

cassandra> set Keyspace1.Standard2['hoge']['hage']='aho'
Value inserted.

cassandra> get Keyspace1.Standard2['hoge']
=> (column=hage, value=aho, timestamp=1283431333222000)
Returned 1 results.

ちゃんとデータの挿入と取出しができました。

管理コマンド

他にもコマンドがあります。
管理系のコマンドでは設定ファイルを見るコマンド。

cassandra> show config file


サーバーAPIのバージョンを確認するコマンド。

cassandra> show api version
2.2.0


クラスター名を確認するコマンド。Test Clusterは初期で設定されてるクラスターですね。

cassandra> show cluster name
Test Cluster


キースペースを確認するコマンド。

cassandra> show keyspaces
Keyspace1
system


キースペースの定義を表示するコマンド。

cassandra> describe keyspace Keyspace1
Keyspace1.Super1
Column Family Type: Super
Columns Sorted By: org.apache.cassandra.db.marshal.BytesType@68cd79

Column Family Type: Super
Column Sorted By: org.apache.cassandra.db.marshal.BytesType
flush period: null minutes
------
Keyspace1.Standard2
<長いので以下省略>


これおもしろそうですね。今度はプログラムと連携させてみようかと思います。
このエントリーをはてなブックマークに追加

↑このページのトップヘ