Node用MeCabバインディング あと非同期版つくろうとして失敗した話
似非原さんの自分で記事を読むのもだるくなってきたので、コンピューターに記事を読ませることにした。 - 蟲!虫!蟲! - #!/usr/bin/bugrammer を読んでて、スクレイピングならnodejsが楽だしnodeでmecab使う手段あったっけーと思いながら調べてたら、あった。
で、KOBA719さんのnode用MeCab(https://github.com/KOBA789/node-mecab)に
# これをforkしてpackage.jsonくっつけてnpmにでも投げてくれる人が現れないかなー(ボソッ
とあったので、とりあえず package.json書いてギッハブに置いといた。https://github.com/mizchi/node-mecab
で、他に登録している人もいないし、npm publishしようと思ったんだけど、自分のnpmのバージョンが古くて登録出来なくて、でよく考えると自分でメンテする気も無いので*1、cloneしてnpmのローカルインストールで使ってください。
インストール
たぶん、mecabをUTF8でインストールしておく必要がある、と思う。node-iconv噛ませたらいけるだろうが、多言語化めんどい。
$ git clone git://github.com/mizchi/node-mecab.git $ cd node-mecab $ npm install . & npm link mecab
使い方:
元のリポジトリに書いてあった使い方だと、品詞とるのに自分でパースする必要があって面倒だったので、適当にインターフェースだけでっち上げた。
mecab = require("mecab"); console.log( mecab.parse("すもももももももものうち"));
[ [ 'すももももも',
'名詞',
'一般',
'*',
'*',
'*',
'*',
'すももももも',
'*',
'*',
'wikipedia' ],
[ 'も',
'助詞',
'係助詞',
'*',
'*',
'*',
'*',
'も',
'モ',
'モ' ],
[ 'もも',
'名詞',
'一般',
'*',
'*',
'*',
'*',
'もも',
'モモ',
'モモ' ],
[ 'の',
'助詞',
'連体化',
'*',
'*',
'*',
'*',
'の',
'ノ',
'ノ' ],
[ 'うち',
'名詞',
'非自立',
'副詞可能',
'*',
'*',
'*',
'うち',
'ウチ',
'ウチ' ] ]
どうでもいいけど、wikipediaから抽出したうちのオリジナル辞書だと「すもももももも」(漫画)が一単語として認識されてて辛い
追記
Twitterで非同期じゃないじゃん、と言われてC++の非同期版書こうとして、挫折した。
失敗した理由は、Taggerの初期化に失敗したか、Tagger#parseへの文字列の受け渡しが失敗してるか、どっちか。
僕はC++見よう見まねで書いてて、全く書けない状態からコピペで作ったので、限界がある。とくにchar* の扱いが苦手。
参考 C++ で node.js ライブラリを作る・その2 - Node.jsで遊ぶよ - Node.jsグループ
mecab_async.cc
#include <node.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <mecab.h> using namespace v8; using namespace node; class AsyncObject : ObjectWrap { public: static void Initialize (const Handle<Object> target) { HandleScope scope; Local<FunctionTemplate> tmpl = FunctionTemplate::New(New); tmpl->InstanceTemplate()->SetInternalFieldCount(1); NODE_SET_PROTOTYPE_METHOD(tmpl, "exec", Exec); target->Set(String::New("AsyncObject"), tmpl->GetFunction()); } ~AsyncObject () {} MeCab::Tagger *tagger; private: AsyncObject (const char *refername) {} struct BufferedData { AsyncObject *refer; char *ret_param; Persistent<Value> callback; MeCab::Tagger *tagger; }; // called at new AsyncObject(); static Handle<Value> New(const Arguments& args) { HandleScope scope; BufferedData *buff = (BufferedData *)malloc(sizeof(BufferedData)); // TODO : 初期化はこれでいい? buff->tagger = MeCab::createTagger(""); // buff->tagger = MeCab::createTagger((args.Length() >= 1 && args[0]->IsString()) ? *String::Utf8Value(args[0]->ToString()) : ""); if (!args.IsConstructCall()) return args.Callee()->NewInstance(); try { (new AsyncObject(*String::Utf8Value(args[0])))->Wrap(args.This()); } catch (const char *msg) { return ThrowException(Exception::Error(String::New(msg))); } return args.This(); } static Handle<Value> Exec (const Arguments& args) { HandleScope scope; BufferedData *buff = (BufferedData *)malloc(sizeof(BufferedData)); buff->refer = Unwrap<AsyncObject>(args.This()); buff->ret_param = strdup(*String::Utf8Value(args[0])); buff->callback = Persistent<Value>::New(args[1]); eio_custom(EIO_Exec, EIO_PRI_DEFAULT, Callback, buff); buff->refer->Ref(); ev_ref(EV_DEFAULT_UC); return Undefined(); } int Exec (const char *arg) { return 1; } static int EIO_Exec(eio_req *req) { HandleScope scope; BufferedData *buff = (BufferedData*)(req->data); req->result = buff->refer->Exec(buff->ret_param); // TODO : パースに失敗しているのか、代入に失敗しているのか buff->ret_param = const_cast<char*>(buff->tagger->parse(buff->ret_param)); // 文字列を渡すだけ。これはちゃんと動く // sprintf( buff->ret_param , "%s", buff->tagger->parse(buff->ret_param)); return 0; } // called as callback static int Callback(eio_req *req) { HandleScope scope; ev_unref(EV_DEFAULT_UC); BufferedData *buff = (BufferedData*)(req->data); if (buff->callback->IsFunction()) { Local<Value> argv[2]; argv[0] = req->result < 0 ? Local<Value>::New(String::New("Error")) : Local<Value>::New(Undefined()); argv[1] = Local<Value>::New( String::New(buff->ret_param) ) ; Persistent<Function>::Cast(buff->callback)->Call( buff->refer->handle_, 2, argv); } buff->refer->Unref(); buff->callback.Dispose(); free(buff->ret_param); free(buff); return 0; } }; extern "C" void init (Handle<Object> target) { AsyncObject::Initialize(target); }
wscript
srcdir = "." blddir = "build" VERSION = "0.0.1" def set_options(opt): opt.tool_options("compiler_cxx") def configure(conf): conf.check_tool("compiler_cxx") conf.check_tool("node_addon") conf.check(lib='mecab', libpath=['/usr/lib', '/usr/local/lib'], uselib_store='MECAB') def build(bld): obj = bld.new_task_gen("cxx", "shlib", "node_addon") obj.source = "mecab_async.cc" obj.target = "mecab_async" obj.uselib = "MECAB"