MongoDBなら検索エンジンが簡単に作れる

形態素解析でインデックスを作って検索する
Mongoでの全文検索 - Docs-Japanese - 10gen Confluenceを参考に、すぐ実装できた
試しに、青空文庫から走れメロス引っ張ってやってみた。

ライブラリ

MongoDB API Docs for python
> easy_install pymongo


SREngine: Sein blog PythonからMeCabを使う。
ごにょごにょやっていれる

やってみる

#!/usr/bin/env python
# encoding:utf-8

import pymongo
import MeCab
import urllib
import re

mecab = MeCab.Tagger("-Ochasen")
con = pymongo.Connection()
col = con.test.row


def get_source(url):
    return [ unicode(re.sub("<.*?>","",line),"sjis") 
             for line in urllib.urlopen(url).readlines()]

def create_index():
    lines = get_source("http://www.aozora.gr.jp/cards/000035/files/1567_14913.html"
)
    for n, line in enumerate(lines):
        words = []

        # 単語に分割 変な文字列渡すとSWIGでエラーが出るっぽい
        try:
            node = mecab.parseToNode( line.encode("utf-8") )
            words = get_words( node )
        except :
            pass
        col.insert( {"at":n, "text":line,"words":words })
    col.ensure_index("words")

def search(word):
    for i in col.find({"words":word}):
        print i["at"],i["text"]

def get_words(node):
    if not node.next:
        return [node.surface]
    return [node.surface] + get_words(node.next)

if __name__ == '__main__':
    create_index() # 一回だけ実行
    search("セリヌンティウス")
> python text-search.py
23  メロスは激怒した。必ず、かの邪智暴虐(じゃちぼうぎゃく)の王を除かなければならぬと決意した。メロスには政治がわからぬ。メロスは、村の牧人である。笛を吹き、羊と遊んで暮して来た。けれども邪悪に対しては、人一倍に敏感であった。きょう未明メロスは村を出発し、野を越え山越え、十里はなれた此(こ)のシラクスの市にやって来た。メロスには父も、母も無い。女房も無い。十六の、内気な妹と二人暮しだ。この妹は、村の或る律気な一牧人を、近々、花婿(はなむこ)として迎える事になっていた。結婚式も間近かなのである。メロスは、それゆえ、花嫁の衣裳やら祝宴の御馳走やらを買いに、はるばる市にやって来たのだ。先ず、その品々を買い集め、それから都の大路をぶらぶら歩いた。メロスには竹馬の友があった。セリヌンティウスである。今は此のシラクスの市で、石工をしている。その友を、これから訪ねてみるつもりなのだ。久しく逢わなかったのだから、訪ねて行くのが楽しみである。歩いているうちにメロスは、まちの様子を怪しく思った。ひっそりしている。もう既に日も落ちて、まちの暗いのは当りまえだが、けれども、なんだか、夜のせいばかりでは無く、市全体が、やけに寂しい。のんきなメロスも、だんだん不安になって来た。路で逢った若い衆をつかまえて、何かあったのか、二年まえに此の市に来たときは、夜でも皆が歌をうたって、まちは賑やかであった筈(はず)だが、と質問した。若い衆は、首を振って答えなかった。しばらく歩いて老爺(ろうや)に逢い、こんどはもっと、語勢を強くして質問した。老爺は答えなかった。メロスは両手で老爺のからだをゆすぶって質問を重ねた。老爺は、あたりをはばかる低声で、わずか答えた。

42 「そうです。帰って来るのです。」メロスは必死で言い張った。「私は約束を守ります。私を、三日間だけ許して下さい。妹が、私の帰りを待っているのだ。そんなに私を信じられないならば、よろしい、この市にセリヌンティウスという石工がいます。私の無二の友人だ。あれを、人質としてここに置いて行こう。私が逃げてしまって、三日目の日暮まで、ここに帰って来なかったら、あの友人を絞め殺して下さい。たのむ、そうして下さい。」

48  竹馬の友、セリヌンティウスは、深夜、王城に召された。暴君ディオニスの面前で、佳(よ)き友と佳き友は、二年ぶりで相逢うた。メロスは、友に一切の事情を語った。セリヌンティウスは無言で首肯(うなず)き、メロスをひしと抱きしめた。友と友の間は、それでよかった。セリヌンティウスは、縄打たれた。メロスは、すぐに出発した。初夏、満天の星である。

75 「フィロストラトスでございます。貴方のお友達セリヌンティウス様の弟子でございます。」その若い石工も、メロスの後について走りながら叫んだ。「もう、駄目でございます。むだでございます。走るのは、やめて下さい。もう、あの方(かた)をお助けになることは出来ません。」

83 「待て。その人を殺してはならぬ。メロスが帰って来た。約束のとおり、いま、帰って来た。」と大声で刑場の群衆にむかって叫んだつもりであったが、喉(のど)がつぶれて嗄(しわが)れた声が幽(かす)かに出たばかり、群衆は、ひとりとして彼の到着に気がつかない。すでに磔の柱が高々と立てられ、縄を打たれたセリヌンティウスは、徐々に釣り上げられてゆく。メロスはそれを目撃して最後の勇、先刻、濁流を泳いだように群衆を掻きわけ、掻きわけ、

84 「私だ、刑吏! 殺されるのは、私だ。メロスだ。彼を人質にした私は、ここにいる!」と、かすれた声で精一ぱいに叫びながら、ついに磔台に昇り、釣り上げられてゆく友の両足に、齧(かじ)りついた。群衆は、どよめいた。あっぱれ。ゆるせ、と口々にわめいた。セリヌンティウスの縄は、ほどかれたのである。

85 「セリヌンティウス。」メロスは眼に涙を浮べて言った。「私を殴れ。ちから一ぱいに頬を殴れ。私は、途中で一度、悪い夢を見た。君が若(も)し私を殴ってくれなかったら、私は君と抱擁する資格さえ無いのだ。殴れ。」

86  セリヌンティウスは、すべてを察した様子で首肯(うなず)き、刑場一ぱいに鳴り響くほど音高くメロスの右頬を殴った。殴ってから優しく微笑(ほほえ)み、

88  メロスは腕に唸(うな)りをつけてセリヌンティウスの頬を殴った。

全文検索エンジンとの比較

このようにMongoDBは、検索機能を簡単にするおもしろい機能を持っていますが、上記の通り、これは専用の全文検索エンジンではありません。

専用のエンジンは以下のようなこともできます。

組み込みのstemming機能
クエリにマッチする語句のランキング (MongoDBでもできるかもしれませんが、ユーザがそういうコードを書く必要があります)
一括でのインデックスの構築 (bulk index building)

一括でのインデックスの構築は、インデックスの作成を速くしますが、リアルタイムではないという欠点もあります。MongoDBは、リアルタイムでの検索に特に向いています。伝統的なツールは、このような使い方はあまり得意ではありません。


適材適所でやろう。MongoDB環境があるとき、気軽に実装できる、という程度。