Full Text search engine dengan Zend Search Lucene
Zend Framework menyediakan sebuah fitur Zend_Search_Lucene yang berguna untuk pencarian Full Text pada dokumen. Fitur ini merupakan adopsi dari project Apache Lucene, merupakan project full-featured full text search engine yang dibangun dalam bahasa JAVA.
Secara garis besar, dalam membuat search engine untuk website, yang perlu dilakukan adalah :
- menginstall Zend Framework.
- melakukan pengindeksan terhadap dokumen-dokumen yang akan digunakan sebagai “kamus data” pencarian.
- melakukan pencarian dalam index yang telah terbentuk.
Menginstal Zend Framework
Untuk menginstall Zend Framework telah saya tulis di posting saya sebelumnya.
Melakukan Pengindeksan
Indeks disini adalah semacam kamus pencarian bagi search engine kita, yaitu tempat dimana dokumen-dokumen diekstrak informasi didalamnya (mis : url, title, isi dokumen itu sendiri, dll) kemudian diurutkan dan disimpan menjadi satu kesatuan yang terindeks.
Untuk melakukan pengindeksan website, kita harus membaca semua halaman yang ada di website tersebut, metodenya adalah kita membaca halaman HTML (frontpage), mengambil informasi (isi dan judul) dan membaca semua link yang ada didalamnya. Kemudian dari link yang terbaca, kita simpan dalam array dan dilakukan pembacaan halaman HTML berdasarkan link yang ada di dalam array. Proses crawling website ini kita batasi pada domain kita saja, jadi proses akan berjalan selama url yang akan dibaca masih dalam domain tertentu mis : wordpress.com maka kita akan melakukan proses crawilng pada wordpress.com beserta hosting-hosting didalamnya.
berikut ini contoh coding crawling website dalam sebuah domain. ada beberapa kelas Zend Framework yang digunakan, antaralain :
- Zend/Search/Lucene.php -> untuk melakukan pengindeksan search Lucene
- Zend/Http/Client.php -> untuk melakukan pembacaan dokumen html melalui http request
- Zend/Log.php -> melakukan proses LOG proses yang dilakukan dan menyimpan di dalam FILE
<?php
/*
@package searching **
@author nugrahaputra@gmail.com
@search controller
@filename index.php
*/
require_once('Zend/Search/Lucene.php');
require_once('Zend/Http/Client.php');
require_once('Zend/Log.php');
require_once('Zend/Log/Writer/Stream.php');
require_once('Zend/Uri.php');
$place = strip_tags($_REQUEST['p']);
define('HOST','http://'.$_SERVER['HTTP_HOST'].'/');
ini_set('display_errors',true);
ini_set('max_execution_time','10000');
if ($place=='situs'){
$start = 'http://www.yourdomain.ac.id';
$match = 'yourdomain.ac.id';
$excp = 'yourdomain';
$EXCEPTION = array (
'http://exception.yourdomain.ac.id/');
define ('APP_ROOT',realpath(dirname(__FILE__)));
define ('START_URI', $start);
define ('MATCH_URI', $match);
$log = new Zend_Log(new Zend_Log_Writer_Stream(APP_ROOT . DIRECTORY_SEPARATOR . 'log' . DIRECTORY_SEPARATOR . 'crawler.log'));
$log->info('Crawler Start');
echo ('Crawler Start
');
$client = new Zend_Http_Client();
$client->setConfig(array('timeout' => 30));
$indexpath = APP_ROOT . DIRECTORY_SEPARATOR . $place ;
try{
$index = Zend_Search_Lucene::open($indexpath);
$log->info("opened index $indexpath");
echo ("opened index $indexpath
");
}catch(Zend_Search_Lucene_Exception $e){
try{
$index = Zend_Search_Lucene::create($indexpath);
$log->info("opened index $indexpath");
// echo("opened index $indexpath
");
}catch (Zend_Search_Lucene_Exception $e){
$log->error("Failed opening and creating");
// echo("Failed opening and creating
");
$log->error($e->getMessage());
echo($e->getMessage());
echo "unable to opening and creating index {$e->getMessage()}
";
exit(1);
}
}
$targets = array(START_URI);
for($i = 0; $i < count($targets); $i++){
try{
if (Zend_Uri::check($targets[$i])&&(! in_array($targets[$i],$EXCEPTION))&&(strpos ($targets[$i],$excp)===false)){
$client->setUri($targets[$i]);
}else{
$client->setUri('http://www.yourdomain.ac.id');
}
}catch(Zend_Uri_Exception $e){
$log->info("{$targets[$i]} not valid");
$client->setUri('http://www.yourdomain.ac.id');
}
try{
$response = $client->request();
}catch (Zend_Http_Client_Exception $e){
// $response = $client->request();
$log->info("{$targets[$i]} cannot connect");
echo ("{$targets[$i]} cannot connect");
}
if ($response->isSuccessful()){
$body = $response->getBody();
//$title = $response->getTitle();
// echo $title;
$log->info("fetched" . strlen($body) . "bytes from {$targets[$i]}" );
// echo("fetched" . strlen($body) . "bytes from {$targets[$i]}
" );
$body_checksum = md5($body);
try{
$hits = $index->find('url:'.$targets[$i]);
}catch (Zend_Search_Lucene_Search_QueryParserException $e){
$log->info("fail");
}
$matched = false;
foreach($hits as $hit){
if ($hit->md5 == $body_checksum){
if($matched == true)$index->delete($hit->id);
$matched = true;
}else{
$log->info($targets[$i] . " out of date and reindexing ");
// $index->delete($hit);
}
}
if ($matched){
$log->info($targets[$i] . " is up to date, skipping ");
continue;
}
echo $targets[$i]."
";
//$doc = new Zend_Search_Lucene_Document();
$doc = Zend_Search_Lucene_Document_Html::loadHTML($body,true);
$doc->addField(Zend_Search_Lucene_Field::UnIndexed('url',$targets[$i]));
$doc->addField(Zend_Search_Lucene_Field::UnIndexed('md5',$body_checksum));
// $doc->addField(Zend_Search_Lucene_Field::Text('title',$doc->getTitle()));
$doc->addField(Zend_Search_Lucene_Field::Text('content',$body));
$index->addDocument($doc);
$log->info("indexed {$targets[$i]}");
echo("indexed {$targets[$i]}
");
$links = $doc->getLinks();
foreach ($links as $link){
if ((strpos($link, MATCH_URI) !== false )&& //cek apakah link sudah tersimpan
(! in_array($link,$targets)&&(strpos($link,$excp)>=0))) $targets[] = $link;
//echo $targets[$i].$link."
";
}
} else {
//$log->warning("requesting $url returned HTTP " . $response->getStatus());
echo("requesting $url returned HTTP " . $response->getStatus()."
");
}
}
$log->info("iterated over " . count($targets). " documents");
//echo("iterated over " . count($targets). " documents
");
$log->info("optimizing index");
//echo("optimizing index
");
//$index->optimize();
//$index->commit();
$log->info("Done index contains " . $index->numDocs() . " documents");
echo("Done index contains " . $index->numDocs() . " documents
");
$log->info("Crawler shutting down");
echo("Crawler shutting down
");
?>
code ini dieksekusi secara berkala untuk melakukan update terhadap index dokumen website.
Pencarian dalam Index dan Manajemen Hasil Pencarian
Untuk melakukan pencarian dalam index yang telah terbentuk, dan melakukan manajemen hasil pencarian, berikut ini codingnya
<?php
/*
@package searching **
@author nugrahaputra@gmail.com
@search controller
@filename search.controller.php
*/
require_once('Zend/Search/Lucene.php');
require_once('Zend/Log.php');
require_once('Zend/Log/Writer/Stream.php');
class search_controller{
var $result;
var $indexPath;
var $index;
var $query;
var $real_query;
var $hits = array();
//fungsi konstuktor untuk membuka index yang telah tersimpan
function __construct ($index_path){
$this->indexPath = $index_path;
try {
$this->index = Zend_Search_Lucene::open($index_path);
}catch(Zend_Search_Lucene_Exception $e){
echo 'gagal membuka index '.$index_path;
}
}
//fungsi untuk "membersihkan" hasil parsing dokumen HTML dari tag-tag script, style, dan special character
function filter($content){
$buffer = strip_tags( $content,"<script> <style> <title> "); //filter tag yang diijinkan
$regex_style = '/<\s*style.+?<\s*\/\s*style.*?>/si';//hilangkan style
$buffer = preg_replace($regex_style, ' ' , $buffer);
$regex_script = '/<\s*script.+?<\s*\/\s*script.*?>/si';//hilangkan script
$buffer = preg_replace($regex_script, ' ' , $buffer);
$buffer = ereg_replace(" | & | " | < | > "," ",$buffer);
return $buffer;
}
//fungsi untuk mencari keyword dalam index
function find ($keyword){
$this->real_query=strtolower($keyword);
$this->query=Zend_Search_Lucene_Search_QueryParser::parse($keyword);
Zend_Search_Lucene::setResultSetLimit(100);
$this->result=$this->index->find($this->query);
$i = 0;
foreach ($this->result as $item){
$this->hits[$i]["id"] = $item->id;
$this->hits[$i]["title"] =$this->query->highlightMatches($item->title);
$this->hits[$i]["url"] = $item->url;
//memberikan "HIGHLIGHT" pada pencarian yang ditemukan
$this->hits[$i]["content"] =$this->query->highlightMatches($this->filter($item->content));
$this->hits[$i]["score"] = $item->score;
$i++;
}
}
//hitung hasil pencarian
function get_num_result(){
return count($this->result);
}
//memecah2 hasil menjadi beberapa halaman @halaman = $offset hasil dan ditampilkan halaman ke $page
function get_result ($offset, $page, $keyword){
$temp = array();
//$this->find($keyword);
$startpos = $page * $offset;
$endpos = $startpos+$offset;
for ($i=$startpos;$i<$endpos;$i++){
if(isset($this->hits[$i]))
$temp[]=$this->hits[$i];
else
break;
}
return $temp;
}
}
?>
class diatas digunakan untuk memproses pencarian dalam index dan manajemen hasil pencarian.
Zend Lucene memberikan fitur pencarian menggunakan query parser, dimana dengan query parser kita dapat meng-customize hasil pencarian yang kita inginkan, misalnya dengan menambahkan operator AND, dsb. Untuk lebih lengkapnya baca manual nya Zend.
Berikut ini contoh penggunaan class searching untuk melakukan pencarian
$print='';
foreach($tes as $item) {
$print .='
<blockquote><strong><a href="'.$item[url].'" >';
$print .= $item[title].$item[id];
$print .= '</a></strong>';
$print .=$item[content];
$print .='</blockquote>
';
}
echo $print;
OK, selamat mencoba

aku kopi ya….thanks sebelumnya
yang diatas kok gak di buat class sekalian?
fatur
September 1, 2009 pada 9:24 am
yg diatas ??? crawlernya kah??? rencananya gitu,, tapi blom sempet..:)
nugrahaputra
September 4, 2009 pada 2:23 am
[...] hasil pencarian ke dalam halaman By mfathur ArtikelĀ FullText Search yang ditulis NugrahaPutra, mengajari saya bagaimana cara membagi hasil pencarian dalam beberapa [...]
Membagi hasil pencarian ke dalam halaman « Jalan Lain
September 3, 2009 pada 4:39 am
mas.,..mau tanya mas..,
pembobotannya itu pake pembobotan apa sih mas? trus kalau mau diganti pembobotan default/aslinya gimana mas?
mohoon bantuannya mas…
terimah kasih
leonardosiagian
Januari 19, 2012 pada 7:51 am
itu sudah bawaan dari Zend lucene dalam indexingnya, kalau mau ganti pembobotan. kalau mau ganti metode pembobotannya mungkin tidak perlu menggunakan engine Zend.
nugrahaputra
Februari 1, 2012 pada 2:27 am