PHPで形態素解析を行う

(1/1)
Yahoo!JAPANの「日本語形態素解析」は、日本語文を形態素に分割し、品詞、読みがなの付与、統計情報を取得できるWebAPIである。
サーバサイドで利用できる形態素解析は、「PHPとKAKASIを使って単語に分解する」で紹介した「KAKASI」や、「ChaSen」、「MeCab」が有名であるが、サーバに負荷がかかる処理である。この「日本語形態素解析」は処理速度も速く、サーバの負荷分散という意味では有用なWebAPIだ。

(2021年10月2日)PHP8対応,リファラ・チェック改良,https対応

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

Yahoo!JAPAN 日本語形態素解析

目次

サンプル・プログラム

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

「Yahoo!JAPAN 日本語形態素解析」による形態素解析

日本語形態素解析」は、入力パラメータ(IN)として GET、POST の2種類の方式を、出力結果(OUT)が XML で戻るというAPIである。
入力パラメータは翻訳したいテキストであるので、GET 渡しでは長くなりすぎてしまう。そこで、POST 渡しを使うことにする。

2008年(平成20年)8月12日、Yahoo!検索プロジェクトがYahoo!デベロッパーネットワークで提供しているWebAPIのドメインが、yahoo.co.jpからyahooapis.jpに変更されましたことに伴い、プログラムを修正した。⇒(Yahoo!検索、Yahoo!カテゴリのWebAPIドメイン変更のお知らせ
マッシュアップを行う場合、このように突然、WebAPIが変更されることがあるので注意が必要だ。
WebAPIのURL
URL
https://jlp.yahooapis.jp/MAService/V1/parse

入力パラメータ
フィールド名 要否 内  容
appid 必須 Yahoo! デベロッパーネットワークのアプリケーションID。無料で入手できる。
sentence 必須 解析する日本語テキスト。UTF-8エンコード。
results 任意 "ma": 形態素解析の結果を ma_result に返す。
"uniq": 出現頻度情報を uniq_result に返す。
両方指定する場合は "ma,uniq"。
応答データ構造(xml) ResultSet ma_result total_count 形態素の総数 filtered_count フィルタにマッチした形態素数 word_list word surface 形態素の表記 reading 形態素の読みがな pos 形態素の品詞 baseform 形態素の基本形表記(活用のない形態素の場合は省略) uniq_result total_count 形態素の総数 filtered_count フィルタにマッチした形態素数 word_list word surface 形態素の表記 reading 形態素の読みがな pos 形態素の品詞 count 形態素の出現数

解説:準備

0040: //Yahoo! JAPAN Webサービス アプリケーションID
0041: //https://developer.yahoo.co.jp/yconnect/v1/registration.htmlにて登録のこと.
0042: define('APPLICATION_ID', '*********************************');

「[Yahoo! ウェブ検索;blue]」を利用するためには、Yahoo! JAPAN Webサービス アプリケーションID を取得する必要がある。その入手方法は「Yahoo!JAPAN デベロッパーネットワーク - WebAPIの登録方法」を参照されたい。
取得したIDは定数 APPLICATION_ID に格納する。

解説:http関数

0155: /**
0156:  * HTTP通信を行う
0157:  * @param   string $url "http://" から始まるURL
0158:  * @param   string $method GET,POST,HEAD (省略時はGET)
0159:  * @param   string $headersその他の任意のヘッダ (省略時は"")
0160:  * @param   array  $post POST変数を格納した連想配列("変数名"=>"値") (省略時はNULL)
0161:  * @param   string $cookie Cookie(利用するときは常に$method="POST") (省略時は"")
0162:  * @return  string取得したコンテンツ/FALSE取得エラー
0163: */
0164: function http($url$method='GET', $headers='', $post=NULL$cookie='') {
0165:     if ($cookie != '')  $method = 'POST';
0166:     $URL = parse_url($url);
0167: 
0168:     $URL['query'] = isset($URL['query']) ? $URL['query'] : '';       //クエリ
0169:     $URL['port']  = isset($URL['port'])  ? $URL['port']  : 80;        //ポート番号
0170: 
0171:     //リクエストライン
0172:     $request  = $method . ' ' . $URL['path'] . $URL['query'] . " HTTP/1.1\r\n";
0173: 
0174:     //リクエストヘッダ
0175:     $request .= 'Host: ' . $URL['host'] . "\r\n";
0176:     $request .= 'User-Agent: PHP/' . phpversion() . "\r\n";
0177: 
0178:     //Basic認証用のヘッダ
0179:     if (isset($URL['user']) && isset($URL['pass'])) {
0180:         $request .= 'Authorization: Basic ' . base64_encode($URL['user'] . ':'. $URL['pass']) . "\r\n";
0181:     }
0182: 
0183:     //追加ヘッダ
0184:     $request .= $headers;
0185: 
0186:     //POSTの時
0187:     if (strtoupper($method) == 'POST') {
0188:         foreach ($post as $name=>$value) {
0189:             $POST[] = $name . '=' . $value;
0190:         }
0191:         $postdata = implode('&', $POST);
0192:         $request .= "Content-Type: application/x-www-form-urlencoded\r\n";
0193:         $request .= 'Content-Length: ' . strlen($postdata) . "\r\n";
0194:         if ($cookie != '')  $request .= "Cookie: $cookie\r\n";
0195:         $request .= "\r\n";
0196:         $request .= $postdata;
0197:     } else {
0198:         $request .= "\r\n";
0199:     }
0200: 
0201:     //接続
0202:     $fp = fsockopen($URL['host'], $URL['port']);
0203:     //エラー処理
0204:     if (!$fp)    return FALSE;
0205: 
0206:     //リクエスト送信
0207:     fputs($fp$request);
0208: 
0209:     //応答データ受信
0210:     $flag = FALSE;
0211:     while (! feof($fp)) {
0212:         $s = trim(fgets($fp));
0213:         if (preg_match('/^\<\?xml/', $s$arr) != 0) {
0214:             $response = $s;
0215:             $flag = TRUE;
0216:         } else if ($flag && preg_match('/^[0-9|a-f]+$/iu', $s) == 0) {
0217:             $response .= $s;
0218:         }
0219:     }
0220:     fclose($fp);
0221: 
0222:     return $response;
0223: }

WebAPIにパラメータをPOST渡しするため、ユーザー関数 http を用意した。

この関数は、メソッドとして GET, POST を使えるほか、任意のヘッダを渡すことができる。また、複数の変数をPOST渡しする際は、連想配列 $post 代入する。

処理としては、まず、HTTPヘッダを変数 $request に構築し、 fsockopen  関数でURLをオープンし、そこへ送信する。
サーバからの応答は、$response に代入する。

解説:パラメータの受け取り

0140: /**
0141:  * 指定したパラメータを取り出す
0142:  * @param   string $key  パラメータ名(省略不可)
0143:  * @param   bool   $auto TRUE=自動コード変換あり/FALSE=なし(省略時:TRUE)
0144:  * @param   mixed  $def  初期値(省略時:空文字)
0145:  * @return  stringパラメータ/NULL=パラメータ無し
0146: */
0147: function getParam($key$auto=TRUE$def='') {
0148:     if (isset($_GET[$key]))         $param = $_GET[$key];
0149:     else if (isset($_POST[$key]))   $param = $_POST[$key];
0150:     else                            $param = $def;
0151:     if ($auto)  $param = mb_convert_encoding($paramINTERNAL_ENCODING, 'auto');
0152:     return $param;
0153: }

一方、このスクリプト自身もパラメータを受け取る必要があるので、ユーザー関数 getParam を定義し、GET/POST いずれの方法でもパラメータを受け取れるようにしている。

解説:解析と並べ替え

0262: /**
0263:  * 「Yahoo!JAPAN日本語形態素解析」を用いてテキストを解析する
0264:  * @param   string $sentence 解析するテキスト
0265:  * @param   array  $items 解析結果を格納する配列
0266:  * @return  
0267: */
0268: function getParse($sentence, &$items) {
0269:     //WebAPIにパラメータをPOST渡しする
0270:     $url = REQ_URL;
0271:     $sentence = urlencode($sentence);
0272:     $post = array(
0273:         'appid'       => APPLICATION_ID,
0274:         'results'     => 'ma,uniq',
0275:         'uniq_filter' => '9|10',
0276:         'sentence'    => $sentence
0277:     );
0278: 
0279:     //リクエスト+エラーチェック
0280:     $res = http($url, 'POST', '', $post);
0281:     if ($res == NULL || $res == FALSE || $res == '|| preg_match('/Requested\s*Was\s*Not\s*Found/', $res) > 0) {
0282:         return FALSE;
0283:     }
0284: 
0285: //PHP4用; DOM XML利用
0286:     if (isphp5over() == FALSE) {
0287:         $dom = domxml_open_mem($res);
0288:         //形態素解析の結果
0289:         $ma_result = $dom->get_elements_by_tagname('ma_result');
0290:         if (($word_list = $ma_result[0]->get_elements_by_tagname('word_list')) == NULLreturn FALSE;
0291:         if (($word = $word_list[0]->get_elements_by_tagname('word')) == NULL)   return FALSE;
0292:         foreach ($word as $val) {
0293:             $node = $val->get_elements_by_tagname('surface');
0294:             $surface = (string)$node[0]->get_content();
0295:             $node = $val->get_elements_by_tagname('reading');
0296:             $items[$surface]['reading'] = $node[0]->get_content();
0297:             $node = $val->get_elements_by_tagname('pos');
0298:             $items[$surface]['pos'] = $node[0]->get_content();
0299:             $items[$surface]['count'] = 1;
0300:         }
0301:         //出現頻度の結果
0302:         $ma_result = $dom->get_elements_by_tagname('uniq_result');
0303:         if (($word_list = $ma_result[0]->get_elements_by_tagname('word_list')) == NULLreturn FALSE;
0304:         if (($word = $word_list[0]->get_elements_by_tagname('word')) == NULL)   return FALSE;
0305:         foreach ($word as $val) {
0306:             $node = $val->get_elements_by_tagname('surface');
0307:             $surface = (string)$node[0]->get_content();
0308:             $node = $val->get_elements_by_tagname('count');
0309:             $items[$surface]['count'] = (int)$node[0]->get_content();
0310:         }
0311: 
0312: //PHP5用; SimpleXML利用
0313:     } else {
0314:         $ResultSet = simplexml_load_string($res);
0315:         //形態素解析の結果
0316:         foreach ($ResultSet->ma_result->word_list->word as $val) {
0317:             $surface = (string)$val->surface;
0318:             $items[$surface]['reading'] = $val->reading;
0319:             $items[$surface]['pos']     = $val->pos;
0320:             $items[$surface]['count']   = 1;
0321:         }
0322:         //出現頻度の結果
0323:         foreach ($ResultSet->uniq_result->word_list->word as $val) {
0324:             $surface = (string)$val->surface;
0325:             $items[$surface]['count']   = (int)$val->count;
0326:         }
0327:     }
0328: 
0329:     return TRUE;
0330: }

解析を実行するのはユーザー関数 getParse である。
解析された単語は配列変数 $response に格納し、出現頻度の多い順に並べ替える。なお、配列変数 $response は多次元連想配列なので、並べ替えには関数  uasort  を利用した。

解説:型キャスト

0316:         foreach ($ResultSet->ma_result->word_list->word as $val) {
0317:             $surface = (string)$val->surface;
0318:             $items[$surface]['reading'] = $val->reading;
0319:             $items[$surface]['pos']     = $val->pos;
0320:             $items[$surface]['count']   = 1;
0321:         }

SimpleXML 関数の要素は、見た目は文字列だが、内部的にはobject型である。このため、string型でなければならない連想配列の添字として代入することができない。そこで、型キャストを行う必要がある。

参考サイト

(この項おわり)
header