PHPでワードクラウドをつくる

(1/1)
ワードクラウド
ワードクラウド(word cloud)とは、与えられたコンテンツを解析し、左図のように出現頻度が高い単語ほど大きく表示する仕組みである。
今回は、与えられた URL のワードクラウドを表示するプログラムを PHP で作ってみることにする。

必要な機能

このプログラムでは、大きく 3 つの機能が要求される。
  1. URL からコンテンツを取り出す。
  2. コンテンツから単語を切り出し、出現頻度を数える。
  3. 出現頻度の高い単語ほど大きく表示する。
前者については、「PHP で形態素解析を行う」で使った WebAPI「日本語形態素解析 Web サービス」(Yahoo!JAPAN)がそのまま利用できる。また、後者については、「PHP で『kizasi.jp』を利用する」で使った表示ルーチン"putTitle"が利用できそうだ。

サンプル・プログラム

入力できるコンテンツは UTF-8 であること。また、出力の文字コードセットも UTF-8 である。
ただし、後述のコマンドライン指定機能を利用することで、UTF-8 以外の入出力ができる。

ダウンロード(PHP 4/5/7)

サンプル・プログラムの解説:日本語形態素解析Webサービス

0028: //Yahoo! JAPAN Webサービス アプリケーションID
0029: //http://help.yahoo.co.jp/help/jp/developer/developer-06.html にて登録のこと.
0030: define('APPLICATION_ID', '**************************************');

WebAPI「日本語形態素解析 Web サービス」(Yahoo!JAPAN)を利用するため、アプリケーション ID を取得する必要がある。https://e.developer.yahoo.co.jp/register から無料で取得できる。
取得した ID は変数 $ApplicationID に格納すること。

0226: /**
0227:  * 「Yahoo!JAPAN 日本語形態素解析Webサービス」を用いてテキストを解析する
0228:  * @param string $sentence 解析するテキスト
0229:  * @param array  $items 解析結果を格納する配列
0230:  * @return 
0231: */
0232: function getParse($sentence, &$items) {
0233:     //WebAPIにパラメータをPOST渡しする
0234:     $url = REQUEST_PARSE_URL;
0235:     $sentence = urlencode($sentence);
0236:     $post = array(
0237:         'appid'       => APPLICATION_ID,
0238:         'results'     => 'ma,uniq',
0239:         'uniq_filter' => '9|10',
0240:         'sentence'    => $sentence
0241:     );
0242: 
0243:     $res = http($url, 'POST', '', $post);

「URL で与えられたコンテンツから単語を切り出し、出現頻度を数える」処理は、「PHP で形態素解析を行う」で作成したユーザー関数 getParse をほぼそのままの形で使う。

ただし今回、検索対象とするのは名詞だけにすることを考えているので、filter を指定できるように、引数に $filter を追加している。

サンプル・プログラムの解説:ワードクラウドの作成

0303: /**
0304:  * ワードクラウドを作成する
0305:  * @param array  $items 情報を格納した配列
0306:  * @return string ワードクラウド(HTML)
0307: */
0308: function getWordCloud($items) {
0309:     $max_fsize = 250;            //最大サイズ(%)
0310:     $outstr = '';
0311:     $n = 0;
0312: 
0313:     //出現頻度の最大値・最小値を調べる
0314:     $count_max = 0;
0315:     $count_min = 0;
0316:     foreach ($items as $surface=>$val) {
0317:         if (mb_strlen($surface) > 1) {       //1文字は無視する
0318:             if ($val['count'] > $count_max)      $count_max = $val['count'];
0319:             if ($val['count'] < $count_min)      $count_min = $val['count'];
0320:         }
0321:     }
0322:     $k = 30 / ($count_max - $count_min);
0323: 
0324:     foreach ($items as $surface=>$val) {
0325:         if (mb_strlen($surface) > 1) {       //1文字は無視する
0326:             $n++;
0327:             $count = $val['count'];
0328:             $fsize = $k * $val['count'] * $val['count'] + 80;
0329:             if ($fsize > $max_fsize)    $fsize = $max_fsize;
0330:             $link = get_link($surface);
0331:             if ($n > 1)     $outstr .= ",\n";
0332: $outstr .=<<< EOT
0333: <span style="font-size: {$fsize}%;">
0334: <a href={$link}>{$surface}</a>
0335: </span>
0336: 
0337: EOT;
0338:         }
0339:     }
0340:     return $outstr;
0341: }

ワードクラウドを作成する処理は、「PHP で『kizasi.jp』を利用する」で使った表示ルーチン"putTitle" からだいぶ変更した。ここでは、表示文字サイズは、計算式を使って算出することにした。

単語の最多出現頻度を C1、最小出現頻度を C2、ある単語の出現頻度を N、表示する文字フォントサイズ(%)を S とすると、

S = 30 ÷ (C1 - C2) * N * N + 80


で計算することにした。
これは、ある単語の出現回数 N が増えると、回数の二乗に比例して文字サイズが大きくなるというものである。当初、単純比例で大きくしようとしたのだが、それでは頻度による大きさの違いがそれほど目立たなかったので、二乗に比例するようにした。
また、あまりにも大きくなりすぎるのも困るので、変数 $max_fsize で制限を設けるようにした。

0292: /**
0293:  * ワードクラウド表示時における個々の単語のリンク先URLを取得する
0294:  * @param string $surface 見出し語
0295: */
0296: function get_link($surface) {
0297:     $url  = 'http://www.google.com/search?hl=ja&lr=lang_ja&q=';    //Google検索
0298:     $site = 'pahoo.org';     //絞り込みsite
0299: 
0300:     return $url . urlencode($surface) . '+site%3A' . $site;
0301: }

ただ文字サイズを変更して並べるだけでは面白くないので、個々の単語にリンクを張ることにした。

ここでは、その単語を Google 検索に投げ、その際に site で絞り込み検索を指定するようにした。$site には自サイトの URL を入れておけばいいだろう。

サンプル・プログラムの解説:コンテンツの取り出し

0204: /**
0205:  * コンテンツの必要な部分を読み込む
0206:  * @param string $url コンテンツのURL
0207:  * @return string 読み込んだコンテンツ/FALSE=失敗
0208: */
0209: function my_get_contents($url) {
0210:     $instr = @file_get_contents($url);
0211:     $instr = mb_convert_encoding($instr, INTERNAL_ENCODING, 'auto');
0212:     if ($instr == FALSE || $instr == '')   return FALSE;
0213: 
0214:     //本文のみ読み込む(ぱふぅ家のホームページの場合)
0215:     preg_match('/\<\!\-\- 本文 \-\-\>(.*)\<\!\-\- フッタ \-\-\>/msu', $instr$arr);
0216:     $ss = isset($arr[1]) ? $arr[1] : $instr;
0217:     $ss = strip_tags($ss);           //タグを除く
0218:     $ss = html_entity_decode($ss);   //特殊文字を通常文字に変換
0219: 
0220:     $sps = array("\n", "\r", "\t");
0221:     $outstr = str_replace($sps, '', $ss);   //改行、タブを除く
0222: 
0223:     return $outstr;
0224: }

ユーザー関数 getParse に渡すコンテンツを URL から取り出す必要がある。
これには少し工夫が必要だ。

まず、URL の全体を getParse に渡すのは非合理的だ。ヘッダやフッタなど、コンテンツとして分析する必要がない部分も含まれてしまう。
たとえば、「ぱふぅ家のホームページ」の場合、以下のコメントで囲まれた部分に本文が入っている。

<!-- 本文 -->
  本文
<!-- フッタ -->


これ以外の部分はヘッダやメニューであるため、コンテンツには含めたくない。

そこで用意したのがユーザー関数 my_get_contents である。

まず、関数  file_get_contents  を使ってコンテンツ全体を変数 $instr に取り込む。
次に、 preg_match  を使って、解析したい本文だけを切り出す。
最後に、関数  strip_tags 、 html_entity_decode 、 str_replace  を使い、HTML や PHP のタグ、特殊文字、改行・タブなどを消去しておく。

ユーザー関数 my_get_contents については、解析したサイトの状況に合わせて変更してほしい。
なお、「ぱふぅ家のホームページ」以外については、ヘッダやフッタを含めて解析するように変更した。

参考サイト

(この項おわり)
header