PHPとKAKASIを使って単語に分解する(その1)

(1/1)
KAKASI という、漢字かなまじり文を平仮名やローマ時に変換するプログラムがある。これを利用すると、日本語テキストを単語に分解することができる。
今回は、PHP から外部プログラムを呼び出すサンプルとして、KAKASIを使って入力したコンテンツ(日本語テキスト)を単語に分解するプログラムを作ってみることにする。

(2021年5月30日)PHP8対応,リファラチェック追加

サンプル・プログラムの実行例

PHPとKAKASIを使って単語に分解する(その1)

サンプル・プログラム

圧縮ファイルの内容
parsewords.phpサンプル・プログラム本体。

KAKASIの準備

0020: //kakasiの実行パス;各自の環境に合わせて変更すること
0021: if (constant('PHP_OS') == 'WINNT') {
0022:     define('KAKASI', 'C:\\kakasi\\bin\\kakasi.exe');   //Windowsの場合
0023: else {
0024:     define('KAKASI', '/usr/local/bin/kakasi');         //Linuxの場合
0025: }

このプログラムを実行するには、KAKASI がインストールされている必要がある。KAKASI の入手方法やインストール方法については公式サイトを参照されたい。

KAKASI がインストールできたら、その実行パスを定数 KAKASI に記述する。OSの種類(WindowsとLinux)は、組み込み定数 PHP_OS を使って自動識別できるようにしてある。

解説:PHPから KAKASI を呼び出す

0168: /**
0169:  * kakasiを使って単語に分解する
0170:  * @param   string $kakasi kakasiの実行パス
0171:  * @param   string $str    分解するコンテンツ
0172:  * @param   string $array  分解結果を格納する配列
0173:  * @return  string分解の際に使った文字エンコード、またはFALSE(分解失敗)
0174: */
0175: function parsing($kakasi$str, &$array) {
0176:     $encode = (constant('PHP_OS') == 'WINNT') ? 'sjis' : 'sjis';
0177:     $str = mb_convert_encoding($str$encodeINTERNAL_ENCODING);
0178: 
0179:     //形態素解析をしたい文章を渡しつつ、kakasiへのハンドルオープン
0180:     $cmd = "echo '{$str}' | {$kakasi} -w -i {$encode} -o {$encode}";
0181:     $handle = popen($cmd, 'r');
0182:     if ($handle == FALSE)   return FALSE;
0183: 
0184:     //結果を1行ずつ取得
0185:     while ($get_kakasi = fgets($handle)) {
0186:         //kakasiの結果を分解
0187:         $get_kakasi = mb_convert_encoding($get_kakasiINTERNAL_ENCODING$encode);
0188:         $result = preg_split("/[\s' ]+/ums", $get_kakasi);
0189:         //結果を配列に格納する
0190:         foreach ($result as $key=>$val) {
0191:             if ($val != '') {
0192:                 if (isset($array[$val]))    $array[$val]++;
0193:                 else                        $array[$val] = 1;
0194:             }
0195:         }
0196:     }
0197:     if (pclose($handle) == (-1)) return FALSE;
0198: 
0199:     return $encode;
0200: }

KAKASI を呼び出しテキストを単語に分解するという本プログラムの中心機能を担っているのが parsing 関数である。
KAKASI には、コマンドライン・オプションを使って様々な機能を提供する。ここでは w オプションを使い、分かち書き(単語に分解)機能を利用することにする。

echo コマンドを使って入力データ(日本語テキスト)$strパイプを使って KAKASI に渡す。コマンドラインで、この処理を実行すると、画面(標準出力)に分解された単語がスペース区切りで出力される。
関数  popen  は、実行したコマンドの出力結果を関数  fgets  で取得できるようにするストリーム関数である。
ここで、日本語テキストをコマンドラインに表示する際に文字コードに注意が必要だ。本プログラムの内部処理は、冒頭の定数 INTERNAL_ENCODING で定義しているように UTF-8 だが、KAKASI に渡す前に SJIS に変換しておく。

なお、PHPセーフモードで動作しているようなサーバでは、関数  popen  の動作が許可されない。この場合は、本プログラムを CGI として動作させる必要がある。具体的には、ソース・ファイルの冒頭行に PHP の実行パス(例 #!/usr/bin/php)を記述し、"parsewords.cgi" という名前でセーブし、実行権限を与える。これでも動作しない場合は、サーバ管理者と相談してほしい。

質疑応答

【質問】 さだ様より

このページで公開されているスクリプトをDLさせていただき、http://kakasi.namazu.org/ から、kakasi-2.3.4.zip をDLし、テスト環境ということもあり、parsewords.php と同じディレクトリ内にkakasiを設置いたしました。
#parsewords.phpに記述のあるパス(テスト環境はWinXP)は通しました。
で、そこで該当ファイルを実行すると、結果として
■分解した単語
出現回数 単語
と、表示されるだけで、ポストしたデータは何も反映されません。
kakasiのインストールに関しても、正常な設定があるのだろうか?と色々と調べてはみたものの、知識に乏しい私には解決に至る内容がなく、失礼ながらにも、こちらに投稿した次第です。
そこで質問なのですが、kakasiのインストールは、単に設置するだけでは駄目なのでしょうか?もし駄目であれば、その対処法を教えてはいただけませんか?(C:\Autoexec.batを書き換えるとの内容をあるサイトでも拝見していますが、そのファイル名がなかったりで、何をしたら良いのか分からない状態です)
また、テスト終了後、実際にレンタルサーバーへアップして利用したいと考えているのですが、その場合、kakasiの設定はどのようにしたら宜しいのでしょうか?
以上、突然の質問で恐縮ですが、アドバイスなど頂戴出来れば幸いです。お忙しい中恐縮ですが、宜しくお願い申し上げます。


【回答】

まずは、障害の切り分けを行いましょう。
以下の1~3の結果をお知らせください。

1.入力データの確認
71行目の直後に
  echo $contents;
  exit(1)
を挿入し、入力された文字が正しく渡されていることを確認してください。

2.kakasi動作の確認
29行目の直後に
  echo $handle ? 'TRUE' : 'FALSE';
  exit(1)
を挿入し、FALSE になっていないことを確認してください。

3.結果取得状況の確認
77行目の直後に
  print_r($keywords);
  exit(1)
を挿入し、結果が配列に代入されていることを確認してください。

【質問】 さだ様より

只今アドバイスを頂戴した内容を確認いたしましたところ、

1.入力データの確認 → 正常にデータは渡されている。
2.kakasi 動作の確認 → TRUEになっている。

ですが、最後の結果取得状況の確認である配列には何もセットされていない状態でした。
お忙しい中、お疲れのところ大変恐縮ではありますが、引き続きご指導いただければ幸いです。


【回答】

kakasi をインストールしたディレクトリに空白が含まれていませんか?

ご承知のように、Windows で空白を含むディレクトリを指定する場合にはダブルクォーテーションで囲まなければならないのですが、これをやるとpopen関数が動作しなくなります。
kakasiはCドライブ直下にkakasiディレクトリをつくり、そこに配置することをお勧めします。

どうしても他ディレクトリに配置する場合は、空白のないディレクトリ名にすることと、環境変数KANWADICTPATHとITAIJIDICTPATHを作成し、それぞれの値に "インストール先\share\kakasi\kanwadict" と "インストール先\share\kakasi\itaijidict"を設定してください。

【質問】 さだ様より

アドバイスを頂戴したように、kakasiをCドライブ直下に設置することで、ポストしたデータがきちんと配列にセットされるまでには至ったのですが、文字化けが出ている状況です。

アドバイスを頂戴し、こちらでも色々と調べてはいるのですが、windowsに関する情報が乏しく(もちろん私の知識も。汗)、スクリプト側でmb_convert_encoding()にて対処しようと試みていますが、思うような結果を得られずにいる次第です。

ぱふぅ様のスクリプトをDLさせていただき、現状ではそのままテストを行っていますが、他のスクリプトに取り入れたく、ぱふぅ様のスクリプトにある、
 mb_internal_encoding('SJIS');
と、
 <meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS" />
を、EUCに変えている次第です。(元のままの文字コードでも文字化けは直らず)


【回答】

Windowsの echo コマンドでは、シフトJIS文字以外を扱うことはできません。
そこで、対策として2つの方法が考えられます。

1.一時的にシフトJISに変換する
まず、ユーザー関数 parsing の中で関数 popen を使って kakasi に文字列を渡す直前に、
 $str = mb_convert_encoding($str, 'SJIS', 'EUC-JP');
として、シフトJISに変換します。そして、popen から取得した値を分解する while ループの中で、関数 split で分解する直前で EUC-JP に戻してやります。
 $result = mb_convert_encoding($str, 'EUC-JP', 'SJIS');
なお、スクリプトの冒頭は
 mb_internal_encoding('EUC-JP');
に変更して下さい。

2.一時ファイルを用意する
echoコマンドで kakasi に文字列を渡す代わりに、EUC-JP文字列を一時ファイルに保存し、kakasi に渡します。kakasi には入出力の文字コードを指定するオプションがあります。
関数 popen の処理を次のように変更して下さい。変数 $infname は一時ファイル名です(空白文字を含めないようにして下さい)。
 $handle = popen("{$kakasi} -w -ieuc -oeuc < {$infname}", "r");

参考サイト

(この項おわり)
header