読者です 読者をやめる 読者になる 読者になる

FutureInsight.info

AI、ビッグデータ、ライフサイエンス、テクノロジービッグプレイヤーの動向、これからの働き方などの「未来」に注目して考察するブログです。

全文検索エンジンLuxのboost-pythonを用いたお手軽Pythonバインディング

上のエントリーで後日クローラ、インデクサ部分を公開すると書きましたが、クローラ部分がかなり時間がかかりそうなので、インデクサを含むPythonバインディング部分を合わせたLuxのPythonバインディングを公開しておきます。

ソースコード

#include <lux/search.h>
#include <lux/index.h>
#include <iostream>
#include <string>
#include <time.h>
#include <sys/time.h>

#include <boost/python.hpp>

using namespace std;

class Searcher
{

public:

  Searcher()
  {
    engine = NULL;
  }

  virtual ~Searcher()
  {
    if(engine!=NULL) {
      delete engine;
    }
  }

  int set_service(string service)
  {
    Lux::Config::Document doc_config;
    if (!Lux::DocumentConfigParser::parse(service, doc_config)) {
      return 0;
    }
    
    engine = new Lux::Engine(doc_config);
    if (engine->open(service, Lux::DB_RDONLY)) {
      return 0;
    }
    return 1;
  }

  int set_keys(PyObject* py_obj)
  {
    if(PyList_Check(py_obj)) {
      PyListObject* py_keys = (PyListObject*)py_obj;
      if (py_keys) {
	int num_keys = PyList_GET_SIZE(py_keys);
	keys.reserve(num_keys);
	for (int i=0; i<num_keys; i++) {
	  PyObject* py_key = PyList_GET_ITEM(py_keys, i);
	  if (PyString_Check(py_key)) {
	    keys.push_back(PyString_AS_STRING(py_key));
	  } else {
	    return 0;
	  }
	}
      }
    } else {
      return 0;
    }
    return 1;
  }

  PyObject* get_rs(string query, int pages)
  {
    double t1, t2;
    t1 = gettimeofday_sec();
    
    // create search condition
    Lux::SortCondition scond(Lux::SORT_SCORE, Lux::DESC);
    Lux::Paging paging(pages);
    Lux::Condition cond(scond, paging);
    Lux::Searcher searcher(*engine);
    Lux::ResultSet rs = searcher.search(query, cond);

    t2 = gettimeofday_sec();
    
    PyObject* pt(PyDict_New());
    
    // create result py object
    PyDict_SetItemString(pt, "total_hits", PyInt_FromLong(rs.get_total_num()));
    PyDict_SetItemString(pt, "base", PyInt_FromLong(rs.get_base()));
    PyDict_SetItemString(pt, "num", PyInt_FromLong(rs.get_num()));
    PyDict_SetItemString(pt, "time", PyFloat_FromDouble(t2-t1));  
    PyObject* prs(PyTuple_New(rs.get_num()));
    rs.init_iter();
    uint32_t index = 0;

    while (rs.has_next()) {    
      Lux::Result r = rs.get_next();
      PyObject* pr(PyDict_New());
      PyDict_SetItemString(pr, "id", PyString_FromString(r.get_id().c_str()));
      PyDict_SetItemString(pr, "score", PyInt_FromLong(r.get_score()));
      
      for (vector<string>::iterator i=keys.begin();i!=keys.end();++i) {
	PyDict_SetItemString(pr, (*i).c_str(),
			     PyString_FromString(r.get(*i).c_str()));	
      }
      
      PyTuple_SetItem(prs, index, pr);
      index++;
    }
  
    PyDict_SetItemString(pt, "result_set", prs);
    return pt;
  }
  
protected:

  Lux::Engine* engine;
  vector<string> keys;

  double gettimeofday_sec()
  {
    struct timeval tv; 
    gettimeofday(&tv, NULL);
    return tv.tv_sec + (double)tv.tv_usec*1e-6;
  }

};


class Indexer
{

public:

  Indexer()
  {
    engine = NULL;
    indexer = NULL;
  }

  virtual ~Indexer()
  {
    if(engine!=NULL) {
      delete engine;
    }

    if(indexer!=NULL){
      delete indexer;
    }
  }

  int set_service(string service)
  {
    Lux::Config::Document doc_config;
    Lux::DocumentConfigParser::parse(service, doc_config);
    
    // search for the query
    engine = new Lux::Engine(doc_config);
    if (!engine->open(service, Lux::DB_CREAT))
    {
      return 0;
    }
    indexer = new Lux::Indexer(*engine);    
    return 1;
  }

  int add(string id, PyObject* py_obj)
  {    
    if(PyDict_Check(py_obj)) {
      PyListObject* keys = (PyListObject*) PyDict_Keys(py_obj);
      if (keys) {
	PyListObject* values = (PyListObject*) PyDict_Values(py_obj);
	if (values) {
	  int num_keys = PyList_GET_SIZE(keys);
	  int num_values = PyList_GET_SIZE(values);
	  if (num_keys == num_values) {
	    Lux::Document* doc = new Lux::Document(id);
	    for (int i=0; i<num_keys; i++) {
	      PyObject* key = PyList_GET_ITEM(keys, i);
	      if (PyString_Check(key)) {
		PyObject* value = PyList_GET_ITEM(values, i);
		doc->add(Lux::Field::create(PyString_AS_STRING(key), 
					    PyString_AS_STRING(value)));
	      }
	    }
	    indexer->add(doc);
	    delete doc;
	  } else {
	    return 0;
	  }
	}
      }
    } else {
      return 0;
    }
    return 1;
  }

protected:

  Lux::Engine* engine;
  Lux::Indexer* indexer;
};

BOOST_PYTHON_MODULE(lux_python)
{
  using namespace boost::python;

  class_<Searcher>("Searcher")
    .def("set_service", &Searcher::set_service)
    .def("set_keys", &Searcher::set_keys)
    .def("search", &Searcher::get_rs)
    ;

  class_<Indexer>("Indexer")
    .def("set_service", &Indexer::set_service)
    .def("add", &Indexer::add)
    ;
}

このソースコードを以下のようにコンパイルするとlux_python.soが作成されます。

g++ -I/usr/include/python2.5 -shared -o lux_python.so $^ -lboost_python -llux -fPIC

使い方

インデックス部分(Indexer)の使い方です。実行するディレクトリに上のセクションで生成したlux_python.soが存在するとします。データはluxのexampleに付属するpostsです。クローラで取得したデータはインデックス部分(Indexer)を用いてインデックス作成を行います。

import lux_python
li = lux_python.Indexer()
li.set_service("blogs")

file_obj = open("posts", "r")
index = 0
for i in file_obj.readlines():
    key_value = {}
    line = i.split("\x18")
    id = line[0]
    key_value["created_at"] = line[1]
    key_value["url"] = line[2]
    key_value["title"] = line[3]
    if(li.add(id, key_value)):
        print "%s item indexed."%index
    index = index + 1

検索部分の使い方です。実行するディレクトリに上のセクションで生成したlux_python.soが存在するとします。データはluxのexampleに付属する上のインデックス部分(Indexer)で作成したblogsです。具体的なWebアプリでの使い方は上のエントリーを参照してください。

import lux_python
lse = lux_python.Searcher()
lse.set_service("blogs")
lse.set_keys(["title", "created_at", "url"])
print lse.search("google", 5)

まとめ

基本的な使い方はこのboost-pythonを用いたお手軽Pythonバインディングでカバーできると思います。どれくらい需要があるか未知数ですがもしよろしければお使い下さい。