Полнотекстовый поиск с Xapian и PHP
Метки: веб-разработка, php, xapian, mysql
Fulltext searches with Xapian and PHP
| ← Тег address | Как быть счастливым на работе. Короткое руководство → |
Иногда MySQL просто не достаточно быстрый. Особенно когда дело доходит до полнотекстового поиска. Все индексы должны быть корректно расставлены, а если мы берем разные поля у которых отличается длина для определения соотношения релевантности, то вещи очень скоро становятся весьма сложными. На помощь приходит Xapian.
Что такое Xapian?
Xapian — это библиотека поискового механизма, схожая с Lucene и Sphinx. Скомпилирована из кода C++, следовательно довольно низкоуровневая. Доступны привязки к PHP, Perl и Python, которые сразу готовы к использованию. Пакеты доступны для Ubuntu и Red Hat, компилируется под OSX, а также можно запускать под Windows через CygWin.
Примеры скриптов
Вместо того чтобы объяснять что да как, я решил создать некоторые демонстрационные скрипты.
db.sql
Убрать подсветку кода
XapianWrapper.php
Убрать подсветку кода
index.php
Убрать подсветку кода
search.php
Убрать подсветку кода
delete.php
Убрать подсветку кода
Запуск примера
Теперь мы должны запустить php-примеры через командную строку.
Убрать подсветку кода
Все верно, я оставил вам примеры для улучшения и подстраивания под ваши личные нужды. Как обычно, любые советы по улучшению приветствуются.
Оригинал: Fulltext searches with Xapian and PHP
Что такое Xapian?
Xapian — это библиотека поискового механизма, схожая с Lucene и Sphinx. Скомпилирована из кода C++, следовательно довольно низкоуровневая. Доступны привязки к PHP, Perl и Python, которые сразу готовы к использованию. Пакеты доступны для Ubuntu и Red Hat, компилируется под OSX, а также можно запускать под Windows через CygWin.
Примеры скриптов
Вместо того чтобы объяснять что да как, я решил создать некоторые демонстрационные скрипты.
db.sql
Убрать подсветку кода
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | CREATE DATABASE `demo`; CREATE TABLE `demo`.`demo` ( `id` INT( 10 ) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY , `unique_key` VARCHAR( 255 ) NOT NULL , `name` VARCHAR( 255 ) NULL DEFAULT NULL , `summary` TEXT NULL DEFAULT NULL , `date` DATETIME NULL DEFAULT NULL , UNIQUE (`unique_key`)); INSERT INTO `demo`.`demo` (`id`, `unique_key`, `name`, `summary`, `date`) VALUES (NULL, 'foo', 'foo', 'foo bar test', '2008-11-05 00:00:00'), (NULL , 'bar', 'bar', 'test foo bar', '2009-11-05 00:00:00'); |
XapianWrapper.php
Убрать подсветку кода
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 | // includes require_once 'xapian.php'; // main class class XapianWrapper { const XAPIAN_FIELD_URL = 0; const XAPIAN_FIELD_NAME = 1; const XAPIAN_FIELD_DATE = 2; const XAPIAN_FIELD_UID = 3; const XAPIAN_FIELD_SUMMARY = 4; const XAPIAN_PREFIX_UID = "UID:"; const SETTINGS_XAPIAN_DB = './xapian_db'; const SETTINGS_MYSQL_HOST = 'localhost'; const SETTINGS_MYSQL_USER = 'root'; const SETTINGS_MYSQL_PASS = 'root'; const SETTINGS_MYSQL_DB = 'demo'; const SETTINGS_MYSQL_TABLE = 'demo'; const DEFAULT_COUNT = 10; private $mysql_link; private $category_cache; private $xapian_read_db; private $xapian_write_db; private $xapian_stemmer; private $xapian_indexer; private $xapian_enquire; private function xapian_init_readonly() { try{ $this->xapian_read_db = new XapianDatabase(self::SETTINGS_XAPIAN_DB); $this->xapian_stemmer = new XapianStem("english"); $this->xapian_enquire = new XapianEnquire($this->xapian_read_db); } catch(Exception $e) { throw new Exception('Could initialize Xapian: ' . $e->getMessage()); } } private function xapian_init_writable() { try{ $this->xapian_write_db = new XapianWritableDatabase( self::SETTINGS_XAPIAN_DB, Xapian::DB_CREATE_OR_OPEN); $this->xapian_indexer = new XapianTermGenerator(); $this->xapian_stemmer = new XapianStem("english"); $this->xapian_indexer->set_stemmer($this->xapian_stemmer); } catch(Exception $e) { throw new Exception('Could initialize Xapian: ' . $e->getMessage()); } } private function mysql_init() { $this->mysql_link = mysql_connect( self::SETTINGS_MYSQL_HOST, self::SETTINGS_MYSQL_USER, self::SETTINGS_MYSQL_PASS); if (!$this->mysql_link) { throw new Exception('Could not connect: ' . mysql_error()); } $db_selected = mysql_select_db(self::SETTINGS_MYSQL_DB, $this->mysql_link); if (!$db_selected) { throw new Exception('Can\'t use db : ' . mysql_error()); } } /** * Index method * */ public function index($params) { $this->xapian_init_writable(); $this->mysql_init(); $start = microtime(true); $response = new stdClass(); $response->indexed = array(); $offset = (isset($params['offset'])) ? intval($params['offset']) : 0; $count = (isset($params['count'])) ? intval($params['count']) : self::DEFAULT_COUNT; $sql = 'SELECT * FROM '. self::SETTINGS_MYSQL_TABLE.' LIMIT ' . $offset . ', ' . $count . ';'; $result = mysql_query($sql); if (!$result) { throw new Exception('Invalid query: ' . mysql_error()); } $this->xapian_write_db->begin_transaction(); while ($row = mysql_fetch_array($result, MYSQL_ASSOC)) { $response->indexed[] = $this->index_row($row); } $this->xapian_write_db->commit_transaction(); mysql_free_result($result); mysql_close($this->mysql_link); return $response; } private function index_row($row) { $doc = new XapianDocument(); $this->xapian_indexer->set_document($doc); $this->xapian_indexer->index_text($row['name'],50); $this->xapian_indexer->index_text($row['summary'], 1); $GUID = self::XAPIAN_PREFIX_UID . $row['unique_key']; $doc->add_term($GUID); $doc->add_value(self::XAPIAN_FIELD_URL, $row['url']); $doc->add_value(self::XAPIAN_FIELD_DATE, date('Ymd', strtotime($row['date']))); $doc->add_value(self::XAPIAN_FIELD_UID, $row['unique_key']); $doc->add_value(self::XAPIAN_FIELD_NAME, $row['name']); $doc->add_value(self::XAPIAN_FIELD_SUMMARY, $row['summary']); $this->xapian_write_db->replace_document(strval($GUID), $doc); $row_response = array(); $row_response['name'] = $row['name']; $row_response['guid'] = $row['unique_key']; $row_response['url'] = $row['url']; return $row_response; } /** * Delete method * */ public function delete($params) { $this->xapian_init_writable(); $this->xapian_write_db->begin_transaction(); $response = array(); foreach($params['items'] as $param_guid) { $GUID = self::XAPIAN_PREFIX_UID . $param_guid; $this->xapian_write_db->delete_document(strval($GUID)); $response[] = $param_guid; } $this->xapian_write_db->commit_transaction(); return $response; } /** * Search method * */ public function search($params) { $this->xapian_init_readonly(); $start = microtime(true); // queries array to later construct full query $arr_queries = array(); // from date if(!empty($params['date_from'])) { $arr_queries[] = new XapianQuery( XapianQuery::OP_VALUE_GE, 6, date('Ymd', strtotime($params['date_from']))); } // to date if(!empty($params['date_to'])) { $arr_queries[] = new XapianQuery( XapianQuery::OP_VALUE_LE, 6, date('Ymd', strtotime($params['date_to']))); } // unique key if(!empty($params['unique_key'])) { $arr_queries[] = new XapianQuery(self::XAPIAN_PREFIX_UID . $params['unique_key']); } // normal search query parsed if(!empty($params['search'])) { $qp = new XapianQueryParser(); $qp->set_stemmer($this->xapian_stemmer); $qp->set_database($this->xapian_read_db); $qp->set_stemming_strategy(XapianQueryParser::STEM_SOME); $arr_queries[] = $qp->parse_query($params['search']); } // Find the results for the query. // construct final query $query = array_pop($arr_queries); foreach($arr_queries as $sq) { $query = new XapianQuery(XapianQuery::OP_AND, $query, $sq); } $this->xapian_enquire->set_query($query); // set the count to the specified params $offset = (isset($params['offset'])) ? intval($params['offset']) : 0; $count = (isset($params['count'])) ? intval($params['count']) : self::DEFAULT_COUNT; $matches = $this->xapian_enquire->get_mset($offset, $count); $response = new stdClass(); $response->result_count = $matches->get_matches_estimated(); $results = array(); $i = $matches->begin(); while (!$i->equals($matches->end())) { $m = array(); $n = $i->get_rank() + 1; $doc = $i->get_document(); $m['position'] = $n; $m['url'] = $doc->get_value(self::XAPIAN_FIELD_URL); $m['name'] = $doc->get_value(self::XAPIAN_FIELD_NAME); $m['summary'] = $doc->get_value(self::XAPIAN_FIELD_SUMMARY); $m['date'] = $doc->get_value(self::XAPIAN_FIELD_DATE); $m['unique_key'] = $doc->get_value(self::XAPIAN_FIELD_UID); $m['percent'] = $i->get_percent(); $results[count($results)] = $m; $i->next(); } $response->results = $results; $end = microtime(true); // runtime info $response->execute = new stdClass(); $response->execute->call = 'search'; $response->execute->offset = $offset; $response->execute->count = $count; $response->execute->start = $start; $response->execute->end = $end; $response->execute->time = $end - $start; // debug stuff $response->execute->debug = $query->get_description(); return $response; } } |
index.php
Убрать подсветку кода
1 2 3 4 5 6 7 | require_once 'XapianWrapper.php'; $x = new XapianWrapper(); $res = $x->index(array()); print_r($res); |
search.php
Убрать подсветку кода
1 2 3 4 5 6 7 8 | require_once 'XapianWrapper.php'; $x = new XapianWrapper(); $params = array('search' => 'foo'); $res = $x->search($params); print_r($res); |
delete.php
Убрать подсветку кода
1 2 3 4 5 6 7 8 9 | require_once 'XapianWrapper.php'; $x = new XapianWrapper(); $params = array( 'items' => array('foo'), ); $res = $x->delete($params); print_r($res); |
Запуск примера
Теперь мы должны запустить php-примеры через командную строку.
Убрать подсветку кода
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | bash$ php index.php
stdClass Object
(
[indexed] => Array
(
[0] => Array
(
[name] => foo
[guid] => foo
[url] =>
)
[1] => Array
(
[name] => bar
[guid] => bar
[url] =>
)
)
)
bash$ php search.php
stdClass Object
(
[result_count] => 2
[results] => Array
(
[0] => Array
(
[position] => 1
[url] =>
[name] => foo
[summary] => foo bar test
[date] => 20081105
[unique_key] => foo
[percent] => 100
)
[1] => Array
(
[position] => 2
[url] =>
[name] => bar
[summary] => test foo bar
[date] => 20091105
[unique_key] => bar
[percent] => 50
)
)
[execute] => stdClass Object
(
[call] => search
[offset] => 0
[count] => 10
[start] => 1256674866.79
[end] => 1256674866.79
[time] => 0.000944852828979
[debug] => Xapian::Query(Zfoo:(pos=1))
)
)
bash$ php delete.php
Array
(
[0] => foo
)
bash$ php search.php
stdClass Object
(
[result_count] => 1
[results] => Array
(
[0] => Array
(
[position] => 1
[url] =>
[name] => bar
[summary] => test foo bar
[date] => 20091105
[unique_key] => bar
[percent] => 100
)
)
[execute] => stdClass Object
(
[call] => search
[offset] => 0
[count] => 10
[start] => 1256674876.02
[end] => 1256674876.02
[time] => 0.000872850418091
[debug] => Xapian::Query(Zfoo:(pos=1))
)
) |
Все верно, я оставил вам примеры для улучшения и подстраивания под ваши личные нужды. Как обычно, любые советы по улучшению приветствуются.
Оригинал: Fulltext searches with Xapian and PHP
Рейтинг:




<< Вы можете поставить оценку этой статьеПодобные статьи:
6 инструментов чтобы быть эффективным Web-разработчиком
Понимание области видимости в объектно-ориентированном JavaScript
Введение в искусство модульного тестирования в PHP
Интеграция FCKeditor в Zend_Form
Автоматизированное тестирование с использованием Zend Framework
Обсуждение статьи:
Сергей [2011-11-03]
А где можно взять файл xapian.php
Українська