« 2009年8月 | トップページ | 2009年10月 »

2009年9月

PEARのXML_Parserを使ったXMLパーサのクラス化(というか、移行方法)

PHPのXMLパーサをクラスで使おうとすると、難しいというか、簡単にはうまくいきません。
すでにxml_parseとか使って書いちゃったよ!って場合、泣かされてしまうところです。
けど、PEARのXML_Parserを使うと、移行がわりと簡単にできます。

まず、以前のようにXMLパーサ関数を使った処理の流れは大体こんなかんじ。
・要素の開始・終了時の処理、cdata(文字データ)の処理を行うハンドラ関数を作成する
・xml_parser_create() でXMLパーサを作成
・xml_set_element_handler() で、要素の開始・終了のハンドラを定義する
・xml_set_characer_data_handler() で、cdataハンドラを定義する
・xml_parse() で、XMLドキュメントの処理
・xml_parser_free() でXMLパーサの解放

手抜きしてるけど、ソースコードを例にするとこんなかんじ。
まず、xmlパース処理を呼び出すところをこんなかんじで作っております。

  $xmlparser = xml_parser_create() ;
xml_set_element_handler( $xmlparser ,
"xmlparse_h_element_start" ,
"xmlparse_h_element_end" ) ;
xml_set_character_data_handler( $xmlparser ,
"xmlparse_h_data" ) ;
$res = xml_parse( $xmlparser , $xmldata ) ;
xml_parser_free( $xmlparser ) ;

あとは、ハンドラの定義がこんなかんじにしてあるとして。
function xmlparse_h_element_start ( $parser , $name , $attrs )
{
/* 中略。ここで element の開始(開きタグ)処理 */
}
function xmlparse_h_element_end( $parser , $name )
{
/* 中略。ここで element 終了(閉じタグ)処理 */
}
function xmlparse_h_data( $parser , $data )
{
/* 中略。ここで 文字データ処理 */
}

これを、移行する方法は、こんなかんじ。
・XML_Parserの派生クラスを作る
・コンストラクタ(__construct)で、最後に「parent::XML_Parser」を呼ぶとき(必ず呼ぶこと!)、2番目の引数を 'event' にするのを忘れない。
・xml_set_element_handler()で定義した要素開始のハンドラ関数の中身を、 startHandler メンバ関数に持ってくる
・xml_set_element_handler()で定義した要素終了のハンドラ関数の中身を、 endHandler メンバ関数に持ってくる
・xml_set_characer_data_handler() で定義したcdataハンドラ関数の中身を、cdataHandler メンバ関数に持ってくる
・必要に応じて、グローバル変数とかを使ってた部分をクラス変数に変更するなど、修正する
・他から呼ぶときは、普段のクラスの呼び出しと同じ。
 ・初期化:$xmlp = new MyXmlPaser( 'UTF-8' , 'UTF-8' ) ;
 ・入力データのセット:$xmlp->setInputString( $xmldata ) ;
 ・XML処理実行:$xmlp->parse() ;

クラス部分はこんなかんじ。

require_once 'XML/Parser.php' ;

class MyXmlPaser extends XML_Parser
{
function __construct( $ca_srcenc , $ca_dstenc ) {
parent::XML_Parser( $ca_srcenc , 'event' , $ca_dstenc ) ;
}
function startHandler ( $ca_parser , $ca_name , $ca_attrs )
{ /* element start 処理 */
}
function endHandler( $ca_parser , $ca_name )
{
/* element end 処理 */
}
function cdataHandler( $ca_parser , $ca_data )
{
if ( $ca_data == FALSE ) {
} else {
/* cdata 処理 */
}
}
}

呼び出す本体をまとめるとこう。
$xmlp = new MyXmlPaser( 'UTF-8' , 'UTF-8' ) ;
$xmlp->setInputString( $xmldata ) ;
$xmlp->parse() ;

なお、基底クラスの XML_Parser でどんなことしてるかは、PEARディレクトリの XML/Parser.php にすべて書いてあるので、わからなくなったら目を通すといいと思います。

この移行によって、大幅に処理の追加や変更をする場所は(基本的に)ありません。
クラス化することによって、全部クラスに集約できるので、グローバル変数で扱うことがなくて、楽になりました。

| | コメント (0) | トラックバック (0)

さくらインターネットサーバでpear (厄介なバージョンに当たった人向け)

クラス内でxml_paserを使って・・・と思ったら、どう足掻いても使えないことがわかったので(参考:PHP-users 23513)、pearからXML_Parserを突っ込もうとしたら・・・

pear install XML_Parser
pear.php.net is using a unsupported protocal - This should never happen.
なんということでしょう〜('A`)

というわけで調べたら、
pear.php.net is using a unsupported protocal - This should never happen. - Do You PHP はてな
5.2.9と5.2.10がだめくさい。
というわけで、調べたら、

% php -v
PHP 5.2.10 (cli) (built: Jul 1 2009 18:42:15)
Copyright (c) 1997-2009 The PHP Group
Zend Engine v2.2.0, Copyright (c) 1998-2009 Zend Technologies
きたよ・・・きたよ・・・あたりのバージョンだよ・・・オワタ\(^o^)/
しかも、「 pear update-channels」を実行するにも、パーミッションの関係で不可。

・・・というわけで。自前でPEAR入れる以外の解決策が(当面は)ありませんでしたので、やることにしました。
(※サポートに言えば多分やってくれるとは思うけど)
さくらでpearインストール覚書
を参考にちょちょいとやってみました。
ちょっと古いのか、一部読み替える必要があります。
1. go-pear.php はここから落とす。→http://pear.php.net/go-pear (参考:Manual :: マネージャの取得 )
2.〜9. まではこのままでOK。ちなみに私は.cshrcにこう書いた。

setenv PATH $HOME/php/bin:$PATH

10.いきなりpearを実行ではなく、
which pear
としたときに、
/home/〜/bin/pear
と出ればOKです。
11.の前に、update-channelをするのですが、そのままだとエラーになるんで、config変えます。
pear config-set bin_dir ~/php/bin
pear config-set php_dir ~/php
pear config-set doc_dir ~/php/PEAR/docs
pear config-set test_dir ~/php/PEAR/test
pear config-set data_dir ~/php/PEAR/data

このあとに、
pear update-channel

そして、
pear install XML_Parser

これで問題なくいくようです。

| | コメント (1) | トラックバック (1)

twitterでOAuthを使う方法(その2:前回の続き〜APIにアクセスしてみる)

前回のおはなし。
twitterでOAuthを使う方法(その1:認証まで)
認証までの手順を説明しました。
・前準備:コンシューマの登録
・ユーザからのリクエスト→サービスプロバイダへリクエストトークンを要求
・サービスプロバイダの認証画面へリダイレクト→サービスプロバイダの承認
・サービスプロバイダへアクセストークンを要求

前回の説明で、署名に関して説明不足の点がありました。

リクエストに絡む情報すべて(※1)ですが、
・リクエストヘッダ
・POSTメソッドによる本体
・GETメソッドのquery_string
すべてを、「&」で結んだあとに、一括してurlencodeします。

これら3つに関して、『全部を』アルファベット順にソートしてから、&でつなげます。
三者間に優先順位・区別はなく、すべてを通してのソートです。
たとえば、
・リクエストヘッダ「a: 60」「c: 30」「g: 40」
・POSTメソッド「b=11」「e=50」
・GETのquery_string「d=24&f=33&h=66」
こういうふうに与えられてる場合、
a=60&b=11&c=30&d=24&e=50&f=33&g=40&h=66
というふうになります。

面倒なんで、コードで書くとこんなかんじです。

  // make signature --> See 9.2
private function MakeSignature( $ca_method , $ca_request_uri , $ca_header ,$ca_data ,
$ca_consumer_secret , $ca_token_secret ) {
$cl_dataarray = array() ;
// もとになるデータを生成
// Header
if ( $ca_header != NULL ) {
foreach ( $ca_header as $cl_key => $cl_val ) {
$cl_data = $cl_key . "=" . $cl_val ;
array_push( $cl_dataarray , $cl_data ) ;
}
}
// POST body
if ( $ca_data != NULL ) {
foreach ( $ca_data as $cl_key => $cl_val ) {
$cl_data = $cl_key . "=" . $cl_val ;
array_push( $cl_dataarray , $cl_data ) ;
}
}
// query_string for GET
if ( ereg( "\?" , $ca_request_uri ) ) {
$cl_uri_spl = explode( "?" , $ca_request_uri , 2 ) ; // split -> explode
$cl_request_uri = $cl_uri_spl[0] ;
if ( $cl_uri_spl[1] != "" ) {
$cl_req_qry = explode( "&" , $cl_uri_spl[1] ) ;
foreach( $cl_req_qry as $cl_data ) {
array_push( $cl_dataarray , $cl_data ) ;
}
}
} else {
$cl_request_uri = $ca_request_uri ;
}

$cl_count = 0 ;
$cl_dataall = "" ;
sort( $cl_dataarray ) ;
foreach ( $cl_dataarray as $cl_key => $cl_val ) {
if ( $cl_count > 0 ) {
$cl_dataall .= "&" ;
}
$cl_dataall .= $cl_val ;
$cl_count ++ ;
}

$cl_base = $ca_method . "&" . urlencode( $cl_request_uri ) ."&" . urlencode( $cl_dataall ) ;
$cl_base = ereg_replace( "\+" , "%20" , $cl_base ) ; // +ではなく%20で。
$cl_key = $ca_consumer_secret . "&" . $ca_token_secret ;

// HMAC-SHA1 でhash値生成
$cl_hash = hash_hmac( 'sha1' , $cl_base , $cl_key , TRUE ) ;

// 結果の処理
$cl_enc = base64_encode( $cl_hash ) ; // まずはbase64エンコードして
$cl_enc = ereg_replace( '\n' , '' , $cl_enc ) ;
$cl_enc2 = urlencode( $cl_enc ) ; // 続いてurlencodeする( 5.1 )

return $cl_enc2 ;
}


●APIへのアクセス (7)
OAuthを使う場合、APIへのアクセス方法は、
・リクエストヘッダにoAuth関連のパラメータを埋める
・GETのquery stringに埋める
・POSTの送信データに埋める
の3つの方法があります。
twitterの場合、残念ながら(リバースプロキシの関係?)、リクエストヘッダに埋める方法には対応しておりません(503エラーがかえってきます。ついでに、くじらさんが拝めます)。
従って、GET/POSTの(フォーム)データに埋めるしかありません。

与えるパラメータは以下の通りです。
・oauth_consumer_key ・・・ コンシューマのキー
・oauth_token ・・・ステップ4で(正式発行された) アクセストークン
あと、必要なもの
・OAuthのバージョン (oauth_version )
・タイムスタンプ ( oauth_timestamp )
・当該アクセスに対して、一意性を表す文字列( oauth_nonce )
・署名のプロトコル( oauth_signature_method )
・署名( oauth_signature )
そして、
・APIに与えるパラメータ

署名の作成ですが、例によって、署名以外のデータを対象にします。
署名に使うキーですが、 oauth_consumer_secret と oauth_token_secret (ステップ4で発行されたもの) を "&" でつないだものを使います。

リクエストを投げる先は、これまで通りのAPIのURIです。


○あと、気がついたこと。
認証の許可と解除を繰り返したせいかはわかりませんが。
気のせいか、updateのAPIがやたら不安定です。
よく『401 Not Authorized』を返してきます。署名が一致しないようで。

| | コメント (0) | トラックバック (0)

twitterでOAuthを使う方法(その1:認証まで)

OAuthって結構難しいと思われてるようですが、難しいというよりは、『ややこしい』です(苦笑)


そんなわけで。
手順毎に順番に説明をしようと思います。
※2009/09/23 説明の図(手書きでごめんなさい)追加しました。

●語句の説明
・サービスプロバイダ(service provider)・・・サービスを提供しているところ。この場合、twitter。
・ユーザ(user)・・・サービスプロバイダに登録していて、そのサービスを利用している人。
・コンシューマ(consumer)・・・サービスを提供しているところに、ユーザにかわって、そのサービスに対してアクセスする第三者。サードパーティ、とでも言うべきでしょうか。要は、この記事を見て「何か作ってみたい」という、あなたです。
2009092311

●ステップ0:まずは登録→Cosumer Token/Secretの取得
まずは、アプリケーションの登録作業です。ある程度かたちになってからのほうが望ましいですが、ぶっちゃけ適当でかまいません(笑)
OAuthの仕様書では、この取得に関するプロトコルは定めておりません。
サービスプロバイダとコンシューマの間で直接やりとりしてね、ってことです。
2009092312

ここから登録できます→http://twitter.com/apps/new
1.「設定」
2.「Connections」
3.右側の「Developers can edit the registration settings for their applications here.」
4.一番下に「Register a new application »」とあるので、こいつをクリック!
と行けば、登録画面に行けます。
たまにくじらさんが出ますが、そのときはリロードしてください。

登録に必要な項目は以下の通りです。
・Application Icon ・・・アプリケーションのアイコンです。アイコンはわりと重要です。
・Application Name ・・・アプリケーション名称です。
・Description ・・・アプリケーションについての説明。あまり長く書けないので簡潔に。
・Application Website ・・・ アプリケーションのサイト。説明してるサイト、もしくはホームを書きます。
・Organization ・・・ 作ってる組織。自分の名前(ニックネーム)でいいでしょう。
・Website ・・・ 作ってる組織のウェブサイト。twitterやってるなら、プロフィールのweb欄と同じで大丈夫。
・Application Type ・・・アプリケーションの種類です。
  ・Cient・・・クライアントアプリケーション。
  ・Browser・・・ブラウザベースで使用する場合。webアプリの場合はこちらでOKです。
・Callback URL・・・認証に成功したときにtwitterが戻すURI
・Use Twitter for login・・・twitterのidを認証に使用する場合はチェック
  ※twitterのidを使って自分のサービスで認証しないときは、チェックする必要はありません。

登録すると、以下の情報がもらえます。
・Consumer key ・・・コンシューマを識別するキー
・Consumer secret ・・・コンシューマの秘密鍵
・Request token URL ・・・ Request token の処理に用いる URI
・Access token URL ・・・ Access token の処理に用いる URI
・Authorize URL ・・・ 認証に用いる URI (今回は使用しない )

登録は以上です。
Consumer key , Consumer secret は、今後の処理で必要なので、忘れずに控えておいて下さい(といっても、自分の作ったアプリ一覧画面で出るから大丈夫だけど )

これからが、実際の「ユーザによる認証」のプロセスとなります。

●ステップ1:ユーザから、サービスプロバイダへのアクセスを「許可したい」と、コンシューマにリクエストが来る
あなたが提供しているサービスに対して、twitterのOAuthを使ってアクセス許可を出したい、というリクエストがきます。
2009092313

●ステップ2:サービスプロバイダへ利用許可を求める (6.1)
はい、ここからが本番です。
コンシューマ(要は、あなた)は、サービスプロバイダへ「request token」を投げます。
必要な、固有の情報は以下のとおりです。
・コンシューマキー ( oauth_consumer_key )
・ユーザが承認したときに(最終的にコンシューマの使用を許可させるために)差し戻すURI ( oauth_callback )
あと、必要なもの
・OAuthのバージョン (oauth_version )
・タイムスタンプ ( oauth_timestamp )
・当該アクセスに対して、一意性を表す文字列( oauth_nonce )
・署名のプロトコル( oauth_signature_method )
・署名( oauth_signature )
※署名に関してはややこしいので、あとで説明します。

以上の情報を添えて、サービスプロバイダへrequest tokenを投げます。
コンシューマキーと署名の情報が合致した場合、サービスプロバイダは以下の情報を返します。
・oauth_token ・・・ユーザのトークン(仮)
・oauth_token_secret ・・・ ユーザの秘密鍵
・oauth_callback_confirmed ・・・ OKだった場合これが「true」になる
 なお、そもそも失敗した場合、HTTPプロトコルの「401」がかえってきますので、成功の可否はこちらで見るべきです。
2009092314

無事に情報がかえってきた場合、コンシューマ(つまり、あなた)は、ユーザに対して、サービスプロバイダの認証用URIに行くように指示(リダイレクト)してください。
2009092315

twitterなら、 http://twitter.com/authorize
引数には、さきほど返してきた「oauth_token」を入れます。
oauth_tokenに「hogehoge1234」とついていた場合、
 http://twitter.com/authorize?oauth_token=hogehoge1234
というURI、となります。
これにより、サービスプロバイダ(この場合、twitter)の認証画面へ飛びます。

●ステップ3:サービスプロバイダによる許可 (6.2)
認証画面に行くと、「このアプリケーションへの使用許可を出しますか?」という画面になります。
ユーザはここで「Accept(許可する)」を選択します。
すると、サービスプロバイダはユーザに対して、ステップ2でサービスプロバイダに与えた「oauth_callback」へ、行くように指示します(リダイレクト)。
2009092316

このとき、以下のパラメータを付与します。
・oauth_token・・・ステップ2で戻ってきた oauth_token
・oauth_verifier・・・ユーザがOKしたということを示す(verify) キー

●ステップ4:コールバック→アクセストークンの取得 (6.3)
callbackされたリソースは、oauth_token/oauth_verifierを使って、ステップ2のユーザが「本当にリクエストしてきた」ということを証明するため、もう一度、サービスプロバイダへアクセスします。
・oauth_consumer_key ・・・ コンシューマのキー
・oauth_token ・・・ステップ2で戻ってきた oauth_token(ユーザごとの、ね)
あと、必要なもの
・OAuthのバージョン (oauth_version )
・タイムスタンプ ( oauth_timestamp )
・当該アクセスに対して、一意性を表す文字列( oauth_nonce )
・署名のプロトコル( oauth_signature_method )
・署名( oauth_signature )
※署名に関してはややこしいので、あとで説明します。
※この時点で、oauth_token_secret (ユーザごとの秘密鍵) が存在することを忘れないでください。

このリクエストを投げると、ユーザからのリクエストが正式なものとして扱われ、以下の情報がかえってきます。
・oauth_token ・・・ 『正式の』アクセストークン
・oauth_token_secret ・・・『正式の』アクセストークン秘密鍵

コンシューマは、この2つの情報を保持して、当該ユーザの情報とリンクさせて、サービスプロバイダへのアクセスに使用します。
2009092317

これで、すべての認証プロセスは終了です。


●最大の問題:署名
一番厄介な部分がここだと思います。

■signature base string ... 署名の対象となる文字列
とりあえず、単刀直入にこんなかんじになります。
・リクエストメソッド(GET/POST等)
・リクエストを投げるURI (?以降は含まない)
・リクエストに絡む情報すべて(※1)
この3つを「&」で結びます。
こんなかんじ。

POST&http%3A%2F%2Fexample.net%2Fapi.php&oauth_consumer_key=dpf43f3p2l4k3l03%26oauth_nonce=kllo9940pd9333jh%26oauth_signature_method=HMAC-SHA1%26oauth_timestamp=1191242096%26oauth_token=nnch734d00sl2jdk%26oauth_version=1.0

リクエストに絡む情報すべて(※1)ですが、
・リクエストヘッダ
・POSTメソッドによる本体
・GETメソッドのquery_string
すべてを、「&」で結んだあとに、一括してurlencodeします。
つまり、3つ目の情報においては
・「&」「=」はurlencodeの対象になる
ので、signature_base_stringの中には「&」が2個だけ存在します。

このsignature base stringに対して、指定したアルゴリズム(twitterなら、HMAC-SHA1 )で、処理したあと、base64エンコーディングします(改行コード等は取り除く)

これで、署名は完成です。


もし、何か間違いがありましたら、気軽にご指摘いただければな、と思います。

参考文献
OAuth core 1.0 ・・・オフィシャル
Yahoo!デベロッパーネットワーク - OAuth - フロー
APIアクセス権を委譲するプロトコル、OAuthを知る − @IT

| | コメント (0) | トラックバック (3)

MacOS X Snow Leopard 入・・・った!

前回の話
MacOS X Snow Leopard 入れ・・・られなかった!

先日、交換しまして。(Apple Storeでしたが、すぐに交換していただけました。多謝。)
・・・そして、無事、入りました。インストールにおいて、特に難しいところはなく。
2009092101

いまのところ、私のところでの動作状況
・Microsoft Excel 2008 → 問題なく稼働(公式情報
・OpenOffice.org → 問題なく稼働(してると思う)
・GIMP(2.4.7) →問題なく稼働
・Emacs(22.3.1) →問題なく稼働
・Getter1→問題なく稼働
・VLC→問題なく稼働
FLV2iTunesエラーで起動失敗、動作せず。
FLV2iTunesが動かない以外は問題なく使えております。

ハードウェア。
・EMOBILE D02HW → 使える(けど再設定が必要)
 再設定について:Mac OS X 10.6 Snow Leopard で イー・モバイルの D02HW を繋ぐ方法 - 転校生@Hatena
・「HUAWEI Mobile」を選択し、右下の「詳細...」。
2009092102

製造元「その他」→「HUAWEI Mobile Connect - 3G Modem」を選択。
2009092103

これでOK。

※クリーンインストールのときは別の方法となります。ドライバから手で入れないとだめ。
 →クリアインストールした Snow Leopard に EMOBILE D02HW のドライバをインストールする方法

まだ検証していないハードウェア いづれも検証完了
・EPSON GT-S600(スキャナ)
  →アップグレード後もそのまま使えた  
・EPSON PM-4000PX(プリンタ)→プリンタは最初から入れ直しになるようです。
  →ちゃんと対応してました!PM-4000PX対応 ドライバー・ソフトウェア | ダウンロード | エプソン
これに関してはこれから調べたいと思います。まだ使ってないw

| | コメント (0) | トラックバック (0)

クラスタリング(分類)処理

前回の記事で、ふぁぼったーのpostの類似性の計算処理を行いました。
今度は、それを元に類似したものどうしでクラスタリングをしてみようと思います。

クラスタリングについては解説されたページがありますが、ちょっと難しいかなーと思います。
クラスタリングとは (クラスター分析とは)

ちなみに私のやった方法ですが、このようにしました。
・類似性の高いもの A-B 、A-C 、A-D 、D-F、D-G、G-J があるとします。
・A-B,A-C,A-Dという関係があるので、まず、B,C,Dの親をAとします。
・D-F,D-Gで、FとGの親をDとしたいのですが、DがすでにAの子なので、これも親をAとします。
・G-Jで、Jの親をGとしたいのですが、GがすでにDの子なので、親をDとします・・・が、DはAの子で「親はA」となっているので、Jも親をAとします。
わかりやすく言うと、クラスタの結合ですね。
ちなみに、さきほどの文献でいうと、階層的クラスタリングの最短距離法という手法に該当します(たぶん)。

これを実装するために、各要素の属性に以下を持たせています。
・自身のID
・自身の直近の親となるID
・自身の親(根本)となるID
・自分が根本の親か、子かのフラグ
4番目は要らないような気がしますが一応持たせました。

今回使用したプログラムの要所をここに紹介したいと思います。

$i_query =<<__QUERY__;
SELECT a.id_parent, COUNT( a.id_parent ) AS cnt, MAX( a.similarity ) AS maxsiml, b.user_id, b.stat_text
FROM `uf_simrel` a, uf_favdata b
WHERE a.id_parent = b.stat_id
GROUP BY a.id_parent
ORDER BY cnt DESC
__QUERY__

$dbh = DBI->connect( $DATABASE_CONSTR , $DATABASE_USER , $DATABASE_PASS ) ;
$sth = $dbh->prepare( $i_query ) ;
$sth->execute() ;

@idlist = () ;
while( @m = $sth->fetchrow_array ) {
$id_parent = $m[0] ;
$counts = $m[1] ;
$maxrate = $m[2] ;
$userid = $m[3] ;
$usertext = $m[4] ;

push @idlist , $id_parent ;
$siml_parent{ $id_parent } = -1 ;
$siml_mark{ $id_parent } = 0 ;
$var_count{ $id_parent } = $counts ;
$var_maxrate{ $id_parent } = $maxrate ;
# print $id_parent . "\n" ;
}
$sth->finish ;

foreach $i_id ( @idlist ) {
$j_query = "SELECT id_parent,id_child,similarity FROM uf_simrel WHERE id_parent=" . $i_id ;
$sth = $dbh->prepare( $j_query ) ; $sth->execute ;
# print $j_query . "\n" ;
if ( $siml_mark{$i_id} == 1 ) {
# already similar
$parent_id = $siml_parent{ $i_id } ;
} else {
# not already
$siml_parent{ $i_id } = $i_id ;
$parent_id = $i_id ;
}
@childlist = () ;
while ( @m2 = $sth->fetchrow_array ) {
$child_id = $m2[1] ;
$simrate = $m2[2] ;
#printf( " parent=%12d child=%12d siml=%6.3f [pp:%12d]\n" ,
# $i_id , $child_id , $simrate , $parent_id ) ;
$siml_parent{ $child_id } = $parent_id ;
$siml_mark{ $child_id } = 1 ;
$parent_count{ $parent_id } ++ ;
}
}

foreach $i_id( @idlist ) {
if ( $siml_mark{$i_id} == 0 ) {
printf( "id=%12d parent=%12d mark=%d count=%4d\n" , $i_id , $siml_parent{ $i_id } , $siml_mark{ $i_id } , $parent_count{ $i_id } ) ;
$k_query = sprintf( "INSERT INTO uf_simclust (parent_id,counts) VALUES (%d,%d) " ,
$i_id , $parent_count{ $i_id } ) ;
$sth = $dbh->prepare( $k_query ) ; $sth->execute() ;
}
}

$dbh->disconnect ;

ここで出力されたクラスタ情報をもとに、元データと結合させると、結果はこんなかんじになりました。
8

思いの外きれいにまとまってしまいました。

| | コメント (0) | トラックバック (0)

類似性を探そう!

ふぁぼったーという、twitterのfavoriteをカウントして集計してくれるサービスがあるのですが、たまに似たようなものというか、ほとんど同じものがふぁぼられてることがあります。
似たようなものを排除できないかなーと思って、いろいろ考えてみました。
というか、実況で同一postで並んでるのをひたすらfavoriteして、TOPが埋まって鬱陶しいんですよ!

方針はこんなかんじ。
1.ふぁぼったーの「人気」から上位・・・だいたい10ページ分を取得
2.HTMLを解析してデータ抽出(スクレイピング)
3.抽出したデータをデータベースに突っ込む
4.突っ込んだデータをもとに、類似性を計算

1〜3は Ruby 、 4は(モジュール等の関係から) Perl を使いました。

1.net/http
2. 特になし。正規表現(そして後方参照)多用。
3.Ruby DBI

4.String::Trigram
これについては、下記ブログを参考にさせていただきました。というか、ここで知りました。
livedoor Developers Blog:String::Trigram でテキストの類似度を測る - livedoor Blog(ブログ)

結果としてはこんなかんじ。大きくなるんでダイジェストで。
左から、件数、類似率、対象1、対象2。

% perl chksiml.pl | sort | uniq -c 
70 57 ぞ! ぞ!!!
6 57 ぞ!!! ぞ!
566 72 ぞ! ぞ!!
80 72 ぞ!! ぞ!
27 78 ぞ!! ぞ!!!
7 78 ぞ!!! ぞ!!
10 88 H!I!N!A!HINAGIKU!... H!I!N!A!HINAGIKU!...
45 100 H!I!N!A!HINAGIKU!... H!I!N!A!HINAGIKU!...
1 100 ぞ! ぞ!
703 100 ぞ! ぞ!
136 100 ぞ!! ぞ!!
1 100 ぞ!!! ぞ!!!
1 100 ぼ! ぼ!

閾値50%にしましたが・・・多すぎっつーか固まりすぎ(笑)


ちなみにソースはこちら。

#!/usr/bin/perl

use POSIX ;
use Time::Local ;
use DBI ;
use String::Trigram ;

$DATABASE_CONSTR = "dbi:mysql:test:db.example.com" ;
$DATABASE_USER = "user" ;
$DATABASE_PASS = "password" ;

$TARGET_DATE = "2009-09-05" ;
$QUERY = "SELECT stat_id,stat_text,stat_date FROM `uf_favdata` WHERE stat_date >='" . $TARGET_DATE . " 00:00:00' AND stat_date <='" . $TARGET_DATE . " 23:59:59' ORDER BY stat_id " ;

$td_year = substr( $TARGET_DATE , 0 , 4 ) ;
$td_mon = substr( $TARGET_DATE , 5 , 2 ) ;
$td_day = substr( $TARGET_DATE , 8 , 2 ) ;

$dbh = DBI->connect( $DATABASE_CONSTR , $DATABASE_USER , $DATABASE_PASS ) ;
$sth = $dbh->prepare( $QUERY ) ;
$sth->execute() ;

@idlist = () ;
while( @m = $sth->fetchrow_array ) {
$st_id = $m[0] ;
$st_text = $m[1] ;
$st_date = $m[2] ;

$st_ctime = mktime( substr( $st_date , 17 , 2 ) ,
substr( $st_date , 14 , 2 ) ,
substr( $st_date , 11 , 2 ) ,
$td_daty ,
$td_mon - 1 ,
$td_year - 1900 ) ;

$var_text{ $st_id } = $st_text ;
$var_date{ $st_id } = $st_ctime ;
push @idlist , $st_id ;
push @idlist2 , $st_id ;
}

$sth->finish ;

foreach $i_id ( @idlist ) {
shift @idlist2 ;
foreach $j_id( @idlist2 ) {
last if ( $var_date{ $j_id } - $var_date{ $i_id } > 60 * 5 ) ;
$similarity_rate = String::Trigram::compare( $var_text{ $i_id } ,
$var_text{ $j_id } ) * 100 ;
if ( $similarity_rate > 50 ) {
$p_i_text = substr( $var_text{ $i_id } , 0 , 27 ) ;
$p_j_text = substr( $var_text{ $j_id } , 0 , 27 ) ;
$p_i_text .= "..." if ( length( $p_i_text ) >= 27 ) ;
$p_j_text .= "..." if ( length( $p_j_text ) >= 27 ) ;
printf( "%3d %-30s %-30s\n" , $similarity_rate , $p_i_text , $p_j_text ) ;
}
}
}

おまけ。
前の記事で、さくらインターネットでのCPAN設定の記事を書きました。
で、今回もさくらのサーバに String::Trigram をCPAN経由で入れようとして・・・案の定失敗しました。

In file included from Trigram.xs:6:
/usr/include/malloc.h:3:2: error: #error "<malloc.h> has been replaced by <stdlib.h>"

こんなのが出て。
~/.cpan/build/ 以下にソースがあるので、探し出して、includeの malloc.h を stdlib.h に変更しました。このへんは注意が必要です。

|

さくらインターネットサーバでのCPAN設定:うまくいかない人のために

私自身が個人でビジネスプロのほうを借りてまして。
とあるものが使いたくて、入れることになりました。
さくらインターネットサーバでのCPAN導入を完全に手順化して見た。 - Perl入門〜サンプルコードによるPerl入門〜
こちらの手順でだいたいあってます・・・が、一部うまくいかなかったので、補足です。
CPANの設定ファイルを作るところ。


echo no | cpan

これだとなぜか無限ループにはまってファイルが出来ないので、以下のようにしました。

cpan
(設定をひたすら手打ち。)
<auto_commit>
Always commit changes to config variables to disk? [no] yes ←ここでyesと打つ
(設定が完了してプロンプト)
cpan[1]> o conf

内容を確認して、q。
ここで ~/.cpan/CPAN/MyConfig.pm に保存されているかを確認します。

それから、次のステップに進めば大丈夫です。
あと、MyConfig.pm.editが更新されないかもしれないので、そのときは手で書き換える必要があります。
参考:otsune's FreeBSD memo :: ユーザー領域にCPANをインストールする方法
あと、


'makepl_arg' => qq[INSTALLDIRS=site INSTALL_BASE=$ENV{HOME}/local LIB=$ENV{HOME}/local/lib/perl5 ],

場合によっては、 PREFIX= は不要のことがあります。(うちは入れていたので「INSTALL_BASEとPREFIXのどちらかにしろ」と怒られた)

| | トラックバック (1)

« 2009年8月 | トップページ | 2009年10月 »