Twitterから取得した「つぶやき」を品詞に分解する

前回のログで、TwitterからTweetとアカウント名称を取得した。
エコノミスト」誌(6/4号)の特集記事のように、Tweetに含まれる語句のワードカウントをとるには、先のデータを品詞分解(形態素解析)しなければならない。

ここでは、形態素解析エンジンとして(以前に使ったことがある)MecabMecabJavaプログラムから利用するために、そのJavaバインディングをつかう。

開発はEclipse Juno。mecabMacBook Pro(OSX Lion)にインストールしたが、Linuxでも基本的には同じでよい。

以下のサイトを参考にさせてもらった。
Qiita: MacにMecabをインストールする (2013.3)
superdry memorandum :-D:MecabのJavaバインディング

Macmecabをインストールする

mecab本家サイトからmecab-0.996.tarをダウンロード、解凍する。
Makeをする必要があるので、必要に応じて、Xcode Command Line Toolとautoconf, automakeをインストールする。

解凍したディレクトリに移動し、以下でインストールする。

./configure
make
sudo make install

つぎに、Mecab-ipadicをインストールする。
これもダウンロードサイトからmecab-ipadic-2.7.0-20070801.tarをダウンロードして、解凍する。
以下でインストールする。

./configure --with-charset=utf8
make
sudo make install

Javaバインディングのインストール

MecabのBindingのページをみると、SwigというC, C++でかかれたプログラム(今回の場合、Mecab)と、Web系言語のブリッジをするソフトウェアを使っているようだ。
SWIGの Supported languageをみると、Perl, Python, Ruby, Javaなどがサポートされている。

本家サイトから、mecab-java-0.996.tarをダウンロードし、解凍する。
参考サイトにしたがって、MakeFileを以下のように修正し、解凍してできたディレクトリに移動してsudo makeする。

TARGET=MeCab
JAVAC=javac
JAVA=java
JAR=jar
CXX=c++
INCLUDE=/System/Library/Frameworks/JavaVM.framework/Headers

PACKAGE=org/chasen/mecab

LIBS=`mecab-config --libs`
INC=`mecab-config --cflags` -I$(INCLUDE) -I$(INCLUDE)/linux

all:
	$(CXX) -O3 -c -fpic $(TARGET)_wrap.cxx  $(INC)
	$(CXX) -shared  $(TARGET)_wrap.o -o lib$(TARGET).so $(LIBS)
	$(JAVAC) $(PACKAGE)/*.java -J-Dfile.encoding=UTF8
	$(JAVAC) test.java -J-Dfile.encoding=UTF8
	$(JAR) cfv $(TARGET).jar $(PACKAGE)/*.class -J-Dfile.encoding=UTF8:

test:
	env LD_LIBRARY_PATH=. $(JAVA) test

clean:
	rm -fr *.jar *.o *.so *.class $(PACKAGE)/*.class
	
cleanall:
	rm -fr $(TARGET).java *.cxx

mecab-java-0.996ディレクトリを、~に移動し、mecab-javaというシンボリックリンクを張る。

Javaプログラム

Eclipse(Juno)にJavaプロジェクトを作成する。ここでは、mecabsampleとした。デフォルトパッケージにtest.javaを入れ、以下の用に修正する。ここでも、参考サイトを参考にさせていただいた。
このサンプルは、「太郎は二郎にこの本を渡した。」を3通りの方法で品詞分解するものとなっている。

import java.io.File;

import org.chasen.mecab.MeCab;
import org.chasen.mecab.Tagger;
import org.chasen.mecab.Model;
import org.chasen.mecab.Lattice;
import org.chasen.mecab.Node;

public class test {
  static {
    try {
    	File f = new File("/Users/tetsuya/mecab-java/libMeCab.so");
    	System.load(f.toString());
    } catch (UnsatisfiedLinkError e) {
       System.err.println("Cannot load the example native code.\nMake sure your LD_LIBRARY_PATH contains \'.\'\n" + e);
       System.exit(1);
    }
  }

  public static void main(String[] argv) {
     System.out.println(MeCab.VERSION);
     Tagger tagger = new Tagger();
     String str = "太郎は二郎にこの本を渡した。";
     System.out.println(tagger.parse(str));
     Node node = tagger.parseToNode(str);
     for (;node != null; node = node.getNext()) {
	System.out.println(node.getSurface() + "\t" + node.getFeature());
     }
     System.out.println ("EOS\n");

     Model model = new Model();
     Tagger tagger2 = model.createTagger();
     System.out.println (tagger2.parse(str));

     Lattice lattice = model.createLattice();
     System.out.println(str);
     lattice.set_sentence(str);
     if (tagger2.parse(lattice)) {
       System.out.println(lattice.toString());
       for (node = lattice.bos_node(); node != null; node = node.getNext()) {
	  System.out.println(node.getSurface() + "\t" + node.getFeature());
       }
       System.out.println("EOS\n");
     }

     lattice.add_request_type(MeCab.MECAB_NBEST);
     lattice.set_sentence(str);
     tagger2.parse(lattice);
     for (int i = 0; i < 10; ++i) {
       if (lattice.next()) {
         System.out.println("nbest:" + i + "\n" +
                            lattice.toString());
       }
     }
  }
}

これを実行すると以下の結果がコンソールに出力される。

0.996
太郎	名詞,固有名詞,人名,名,*,*,太郎,タロウ,タロー
は	助詞,係助詞,*,*,*,*,は,ハ,ワ
二郎	名詞,固有名詞,人名,名,*,*,二郎,ジロウ,ジロー
に	助詞,格助詞,一般,*,*,*,に,ニ,ニ
この	連体詞,*,*,*,*,*,この,コノ,コノ
本	名詞,一般,*,*,*,*,本,ホン,ホン
を	助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
渡し	動詞,自立,*,*,五段・サ行,連用形,渡す,ワタシ,ワタシ
た	助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
。	記号,句点,*,*,*,*,。,。,。
EOS

	BOS/EOS,*,*,*,*,*,*,*,*
太郎	名詞,固有名詞,人名,名,*,*,太郎,タロウ,タロー
は	助詞,係助詞,*,*,*,*,は,ハ,ワ
二郎	名詞,固有名詞,人名,名,*,*,二郎,ジロウ,ジロー
に	助詞,格助詞,一般,*,*,*,に,ニ,ニ
この	連体詞,*,*,*,*,*,この,コノ,コノ
本	名詞,一般,*,*,*,*,本,ホン,ホン
を	助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
渡し	動詞,自立,*,*,五段・サ行,連用形,渡す,ワタシ,ワタシ
た	助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
。	記号,句点,*,*,*,*,。,。,。
	BOS/EOS,*,*,*,*,*,*,*,*
EOS

太郎	名詞,固有名詞,人名,名,*,*,太郎,タロウ,タロー
は	助詞,係助詞,*,*,*,*,は,ハ,ワ
二郎	名詞,固有名詞,人名,名,*,*,二郎,ジロウ,ジロー
に	助詞,格助詞,一般,*,*,*,に,ニ,ニ
この	連体詞,*,*,*,*,*,この,コノ,コノ
本	名詞,一般,*,*,*,*,本,ホン,ホン
を	助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
渡し	動詞,自立,*,*,五段・サ行,連用形,渡す,ワタシ,ワタシ
た	助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
。	記号,句点,*,*,*,*,。,。,。
EOS

太郎は二郎にこの本を渡した。
太郎	名詞,固有名詞,人名,名,*,*,太郎,タロウ,タロー
は	助詞,係助詞,*,*,*,*,は,ハ,ワ
二郎	名詞,固有名詞,人名,名,*,*,二郎,ジロウ,ジロー
に	助詞,格助詞,一般,*,*,*,に,ニ,ニ
この	連体詞,*,*,*,*,*,この,コノ,コノ
本	名詞,一般,*,*,*,*,本,ホン,ホン
........

取得したTweetを品詞分解するJavaプログラム

前回のログで、Twitter API経由で取得したTweetとアカウント名をテキストファイルに書き出した(tweets.txt)。
これを読み込んでMecabで品詞分解し、「名詞」のみを別のファイル(tweets_noun.txt)に書き出すプログラムを作成する。
HadoopMapReduceを使ってワードカウントを取りたいので、1名詞で1行出力する。

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.StringTokenizer;

import org.chasen.mecab.MeCab;
import org.chasen.mecab.Tagger;
import org.chasen.mecab.Node;

public class TestTweet {
  static {
    try {
    	File f = new File("/Users/tetsuya/mecab-java/libMeCab.so");
    	System.load(f.toString());
    } catch (UnsatisfiedLinkError e) {
       System.err.println("Cannot load the example native code.\nMake sure your LD_LIBRARY_PATH contains \'.\'\n" + e);
       System.exit(1);
    }
  }

  public static void main(String[] argv) {
     System.out.println(MeCab.VERSION);
 	try {
 		// 読み込み用
 		File file = new File("[path to the file]/tweets.txt");
 		BufferedReader br;
 		br = new BufferedReader(new FileReader(file));
 		// 書き出し用
		File ofile = new File("[path to the file]/tweets_noun.txt");
		PrintWriter pw;
		// 追記で書き込む
		//pw = new PrintWriter(new BufferedWriter(new FileWriter(ofile,true)));
		pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(ofile,true),"UTF-8")));
		
		// 読み込み
 		String str = br.readLine();
 		while(str != null){
 			Tagger tagger = new Tagger();
 			Node node = tagger.parseToNode(str);
 			for (;node != null; node = node.getNext()) {
    		 	String sf = node.getSurface();
    		 	String ft = node.getFeature();
				
    		 	// ハッシュタグとURLの削除	
				StringTokenizer sta = new StringTokenizer(ft, ",");
				//トークンの出力
			    while(sta.hasMoreTokens()) {
			    	if(sta.nextToken().equals("名詞")){
			    		pw.println(sf);
			    		//System.out.println(sf + "\t" + ft);
			    	}
			    }
 			}
 			str = br.readLine();
     	}
        pw.close();
		br.close();
		
	} catch (FileNotFoundException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	} catch (IOException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
     System.out.println ("EOS\n");
  }

}

結果

前回のログでは、「ある地方都市を意味するハッシュタグ」のついたTweet(5日分)を取得し、本文とアカウント名称を抜き出した(2394行)。
これを品詞分解した実行サンプル(一部)を以下に示す。(全体では約14000語)
記号、数字が紛れ込んでいるのが分かる。

お客様
還元
!!
月額
料金
得
♪
問い合わせ
クラブ
メディア
?
048
クラブ
メディア
北浦和
店
日曜