日本語形態素解析プログラム MeCab

TwitterBotを作る上で有るととても便利な気がするのが形態素解析プログラム。
その紹介とどうやってPHP形態素解析できるようにしたかのメモ。

形態素解析とはこんなの。(MeCabの実行例)

$ mecab
バッハのゴールドベルク変奏曲は本当に美しい。

バッハ  名詞,固有名詞,人名,一般,*,*,バッハ,バッハ,バッハ
の      助詞,連体化,*,*,*,*,の,ノ,ノ
ゴールドベルク  名詞,固有名詞,人名,姓,*,*,ゴールドベルク,ゴールドベルク,ゴールドベルク
変奏曲  名詞,一般,*,*,*,*,変奏曲,ヘンソウキョク,ヘンソーキョク
は      助詞,係助詞,*,*,*,*,は,ハ,ワ
本当に  副詞,一般,*,*,*,*,本当に,ホントウニ,ホントーニ
美しい  形容詞,自立,*,*,形容詞・イ段,基本形,美しい,ウツクシイ,ウツクシイ
。      記号,句点,*,*,*,*,。,。,。
EOS

こんな感じで、日本語の文章を単語毎に解析してくれるもの。


MeCabは工藤拓氏が京大工学部情報学科等の共同プロジェクトで作ったもの。
工藤氏は最近話題になったGoogleIMEの開発にも関わっている。

詳しくはこちら。
http://mecab.sourceforge.net/

上のサイトにソフトのダウンロードやインストール方法も書かれている。

UNIX環境でもWindows環境でも動くのが嬉しいところ。




私のbotXREAレンタルサーバー(UNIX)に置いてある。
そしてXREAはすばらしいことにサーバーによってはMeCabを既に入れてくれている。
私が使用しているs24サーバーもMeCab導入済みだった。
XREAはこんなの入れてほしい、という要望が通りやすいそうでそういうところがお気に入り。

PHPMeCabを動かす

さて、次はPHPMeCabを動かす方法。
MeCabは標準入力から日本語を受け取り、標準出力に解析結果を返すためパイプを使わないといけない。
その辺がややこしい。私自身パイプはあまり理解できていないので変なことをしているかも知れない。

パイプの情報を保存する変数を作る。

$descriptorspec = array(
      0 => array("pipe", "r")
    , 1 => array("pipe", "w")
);

パイプを開く
※ $this->pathは "/usr/local/bin/mecab" 等mecabのパス。

$process = proc_open($this->path, $descriptorspec, $pipes);

パイプを通じて読み書きをする。

if (is_resource($process)) {
    fwrite($pipes[0], $str);
    fclose($pipes[0]);
    $result = stream_get_contents($pipes[1]);
    fclose($pipes[1]);
    proc_close($process);
}


しかし、これではXREAのs24サーバーでは動かなかった。
どうやらPHPのバージョンが古いため stream_get_contents が無いらしい。
WEBからいろいろ引っ張ってきて実装。というかコピペ。

function stream_get_contents($handle) {
    $contents = '';
    while (!feof($handle)) {
        $contents .= fread($handle, 8192);
    }
    return $contents;
}

MeCabを実行して値を取得する関数

結局こうなった。

<?php
	function execMeCab($str) {
		
		if (!function_exists('stream_get_contents')) {
		    function stream_get_contents($handle) {
		        $contents = '';
		        while (!feof($handle)) {
		            $contents .= fread($handle, 8192);
		        }
		        return $contents;
		    }
		}
		
		$descriptorspec = array(
		      0 => array("pipe", "r")
		    , 1 => array("pipe", "w")
		);
		$result = "";
		$process = proc_open($this->path, $descriptorspec, $pipes);
		if (is_resource($process)) {
		    fwrite($pipes[0], $str);
		    fclose($pipes[0]);
		    $result = stream_get_contents($pipes[1]);
		    fclose($pipes[1]);
		    proc_close($process);
		}
		return $result;
	}
?>

$strに日本語、例えば "私はこのサイトの管理人です"とかを入れると、解析結果の文字列を返してくれる。

帰ってきた文字列を解析して連想配列に格納するなど

しかし、このままでは帰ってくるのは文字列。これでは使えないので、以下のような連想配列に格納したい。

$result[0]["word"] : "私"
$result[0]["kind"] : "名詞"
....

機能が大きくなってくるので、クラスで実装した。PHPのバージョンは5ではなくて4を想定している。(s24サーバーのPHPバージョンが4なため)


最終的なコードは以下。

<?php

// MeCabのパーサーです。
class MeCab
{
	var $path = "/usr/local/bin/mecab";
	var $resultText;
	var $data;
	
	function MeCab($str = false) {
		if($str) $this->parse($str);
	}
	
	// 形態素解析を行います。
	function parse($str) {
		
		// MeCab実行
		$this->resultText = $this->execMeCab($str);
		$this->data = array();
		/*
		 * 表層形\t品詞,品詞細分類1,品詞細分類2,品詞細分類3,活用形,活用型,原形,読み,発音
		 * ちゃんと	副詞,一般,*,*,*,*,ちゃんと,チャント,チャント
		 * 変換	名詞,サ変接続,*,*,*,*,変換,ヘンカン,ヘンカン
		 * し	動詞,自立,*,*,サ変・スル,連用形,する,シ,シ
		 * て	助詞,接続助詞,*,*,*,*,て,テ,テ
		 */
		
		// 帰ってきた文字列を変換
		$lines = explode("\n", $this->resultText);
		foreach ($lines as $line) {
			
			$line = preg_replace("/,\*/", ",", $line);
			if(preg_match("/^([^\t]+)\t(.*)$/", $line, $match)) {
				$original = $match[1];
				$tmp = explode(",", $match[2]);
				
				list($conjKind, $conjCol) = explode("", $tmp[4]);
				
				$this->data[] = array(
					"word" => $original,	// 元の単語
					"kind" => $tmp[0],		// 品詞
					"detail1" => $tmp[1],	// 品詞詳細1
					"detail2" => $tmp[2],	// 品詞詳細2
					"detail3" => $tmp[3],	// 品詞詳細3
					"conjKind" => $conjKind,// 活用形(サ変)
					"conjCol" => $conjCol,	// 活用段(ラ行)
					"conjForm" => $tmp[5],	// 活用型(連用形)
					"original" => $tmp[6],	// 原型
					"kana" => $tmp[7]		// 読み
				);
			}
		}
		return $this->data;
	}
	
	// MeCabを実行します。
	function execMeCab($str) {
		
		if (!function_exists('stream_get_contents')) {
		    function stream_get_contents($handle) {
		        $contents = '';
		        while (!feof($handle)) {
		            $contents .= fread($handle, 8192);
		        }
		        return $contents;
		    }
		}
		
		$descriptorspec = array(
		      0 => array("pipe", "r")
		    , 1 => array("pipe", "w")
		);
		$result = "";
		$process = proc_open($this->path, $descriptorspec, $pipes);
		if (is_resource($process)) {
		    fwrite($pipes[0], $str);
		    fclose($pipes[0]);
		    $result = stream_get_contents($pipes[1]);
		    fclose($pipes[1]);
		    proc_close($process);
		}
		return $result;
	}
	
}

?>

その場の思いつきで、かつとりあえず使えればいいや、的なノリで作ったのでMeCabの機能を全て出し切ることはできていないし、変数名もとってもかっこわるい。
でもとりあえず動く。たぶん。

クラスのインスタンスを作成し($mecabとする)、$mecab->parse("日本語ですよ")等とすると解析結果を連想配列で返してくれる。


でもXREAではPHPで外部プログラムを実行できない。

XREAでは外部プログラムをモジュール版PHPでは実行できないようです。
そのため、CGIPHPで実行しましょう。
※以下の設定はXREA固有のものなので他のサーバーでは通用しないかもしれません

具体的には、このPHPファイルがあるディレクトリに.htaccessを置き、.htaccessには以下のような記述をします。

AddHandler application/x-httpd-phpcgi .php

これでXREAの場合はPHPCGIとして動かせるようになります。
詳しくはググればXREAの公式サポートでのやりとりにいけるはずです。



今日のところはこんな感じで。


ちなみに、ボットでこれを使用する場合で
/..../〜〜.php のようにシェルスクリプトから実行する場合、実行権をそのphpファイルに与えないといけないのでご注意。



上に上げたMeCabの解析クラスはご自由に使っていただいて結構です。また適当に作ったものなので使いやすく改変することをお勧めします。
ただし動作や安全性に関して保証は一切できませんのでご自身の責任でお願いします。