PythonからMecab (少しだけ使いやすく)


はじめに

Mecabとは形態素解析エンジンで、要するに文章を解析するソフトである。(僕が言うまでもないか。) googleやyahooにも使われてるらしく、昨日は素晴らしいの一言である。そして今回は、mecabをpythonから使いやすくしたい。python-mecab というライブラリがあって調べると重要なもののようだが、素人にはよくわからない。使いやすくなってるようには見えないので一応書いてみる。

Mecabのホームページ

コード

mecab.py
import subprocess as sp
import os
import sys
import random

if "nt" in sys.builtin_module_names:
        ENCODING="sjis"
        TYPE_COMMAND="type"
else:
        ENCODING="utf8"
        TYPE_COMMAND="cat"

def tmpf():
        fname="__mecab_tmp{0}.txt".format(random.randint(4545,46494))
        return fname if os.path.exists(fname) else tmp()

class Mecab:
        def __init__(self,text):
                self.text=text
        def __call__(self):
                return Mecab.__analize__(self.text)
        @staticmethod
        def __analize__(text):
                tmp=tmpf()
                with open(tmp,"wb") as f:
                        f.write(text.encode(ENCODING,"ignore"))
                try:
                        for line in sp.check_output("type {0} {1} | mecab".format(TYPE_COMMAND,tmp),shell=True).decode(ENCODING,"ignore").split("\n"):
                                data=line.rstrip().split("\t",1)
                                if not data[0]:
                                        pass
                                elif data[0]=="EOS\r":
                                        yield ("EOS",None)
                                else:
                                        data__=data[1].split(",")
                                        yield (data[0],data__)

               except:
                        pass
                os.remove(tmp)

        @staticmethod
        def __getDataAsSentence__(text):        #res list include data
                sentence=list()
                for word,data in Mecab.__analize__(text):
                        if word=="EOS" or (data and "\\xe3\\x80\\x82" in str(word.encode())):#str(data[1].encode())=="b\'\\xe5\\x8f\\xa5\\xe7\\x82\\xb9\'"):    #KUTEN
                                yield sentence
                                sentence=list()
                        else:
                                sentence.append((word,data))
        @staticmethod
        def getDataAsSentence(text):
                for sentence in Mecab.__getDataAsSentence__(text):
                        yield "".join(map(lambda a:a[0],sentence))
        @staticmethod
        def getWords(text):
                for word,data in Mecab.__analize__(text):
                        if word=="EOS" or (data and "\\xe3\\x80\\x82" in str(word.encode())):
                                continue
                        yield word

少し複雑にしてしまったが、ご愛敬。

まず

if "nt" in sys.builtin_module_names:
        ENCODING="sjis"
        TYPE_COMMAND="type"
else:
        ENCODING="utf8"
        TYPE_COMMAND="cat"

は、osに応じて、ファイルのエンコードと type コマンドを決めている。その他は linux 系しかそうていしない。(笑)

def tmpf():
        fname="__mecab_tmp{0}.txt".format(random.randint(4545,46494))
        return fname if not os.path.exists(fname) else tmpf()

は一時ファイルを作る。もっとうまく書けそうだが、まあ何でもいい。(笑)

Mecab クラスが肝。以下、関数の意味

init

引数 意味
text 文字列

call

Mecab.__analize__(self.text) と同じ。

__analize__

返り値 意味
data 検索結果を一行ごとに返す。 形式は (単語,それ以外のデータのリスト)

__getDataAsSentence__

EOSを区切りに、データを文単位で返す。 __analize__ の返り値のリスト。

getDataAsSentence

EOSを区切りにデータを、文にして返す。__getDataAsSentence__の文単位のデータの単語をつなげて返す。

getWords

単語を返す。

__analize__ の中で、わざわざ一時ファイルを作って書き込んでいるのは、大きいデータになると、

for line in sp.check_output("type {0} | mecab".format(tmp),shell=True).decode(ENCODING,"ignore").split("\n"):

type ... のところで、メモリーエラーになったことがあるから。もっとうまい方法があるのだろうか。。。

使ってみる

main.py
import mecab
import sys

text=sys.argv[1]

for word in mecab.Mecab.__analize__(text):
        print(word)
for word in mecab.Mecab.getWords(text):
        print(word)
実行結果
$ python main.py 隣の柿はよく客食う柿だ。
('隣', ['名詞', '一般', '*', '*', '*', '*', '隣', 'トナリ', 'トナリ'])
('の', ['助詞', '連体化', '*', '*', '*', '*', 'の', 'ノ', 'ノ'])
('柿', ['名詞', '一般', '*', '*', '*', '*', '柿', 'カキ', 'カキ'])
('は', ['助詞', '係助詞', '*', '*', '*', '*', 'は', 'ハ', 'ワ'])
('よく', ['副詞', '一般', '*', '*', '*', '*', 'よく', 'ヨク', 'ヨク'])
('客', ['名詞', '一般', '*', '*', '*', '*', '客', 'キャク', 'キャク'])
('食う', ['動詞', '自立', '*', '*', '五段・ワ行促音便', '基本形', '食う', 'クウ', 'クウ'])
('柿', ['名詞', '一般', '*', '*', '*', '*', '柿', 'カキ', 'カキ'])
('だ', ['助動詞', '*', '*', '*', '特殊・ダ', '基本形', 'だ', 'ダ', 'ダ'])
('。', ['記号', '句点', '*', '*', '*', '*', '。', '。', '。'])
('EOS', None)
隣
の
柿
は
よく
客
食う
柿
だ

おわりに

データを返すときに、辞書型にすればもっと使いやすいだろうが面倒やし、個人的な需要がないのでつくらず。必要は発明の母とはよく言ったものだ。(笑)

コード