Hyperestraierzでmysqlのインデックス作成からのー

さくらVPSにHyperestraierを設置。

前回に引き続きHyperestraierネタ。

前回やったのは特定フォルダ配下の静的コンテンツや、
cakephpのURLパスにクローラーを流して、インデックスを作成する方法。

今回はmysqlに接続するperlを作成して、定期的にインデックスを作成する。


http://blog.livedoor.jp/techblog/archives/64637186.html

を参考にして操作を行うも色々ひっかかったので、メモ。

インストールメモ

libiconvのインストール

sudo yum -y install libiconv-devel

zlibのインストール

sudo yum -y install zlib-devel

QDBMのインストール

wget http://qdbm.sourceforge.net/qdbm-1.8.77.tar.gz
gunzip ./qdbm-1.8.77.tar.gz
tar -xf ./qdbm-1.8.77.tar
rm -f ./qdbm-1.8.77.tar
cd ./qdbm-1.8.77/
./configure --enable-zlib
make
sudo make install

HyperEstraierのインストール

wget http://hyperestraier.sourceforge.net/hyperestraier-1.4.13.tar.gz
gunzip ./hyperestraier-1.4.13.tar.gz
tar -xf ./hyperestraier-1.4.13.tar
rm -f ./hyperestraier-1.4.13.tar
cd ./hyperestraier-1.4.13
./configure
make
sudo make install

インストール後の確認

which estcmd

これで
/usr/local/bin/estcmd
と返って来ればインストールが正常にいった証拠。


DBサーバーにテーブルを作成

今回はWEBサーバーとDBサーバーが別。

create table entry (
  id int(10) unsigned auto_increment not null,
  title varchar(255) not null,
  body text not null,
  created_on datetime not null DEFAULT '0000-00-00 00:00:00',
  updated_on timestamp(14) not null,
  PRIMARY KEY (id),
  KEY title (title),
  KEY created_on (created_on)
) Type=InnoDB;

INSERT INTO entry (title, body, created_on) values ('greeting1', 'おはようございます。livedoor 開発 blog です。', now());
INSERT INTO entry (title, body, created_on) values ('greeting2', 'こんにちは。livedoor 開発 blog です。', now());
INSERT INTO entry (title, body, created_on) values ('greeting3', 'こんばんは。livedoor 開発 blog です。', now());

接続情報を更新。

indexer.plを設置

場所はどこでも良い。とりあえず/var/www/html/test/indexer.plと設置。
chmod 777 indexer.plとして実行権限を付与。
※ちなみにPHPのバッチは公式には存在しなかったのだけど、
http://page2.xrea.jp/#Services_HyperEstraier
で作っている人もいる様子。なれたら挑戦してみようー。

#!/usr/bin/perl
##Livedoorブログのindexer.plにコメントをいれただけ
use strict;
use DBI;
use Readonly;
use Encode;
use DateTime::Format::MySQL;

#データベースの接続先を指定
Readonly my $datasources => [ 'dbi:mysql:test:182.48.40.**', 'testuser', '*****' ];

#ノードを作成左記を指定
Readonly my $base_dir     => "/tmp";
Readonly my $node_path    => "$base_dir/casket/";

##estcmdのインストール先を指定
Readonly my $ESTCMD       => "/usr/local/bin/estcmd";

##文書ドラフトの形式を指定
Readonly my $DRAFT_FORMAT => <<'END_DRAFT'
@uri=http://localhost/casket/detail.cgi?id=%d
@title=%s
@cdata=%s+09:00

%s
END_DRAFT
    ;

## インデックスを作成する
## ESTCMD create インデックスのパス [インデックスの作成]
system( ESTCMD, "create", $node_path )
    unless -d $node_path;

## 文書ドラフト形式のファイルを登録する
## ESTCMD put インデックスのパス optionの-clは上書きされた文書の整理を行う
my $entries = get_entries();
for my $entry (@$entries) {
    do_estcmd("put", {
        entry => $entry,
        opts  => [qw(-cl)],
    }, \&_mk_draft);
}
#extkeysはインデックス内の文書キーワードを抽出したデータベースを作成する
#オプションの-umは、キーワード抽出の際に形態素解析
do_estcmd("extkeys", { opts => [qw(-um)] });

##ESTCMDのコマンドを生成する。>ESTCMD、サブコマンド(put,out,edit)、オプション、ノードパス
sub build_estcmd {
    my ( $subcmd, $opts ) = @_;
    my $cmd = join( ' ', $ESTCMD, $subcmd, @$opts, $node_path );
    return $cmd;
}
##ESTCMDのコマンドを実行する
sub do_estcmd {
    my ($subcmd, $params, $callback) = @_;
    my $command = build_estcmd($subcmd, $params->{opts});
    open( CMD, "| $command" ) || die $!;
    print CMD $callback->($params->{entry}) if $callback;
    close( CMD );
}
##文書ドラフト形式を作成する
sub _mk_draft {
    #shift関数は配列変数から一番若いインデックスの要素を取り出す
    my $entry = shift;
    my $dt = DateTime::Format::MySQL->parse_datetime( $entry->{created_on} );
    #ヘッダーで指定したドラフトフォーマットに変数を入れる
    #1.ID 2.TITLE 3.時間 4.本文
    my $input = sprintf( $DRAFT_FORMAT,
        $entry->{id}, $entry->{title}, $dt, $entry->{body} );
    return $input;
}
##SQLから記事を取得する(perlのdbiプログラミングを参照)
##connect->prepare(準備)->execute(実行)->fetchrow_array->finish(開放)->disconnectの順番行を取り出す
sub get_entries {
    my $dbh = DBI->connect( @{$datasources},
        { RootClass => "DBIx::ContextualFetch" } )
        or die $DBI::errstr;
    my $sql   = qq{SELECT * FROM entry};
    my $sth   = $dbh->prepare($sql);
    $sth->execute;
    my $entries = $sth->fetchall_hash;
    $sth->finish;
    $dbh->disconnect;
    return $entries;
}

1;

__END__

cpanとの戦い。

./indexer.plと実行するとやっぱりライブラリが足りずに都度追加。めんどくさー。

(エラー)Can't locate Readonly.pm in @INC (@INC contains: /usr/lib64/perl5/site_perl/5.8.8/x86_64-linux-thread-m
(対処)cpan install Readonly

(エラー)Can't locate DateTime/Format/MySQL.pm in @INC (@IN
(対処)cpan force install DateTime::Format::MySQL
(ここだけなぜか一度#cpanで cpan>force install DateTime::Format::MySQLとやるといけた)

(エラー)Can't locate parent.pm in @INC (@
(対処)cpan install parent

(エラー)Can't locate Class/Load.pm
(対処)cpan install Class::Load

(エラー)Can't locate DBD/mysql.pm
(対処)cpan install DBD::mysql
(yumでmysql-serverをインストールした後に実行すべし)

(エラー)Can't locate DBIx/ContextualFetch.pm
cpan install DBIx::ContextualFetch

(エラー)そのようなファイルやディレクトリはありません at ./indexer.pl line 44.
indexer.plのnode_pathに/tmp/casketを作ってやる。

実行できた

/usr/local/bin/estcmd: INFO: status: name=/tmp/casket/ dnum=0 wnum=0 fsiz=6899176 crnum=0 csiz=0 dknum=0
/usr/local/bin/estcmd: INFO: 1 (http://localhost/casket/detail.cgi?id=1): registered
/usr/local/bin/estcmd: INFO: flushing index words: name=/tmp/casket/ dnum=1 wnum=1 fsiz=6899706 crnum=15 csiz=1008 dknum=0
/usr/local/bin/estcmd: INFO: closing: name=/tmp/casket/ dnum=1 wnum=16 fsiz=6899955 crnum=0 csiz=0 dknum=0
/usr/local/bin/estcmd: INFO: status: name=/tmp/casket/ dnum=1 wnum=16 fsiz=6900126 crnum=0 csiz=0 dknum=0
/usr/local/bin/estcmd: INFO: 2 (http://localhost/casket/detail.cgi?id=2): registered
/usr/local/bin/estcmd: INFO: flushing index words: name=/tmp/casket/ dnum=2 wnum=16 fsiz=6900431 crnum=12 csiz=805 dknum=0
/usr/local/bin/estcmd: INFO: closing: name=/tmp/casket/ dnum=2 wnum=21 fsiz=6900452 crnum=0 csiz=0 dknum=0
/usr/local/bin/estcmd: INFO: status: name=/tmp/casket/ dnum=2 wnum=21 fsiz=6900452 crnum=0 csiz=0 dknum=0
/usr/local/bin/estcmd: INFO: 3 (http://localhost/casket/detail.cgi?id=3): registered
/usr/local/bin/estcmd: INFO: flushing index words: name=/tmp/casket/ dnum=3 wnum=21 fsiz=6900753 crnum=12 csiz=805 dknum=0
/usr/local/bin/estcmd: INFO: closing: name=/tmp/casket/ dnum=3 wnum=24 fsiz=6900789 crnum=0 csiz=0 dknum=0
/usr/local/bin/estcmd: INFO: status: name=/tmp/casket/ dnum=3 wnum=24 fsiz=6900789 crnum=0 csiz=0 dknum=0
/usr/local/bin/estcmd: INFO: 1 (http://localhost/casket/detail.cgi?id=1): extracted
/usr/local/bin/estcmd: INFO: 2 (http://localhost/casket/detail.cgi?id=2): extracted
/usr/local/bin/estcmd: INFO: 3 (http://localhost/casket/detail.cgi?id=3): extracted
/usr/local/bin/estcmd: INFO: flushing auxiliary keywords: name=/tmp/casket/ dnum=3 wnum=24 fsiz=6901175 crnum=7 csiz=563 dknum=0
/usr/local/bin/estcmd: INFO: closing: name=/tmp/casket/ dnum=3 wnum=24 fsiz=6901385 crnum=0 csiz=0 dknum=0
/usr/local/bin/estcmd: INFO: finished successfully: elapsed time: 0h 0m 0s

検索方法

/usr/local/bin/estcmd search -ic utf-8 -vh /tmp/casket こんにちは | nkf -e

検索用CGIの設置

最初estseek.cgiにアクセスしたときにPremature end of script headers
とかってエラーがapacheに出ていた。
一度windowsに持ってきたのがいけなかったっぽくおとなしく下記のように
直接コピーで対処。

cp  /usr/local/libexec/estseek.cgi /var/www/html/hyperestraier/htdocs/estseek.cgi
cp /usr/local/libexec/estproxy.cgi /var/www/html/hyperestraier/htdocs/estproxy.cgi
cp /usr/local/share/hyperestraier/estproxy.conf /var/www/html/hyperestraier/htdocs/estproxy.conf


cp  /usr/local/share/hyperestraier/locale/ja/estseek.tmpl /var/www/html/hyperestraier/tmpl/estseek.tmpl
cp  /usr/local/share/hyperestraier/locale/ja/estseek.top /var/www/html/hyperestraier/tmpl/estseek.top
cp  /usr/local/share/hyperestraier/locale/ja/estseek.help /var/www/html/hyperestraier/tmpl/estseek.help

vi  /var/www/html/hyperestraier/htdocs/estseek.conf

estseek.confの中身(livedoorブログのまんまを使用させてもらいました)

indexname: /tmp/casket
tmplfile: /home/www/hyperestraier/tmpl/estseek.tmpl
topfile:  /home/www/hyperestraier/tmpl/estseek.top
helpfile: /home/www/hyperestraier/tmpl/estseek.help
lockindex: true
pseudoindex:
showlreal: false
deftitle: 超迷子: 共同体的全文検索系
formtype: web
perpage: 10 100 10
attrselect: false
attrwidth: 80
showscore: true
snipwwidth: 480
sniphwidth: 96
snipawidth: 96
condgstep: 2
dotfidf: true
scancheck: 3
dispproxy: ./estproxy.cgi
phraseform: 2
candetail: true
candir: false
auxmin: 32
smlrvnum: 32
smlrtune: 16 1024 4096
clipview: 2
clipweight: none
relkeynum: 0
spcache:
wildmax: 256
qxpndcmd:
logfile:
logformat: {time}\t{REMOTE_ADDR}:{REMOTE_PORT}\t{cond}\t{hnum}\n

CGI設置の罠

cgiを/var/www/cgi-bin/以外のところに置いてアクセスするためには
apacheの設定が必要ということを知らず、
Forbidden
You don't have permission to access test.cgi on this server.
といわれ続けて、ファイルの権限??と迷ってしまった。

この状態のときにapacheのログは下記のようなエラーが出ている。
Apacheのerror-log
Options ExecCGI is off in this directory

解決するには

# The Options directive is both complicated and important.  Please see
# http://httpd.apache.org/docs/2.2/mod/core.html#options
# for more information.
#
    Options Indexes FollowSymLinks

とあるところに

#Options Indexes FollowSymLinks
Options FollowSymLinks SymLinksifOwnerMatch ExecCGI

こんな感じでExecCGIを足したら解決。
SymLinksifOwnerMatch 所有者が一致する場合のみシンボリックリンクを有効にする
ExecCGI CGIを有効にする



あと、CGI実行させるための基本的な設定は下記。

AddHandler cgi-script .cgi  <=AddHandlerにCGIを認識させる
AddHandler send-as-is asis
AddHandler type-map var
ScriptAlias /cgi-test/ /var/www/cgi-bin/ <=http://aaaa/cgi-bin/へのアクセスをすべて/var/www/cgi-binにアクセスさせる。

検索窓から色々検索してみる

http://****/hyperestraier/htdocs/estseek.cgi
にアクセスする。

検索結果をPHPXMLにして返す

http//AAAA/return_xml.php?search_word=テスト&max_count=15
こんな感じでHTTP経由でxmlを返すようにしてみた。

<?php
if(isset($_GET['search_word'])) {
	if(isset($_GET['max_count'])) {
		$max_count = $_GET['max_count'];
	}else{
		$max_count = 10;
	}
	$search_word = $_GET['search_word'];
	$results = shell_exec('/usr/local/bin/estcmd search -ic utf-8 -vx -max '.$max_count.' /tmp/casket '.$search_words.' | nkf -e');
	$results = mb_convert_encoding($results, "UTF-8", 'EUC-JP,SJIS,ASCII,JIS,UTF-8,EUC');
	echo $results;
}else{
	echo '検索文字を設定してください';
}
?>