Создание собственной поисковой системы с помощью PHP

Sphinx индексирует контент, быстро находит текст и выдает полезные результаты поиска

Хотя Google и ему подобные знают практически всё обо всём, могучие поисковые машины не всегда подходят для всех сайтов. Если контент вашего сайта очень специфичен или четко классифицирован, используйте Sphinx и PHP для создания точно настроенной локальной поисковой системы.

Мартин Стрейчер, главный редактор, Linux Magazine

Мартин Стрейчер (Martin Streicher) -- главный редактор журнала Linux Magazine. Он имеет степень магистра компьютерных наук Университета Пардью (Purdue University) и с 1982 занимается программированием на языках Pascal, C, Perl, Java и (с недавнего времени) Ruby в UNIX-подобных операционных системах.



23.10.2007

В эпоху Интернета люди хотят, чтобы информация была упакована как фаст-фуд: подавалась моментально, без проблем, и в точно отмеренных порциях. Безусловно, чтобы накормить нетерпеливую и голодную массу, сегодня даже от небольших Web-сайтов ждут широкого выбора блюд с быстрой доставкой:

  • RSS - это ваша «служба доставки пиццы», которая приносит свежеиспеченные данные прямо к вашей двери.
  • Блог - это ближайший ресторанчик с китайской кухней на вынос, предлагающий ваши любимые острые блюда.
  • Форум - это ресторанчик по соседству (если быть более точным, лучше вспомнить сцену битвы едой из фильма "Зверинец").
  • А поиск похож на «съешь, сколько можешь» ночью в местном кафетерии: Просто поставьте на поднос все, чего душа желает, снова и снова, пока это вынесет ваш желудок — и ваш стул.

К счастью, разработчики PHP могут найти множество программного обеспечения для RSS, блогов и форумов, чтобы создать или дополнить свой сайт. И если Google и ему подобные знают практически всё и переваривают огромный трафик, эти поисковые системы не обязательно хорошо подходят ко всем сайтам.

Например, если ваш сайт предлагает сотни тысяч новых и восстановленных запчастей для Porsche, Google может помочь в широких запросах, например, "Carrera parts" ("запчасти для Carrera"), но он будет не очень полезен для более точных запросов, например, "used 1991 Porsche 911 Targa headlight bezel" ("обрамление подфарника для Porsche 911 Targa 1991 года, б/у").

Если контент вашего сайта очень специфичен или посетителям вашего сайта нужен поиск, адаптированный к процессам работы самого сайта, лучше всего будет дополнить глобальные поисковые системы локальной, специально созданной для вашего сайта. (Другие примеры специализированного поиска приведены в главе "Иголка в миллиарде стогов сена".)

Узнайте, как добавить на сайт PHP быструю и бесплатную поисковую систему с широкими возможностями и открытым исходным кодом. Здесь разрабатывается лишь малая видимая часть Web-сайта. Основное же внимание уделяется компонентам, необходимым для эффективного поиска: базе данных, индексу, поисковой системе и API-интерфейсу PHP.

Посещение Великого сфинкса

Чтобы реализовать на сайте собственную функцию поиска, нужен источник данных и возможность выполнять поиск по нему. В Web-приложениях источником данных, как правило, выступает реляционная база данных, имеющая некоторые встроенные возможности поиска. Равенство представляет собой простейший поисковый оператор, так же как SQL-оператор LIKE.) Однако некоторые виды поиска могут быть слишком специализированными для базы данных; также поиск может быть настолько сложным, что связанные с ним SQL-операторы JOIN будут просто выполняться слишком медленно.

Иголка в миллиарде стогов сена

Многие сайты предлагают контент для определенной отрасли, рода занятий или развлечений, например, для медицины, законодательства, музыки или автотехобслуживания. Чтобы углубиться в контент такого рода, могут потребоваться специальные инструменты или знания, или просто специально организованный индекс, возвращающий релевантные и полезные результаты.

Вот несколько типичных сценариев, в которых может потребоваться собственная поисковая система:

  • Найти все статьи о Кубке Стэнли, написанные Джо Хоки.
  • Найти последнюю версию драйверов для многофункционального принтера HP LaserJet 3015.
  • Найти запись Dinosaur Jr., показанную в "Последнем сеансе с Дэвидом Леттерманом".

Для ускорения поиска вам может потребоваться изменить структуру таблиц, чтобы упростить запросы. (Оптимизация таблиц и SQL-запросов сильно зависит от схемы и системы управления базой данных. В Интернете можно найти множество книг и статей по оптимизации производительности баз данных.) Альтернативный путь – применение специализированной поисковой системы. Какую поисковую систему применять - зависит от вида (и количества) данных, а также от вашего бюджета. Существует множество вариантов. Вы можете подключить к своей сети устройство Google, купить Endeca или другой коммерческий поисковый продукт для крупных предприятий, или попробовать Lucene. Однако во многих случаях коммерческие продукты не оправдывают рекламы или тяжелым грузом ложатся на бюджет, а Lucene, на момент написания этой статьи в июле 2007 года, не предлагал PHP API.

В качестве альтернативы можно рассмотреть Sphinx, бесплатную поисковую систему с открытым исходным кодом, предназначенную для исключительно быстрого поиска текста. Например, в реальной базе данных, состоящей примерно из 300 000 строк и пяти индексированных столбцов, где каждый столбец содержит около 15 слов, Sphinx может выдать результат по поиску "любое из этих слов" за одну сотую долю секунды (на сервере с процессором AMD Opteron с частотой 2 ГГц и 1 ГБ памяти, работающем под управлением Debian Linux® Sarge).

В Sphinx реализовано множество функций, в том числе:

  • Он может индексировать любые данные, представимые в виде строк.
  • Он может индексировать одни и те же данные различными способами. Создав несколько индексов, каждый из которых настроен на решение определенной задачи, вы можете выбрать наиболее подходящий для оптимизации результатов поиска.
  • Он может связывать атрибуты с каждым элементом индексированных данных. Впоследствии вы можете использовать один или несколько атрибутов для фильтрации результатов поиска.
  • Он поддерживает морфологические вариации слов, поэтому поиск по слову "cats" также выдаст результат по первичной словоформе "cat".
  • Индекс Sphinx можно распределить по нескольким машинам, обеспечив отказоустойчивую работу.
  • Он может создавать индексы префиксов слов произвольной длины и индексы инфиксов различной длины. Например, номер детали может состоять из 10 символов. Индекс префикса будет содержать все возможные подстроки, начинающиеся с начала строки. Индекс инфикса будет содержать подстроки, содержащиеся в любом месте строки.
  • Sphinx можно запустить в качестве системы хранения в рамках MySQL V5, что исключает необходимость запуска еще одного демона, который зачастую рассматривается как дополнительная точка сбоя.

Полный перечень функций можно найти в Интернете и в файле README, распространяемом с исходным кодом Sphinx. На Web-сайте Sphinx также перечислено несколько проектов, в которых используется Sphinx.

Sphinx написан на C++, скомпилирован с помощью GNU, поддерживает 64-разрядные вычисления на соответствующих платформах, и работает под управлением Linux, UNIX®, Microsoft® Windows® и Mac OS X. Собрать Sphinx очень просто: Загрузите и распакуйте код, после чего запустите команду ./configure && make && make install.

По умолчанию утилиты Sphinx устанавливаются в папку /usr/local/bin/, а файл конфигурации для всех компонентов Sphinx - в папку /usr/local/etc/sphinx.conf.

Sphinx состоит из трех компонентов: генератор индекса, поисковая система и поисковая утилита, работающая в командной строке:

  • Генератор индекса называется индексатором (indexer). Он выполняет запросы к базе данных, индексирует каждую колонку в каждой строке результата и привязывает каждую запись индекса к первичному ключу строки.
  • Поисковая система представляет собой демон, который называется searchd. Демон получает критерии поиска и другие параметры, проходит по одному или нескольким индексам и возвращает результат. Если соответствие находится, searchd возвращает массив первичных ключей. Используя эти ключи, приложение может выполнить запрос по соответствующей базе данных и найти полные записи, удовлетворяющие критериям поиска. Searchd взаимодействует с приложением через сокет на порту 3312.
  • Удобная утилита search позволяет выполнять поиск из командной строки без написания кода. Если searchd возвращает результат, поисковая система формирует запрос к базе данных и выводит строки, содержащиеся в результирующем множестве. Утилита search полезна для отладки конфигурации Sphinx и выполнения импровизированных поисковых запросов.

Кроме того, автор Sphinx Андрей Аксёнов и другие участники проекта реализовали интерфейсы для PHP, Perl, C/C++ и других языков программирования.


Поиск деталей кузова

Предположим, сайт Body-Parts.com продает детали кузовов — крылья, бамперы, хромированные детали и т.п. — для редких и коллекционных автомобилей. Как и в реальном мире, посетитель сайта Body Parts, вероятно, захочет искать детали по производителю (скажем, Porsche или производитель эквивалентных деталей), номеру изделия, марке, модели, году выпуска, состоянию (новая, восстановленная, бывшая в использовании), по описанию или по сочетанию этих характеристик.

Чтобы реализовать функцию поиска сайта Body Parts, мы будем использовать в качестве источника данных MySQL V5.0 и поисковый демон Sphinx, который будет выполнять быстрый и точный текстовый поиск. База данных MySQL V5.0 обладает очень широкими возможностями, однако функция полнотекстового поиска - не самая сильная ее черта. Фактически она ограничена таблицами MyISAM — форматом таблиц, который не поддерживает внешние ключи, и, следовательно, имеет ограниченную применимость.

В листингах с 1 по 4 показаны элементы схемы Body Parts, относящиеся к этому примеру. Вы можете увидеть таблицы Model (листинг 1), Assembly (листинг 2), Inventory (листинг 3) и Schematic (листинг 4) соответственно.

Таблица Model

Таблица Model, представленная в листинге 1, очень проста: Столбец метки указывает название модели ("Corvette"); описание действует как описание автомобиля в дружественной форме ("Two-door roadster; first year of introduction" - "Двухдверный родстер, первый год выпуска модели"); а begin_production и end_production обозначают, когда соответственно началось и завершилось производство этой версии. Поскольку значения упомянутых выше столбцов колонок не уникальны, каждой четверке значений столбцов (label, description, begin_production, end_production) сопоставлен отдельный идентификатор, который является внешним ключом других таблиц.

Листинг 1. Таблица Model для Body Parts
CREATE TABLE Model (
  id int(10) unsigned NOT NULL auto_increment,
  label varchar(7) NOT NULL,
  description varchar(256) NOT NULL,
  begin_production int(4) NOT NULL,
  end_production int(4) NOT NULL,
  PRIMARY KEY (id)
) ENGINE=InnoDB;

Ниже приведено несколько примеров данных для таблицы Model:

INSERT INTO Model 
  (`id`, `label`, `description`, `begin_production`, `end_production`) 
VALUES 
  (1,'X Sedan','Four-door performance sedan',1998,1999),
  (3,'X Sedan','Four door performance sedan, 1st model year',1995,1997),
  (4,'J Convertible','Two-door roadster, metal retracting roof',2002,2005),
  (5,'J Convertible','Two-door roadster',2000,2001),
  (7,'W Wagon','Four-door, all-wheel drive sport station wagon',2007,0);

Таблица Assembly

Узел (assembly) это подсистема автомобиля, например, трансмиссия или полный набор стекол. Чтобы найти нужную запчасть, владельцы сверяются со сборочными чертежами и прилагающимися к ним спецификациями. Таблица Assembly, показанная в листинге 2, также не содержит ничего сложного: Она сопоставляет названию и описанию узла уникальный идентификатор.

Листинг 2. Таблица Assembly
CREATE TABLE Assembly (
  id int(10) unsigned NOT NULL auto_increment,
  label varchar(7) NOT NULL,
  description varchar(128) NOT NULL,
  PRIMARY KEY (id)
) ENGINE=InnoDB;

Несколько примеров данных для таблицы Assembly приведены ниже:

INSERT INTO Assembly 
  (`id`, `label`, `description`) 
VALUES 
  (1,'5-00','Seats'),
  (2,'4-00','Electrical'),
  (3,'3-00','Glasses'),
  (4,'2-00','Frame'),
  (5,'1-00','Engine'),
  (7,'101-00','Accessories');

Таблица Inventory

Таблица Inventory представляет собой канонический список деталей автомобилей. Деталь — например, болт или лампа — может присутствовать в разных автомобилях и в различных сборках, но в таблице Inventory каждая деталь упоминается только один раз. Каждая строка таблицы Inventory содержит:

  • Уникальный 32-разрядный целочисленный серийный номер serialno, используемый для идентификации строки.
  • Буквенно-цифровой номер детали. (Этот номер уникален и мог бы служить первичным ключом. Однако, поскольку он может содержать не только цифры, но и буквы, он не подходит для использования в Sphinx, который требует, чтобы у каждой индексируемой записи был уникальный 32-разрядный целочисленный ключ.)
  • Текстовое описание.
  • Цена.

Спецификация таблицы Inventory показана в листинге 3.

Листинг 3. Таблица Inventory
CREATE TABLE Inventory (
  id int(10) unsigned NOT NULL auto_increment,
  partno varchar(32) NOT NULL,
  description varchar(256) NOT NULL,
  price float unsigned NOT NULL default '0',
  PRIMARY KEY (id),
  UNIQUE KEY partno USING BTREE (partno)
) ENGINE=InnoDB;

Фрагмент перечня деталей может выглядеть следующим образом

INSERT INTO `Inventory` 
  (`id`, `partno`, `description`, `price`) 
VALUES 
  (1,'WIN408','Portal window',423),
  (2,'ACC711','Jack kit',110),
  (3,'ACC43','Rear-view mirror',55),
  (4,'ACC5409','Cigarette lighter',20),
  (5,'WIN958','Windshield, front',500),
  (6,'765432','Bolt',0.1),
  (7,'ENG001','Entire engine',10000),
  (8,'ENG088','Cylinder head',55),
  (9,'ENG976','Large cylinder head',65);

Таблица Schematic

Таблица Schematic связывает детали с узлами и моделями. Например, таблицу Schematic можно использовать для поиска всех деталей, составляющих двигатель кабриолета J Class 1979 года. В каждой строке таблицы Schematic содержится уникальный ID, внешний ключ к строке таблицы Inventory, внешний ключ, который идентифицирует узел, и еще один ключ, указывающий на определенную модель и модификацию из таблицы Model. Строки показаны в листинге 4.

Листинг 4. Таблица Schematic
CREATE TABLE Schematic (
  id int(10) unsigned NOT NULL auto_increment,
  partno_id int(10) unsigned NOT NULL,
  assembly_id int(10) unsigned NOT NULL,
  model_id int(10) unsigned NOT NULL,
  PRIMARY KEY (id),
  KEY partno_index USING BTREE (partno_id),
  KEY assembly_index USING BTREE (assembly_id),
  KEY model_index USING BTREE (model_id),
  FOREIGN KEY (partno_id) REFERENCES Inventory(id),
  FOREIGN KEY (assembly_id) REFERENCES Assembly(id),
  FOREIGN KEY (model_id) REFERENCES Model(id)
) ENGINE=InnoDB;

Чтобы понять цель таблицы, посмотрите на небольшой список строк из Schematic:

INSERT INTO `Schematic` 
  (`id`, `partno_id`, `assembly_id`, `model_id`) 
VALUES 
  (1,6,5,1),
  (2,8,5,1),
  (3,1,3,1),
  (4,5,3,1),
  (5,8,5,7),
  (6,6,5,7),
  (7,4,7,3),
  (8,9,5,3);

Поиск по таблицам

При такой структуре таблиц можно легко ответить на множество поисковых запросов:

  • Вывести все модификации определенной модели
  • Показать все узлы, необходимые для сборки определенной модели и модификации
  • Показать все детали, входящие в конкретный узел определенной модели и модификации

Однако некоторые типы запросов будут особенно затратны по времени:

  • Найти все вхождения деталей в любой модели и модификации, номера которых начинаются с "WIN"
  • Найти детали, в описании которых указано "lacquer" (покрыто лаком) или "paint" (окрашено)
  • Найти все детали, в описании которых указано "black leather" (черная кожа)
  • Найти все детали J Series 2002 года со словом "paint" (окрашено) в описании

Чтобы выполнить поиск такого вида, нужно делать громоздкие запросы с множеством JOIN или с ресурсоемкими операторами LIKE, особенно в случаях, когда таблицы Inventory и Schematic очень большие. Более того, сложный текстовый поиск вообще лежит за пределами возможностей MySQL. Если нужен поиск в больших объемах текстовых данных, стоит подумать о создании и использовании индекса Sphinx.


Интеграция программного обеспечения Sphinx

Чтобы решить проблему с помощью Sphinx, нужно определить один или несколько источников данных и один или несколько индексов.

Источник (source) определяет базу данных, которую нужно индексировать, предоставляет информацию для аутентификации и указывает запросы, которые нужно использовать для формирования каждой из строк. При желании источник может определять один или несколько столбцов как фильтры, или, как это называется в Sphinx, группы (group). Группы используются для фильтрации результатов. Например, по слову "paint" может выдаваться 900 совпадений. Если вас интересуют совпадения только для определенной модели, вы можете отфильтровать результат по группе model.

Для индекса (index) необходимо, чтобы был определен источник (то есть множество строк) и способ классификации данных, извлеченных из этого источника.

Источник(и) и индекс(ы) определяются в файле sphinx.conf. Источником для сайта Body Parts является база данных MySQL. В листинге 5 показана часть определения источника под названием catalog — фрагмент показывает, к какой базе данных подключаться и как выполнять соединение (сервер, сокет, пользователь и пароль).

Листинг 5. Настройки доступа к базе данных MySQL
source catalog 
{
    type                            = mysql
    
    sql_host                        = localhost
    sql_user                        = reaper
    sql_pass                        = s3cr3t
    sql_db                          = body_parts
    sql_sock                        =  /var/run/mysqld/mysqld.sock
    sql_port                        = 3306

Теперь необходимо создать запрос, который будет возвращать строки, подлежащие индексации. Обычно, чтобы получить строку, вы создаете запрос SELECT, возможно, соединяя множество таблиц с помощью JOIN. Здесь, однако, имеется проблема: Для поиска модели и года выпуска необходимо использовать таблицу Assembly, а номер детали и ее описание можно найти только в таблице Inventory. Для выполнения своих функций Sphinx необходима возможность связать результат с 32-разрядным целочисленным первичным ключом.

Для получения данных в нужной форме создадим представление (view)— - новую конструкцию, реализованную в MySQL V5, которая собирает колонки из различных таблиц в единую сборную виртуальную таблицу. При работе с представлениями все данные, необходимые для любого вида поиска, находятся в одном месте, даже в том случае, если фактические данные располагаются в других таблицах. В листинге 6 показан SQL-запрос, определяющий представление Catalog.

Листинг 6. Представление Catalog собирает данные в виртуальную таблицу
CREATE OR REPLACE VIEW Catalog AS
SELECT
  Inventory.id,
  Inventory.partno,
  Inventory.description,
  Assembly.id AS assembly,
  Model.id AS model
FROM
  Assembly, Inventory, Model, Schematic
WHERE
  Schematic.partno_id=Inventory.id 
  AND Schematic.model_id=Model.id 
  AND Schematic.assembly_id=Assembly.id;

Если вы создали базу данных body_parts с показанными ранее таблицами и данными, представление Catalog будет иметь примерно следующий вид:

mysql> use body_parts;
Database changed
mysql> select * from Catalog;
+----+---------+---------------------+----------+-------+
| id | partno  | description         | assembly | model |
+----+---------+---------------------+----------+-------+
|  6 | 765432  | Bolt                |        5 |     1 | 
|  8 | ENG088  | Cylinder head       |        5 |     1 | 
|  1 | WIN408  | Portal window       |        3 |     1 | 
|  5 | WIN958  | Windshield, front   |        3 |     1 | 
|  4 | ACC5409 | Cigarette lighter   |        7 |     3 | 
|  9 | ENG976  | Large cylinder head |        5 |     3 | 
|  8 | ENG088  | Cylinder head       |        5 |     7 | 
|  6 | 765432  | Bolt                |        5 |     7 | 
+----+---------+---------------------+----------+-------+
8 rows in set (0.00 sec)

Поле id представления указывает на запись детали в таблице Inventory. Столбцы partno и description содержат собственно текст для поиска, а столбцы assembly и model служат группами для дальнейшей фильтрации результатов. При наличии такого представления поисковые запросы создаются моментально. В листинге 7 показана оставшаяся часть определения источника данных catalog.

Листинг 7. Запрос на создание строк для индексации
    # indexer query
    # document_id MUST be the very first field
    # document_id MUST be positive (non-zero, non-negative)
    # document_id MUST fit into 32 bits
    # document_id MUST be unique
    sql_query                       = \
            SELECT \
                    id, partno, description, \
                    assembly, model \
            FROM \
                    Catalog;
    
    sql_group_column                = assembly
    sql_group_column                = model
    
    # document info query
    # ONLY used by search utility to display document information
    # MUST be able to fetch document info by its id, therefore
    # MUST contain '$id' macro 
    #
    sql_query_info          = SELECT * FROM Inventory WHERE id=$id
}

В запрос sql_query должен входить первичный ключ, который вы хотите использовать в последующем для поиска, а также все поля, которые вы желаете индексировать и использовать в качестве групп. Две записи sql_group_column объявляют, что для фильтрации результатов могут использоваться поля Assembly и Model. Для поиска нужных записей в поисковой утилите используется sql_query_info. В запросе $id заменяется каждым первичным ключом, возвращенным searchd.

Последним действием по настройке является построение индекса. В листинге 8 показан индекс для источника данных catalog.

Листинг 8. Описание одного из возможных индексов для источника catalog
index catalog
{
    source                  = catalog
    path                    = /var/data/sphinx/catalog
    morphology              = stem_en

    min_word_len            = 3
    min_prefix_len          = 0
    min_infix_len           = 3
}

В строке 1 дается ссылка на именованный источник данных в файле sphinx.conf. Во второй строке определяется, где хранить индексированные данные; по умолчанию индексы Sphinx хранятся в /var/data/sphinx. В строке 3 указывается на необходимость использования английского морфологического словаря. Строки 5-7 говорят индексатору о том, что необходимо индексировать только слова, состоящие из трех символов и более, и включать в индекс инфиксов все подстроки, содержащие три символов и более. (Для простоты в листинге 9 показан весь файл sphinx.conf для сайта Body Parts.)

Листинг 9. Пример sphinx.conf для Body Parts
source catalog
{
    type                            = mysql
    
    sql_host                        = localhost
    sql_user                        = reaper
    sql_pass                        = s3cr3t
    sql_db                          = body_parts
    sql_sock                        =  /var/run/mysqld/mysqld.sock
    sql_port                        = 3306                  

    # indexer query
    # document_id MUST be the very first field
    # document_id MUST be positive (non-zero, non-negative)
    # document_id MUST fit into 32 bits
    # document_id MUST be unique

    sql_query                       = \
            SELECT \
                    id, partno, description, \
                    assembly, model \
            FROM \
                    Catalog;

    sql_group_column                = assembly
    sql_group_column                = model

    # document info query
    # ONLY used by search utility to display document information
    # MUST be able to fetch document info by its id, therefore
    # MUST contain '$id' macro 
    #

    sql_query_info          = SELECT * FROM Inventory WHERE id=$id
}

index catalog
{
    source                  = catalog
    path                    = /var/data/sphinx/catalog
    morphology              = stem_en

    min_word_len            = 3
    min_prefix_len          = 0
    min_infix_len           = 3
}

searchd
{
	port				= 3312
	log					= /var/log/searchd/searchd.log
	query_log			= /var/log/searchd/query.log
	pid_file			= /var/log/searchd/searchd.pid
}

В расположенном в конце файла разделе searchd выполняется настройка демона searchd. Записи этого раздела говорят сами за себя. Особенно полезен параметр query.log (журнал запросов): В него заносятся все запущенные поисковые запросы, а также результаты их выполнения, например, количество документов, по которым производился поиск, и количество совпадений.


Создание и проверка индекса

Теперь мы готовы сформировать индекс для приложения Body Parts. Для этого:

  1. Создадим иерархию каталогов /var/data/sphinx, выполнив: $ sudo mkdir -p /var/data/sphinx
  2. Предполагая, что MySQL работает, запустите индексатор, выполнив приведенный ниже код.
    Листинг 10. Создание индексов
    $ sudo /usr/local/bin/indexer --config /usr/local/etc/sphinx.conf --all
    Sphinx 0.9.7
    Copyright (c) 2001-2007, Andrew Aksyonoff
    
    using config file '/usr/local/etc/sphinx.conf'...
    indexing index 'catalog'...
    collected 8 docs, 0.0 MB
    sorted 0.0 Mhits, 82.8% done
    total 8 docs, 149 bytes
    total 0.010 sec, 14900.00 bytes/sec, 800.00 docs/sec
    Примечание: Аргумент -all перестраивает все индексы, перечисленные в файле sphinx.conf. Если вам не нужно перестраивать все индексы, вы можете сделать это выборочно, указав другой аргумент.
  3. Теперь вы можете протестировать индекс с помощью утилиты search, используя приведенный ниже код. (Для работы search запуск searchd не требуется.)
    Листинг 11. Проверка индекса с помощью search
    $ /usr/local/bin/search --config /usr/local/etc/sphinx.conf ENG
    Sphinx 0.9.7
    Copyright (c) 2001-2007, Andrew Aksyonoff
    
    index 'catalog': query 'ENG ': returned 2 matches of 2 total in 0.000 sec
    
    displaying matches:
    1. document=8, weight=1, assembly=5, model=7
            id=8
            partno=ENG088
            description=Cylinder head
            price=55
    2. document=9, weight=1, assembly=5, model=3
            id=9
            partno=ENG976
            description=Large cylinder head
            price=65
    
    words:
    1. 'eng': 2 documents, 2 hits
    
    $ /usr/local/bin/search --config /usr/local/etc/sphinx.conf wind 
    Sphinx 0.9.7
    Copyright (c) 2001-2007, Andrew Aksyonoff
    
    index 'catalog': query 'wind ': returned 2 matches of 2 total in 0.000 sec
    
    displaying matches:
    1. document=1, weight=1, assembly=3, model=1
            id=1
            partno=WIN408
            description=Portal window
            price=423
    2. document=5, weight=1, assembly=3, model=1
            id=5
            partno=WIN958
            description=Windshield, front
            price=500
    
    words:
    1. 'wind': 2 documents, 2 hits
    
    $ /usr/local/bin/search \
    --config /usr/local/etc/sphinx.conf --filter  model 3 ENG
    Sphinx 0.9.7
    Copyright (c) 2001-2007, Andrew Aksyonoff
    
    index 'catalog': query 'ENG ': returned 1 matches of 1 total in 0.000 sec
    
    displaying matches:
    1. document=9, weight=1, assembly=5, model=3
            id=9
            partno=ENG976
            description=Large cylinder head
            price=65
    
    words:
    1. 'eng': 2 documents, 2 hits

Первая команда, /usr/local/bin/search --config /usr/local/etc/sphinx.conf ENG, находит два упоминания ENG в номерах деталей. Вторая команда, /usr/local/bin/search --config /usr/local/etc/sphinx.conf wind, находит подстроку wind в двух описаниях деталей. А третья команда ограничивает результат только теми записями, в которых model равно 3.


Написание кода

Теперь, наконец, вы можете приступать к написанию кода PHP для вызова поисковой системы Sphinx. API-интерфейс Sphinx для PHP невелик и очень прост в освоении. В листинге 12 показано небольшое приложение PHP, которое вызывает searchd и извлекает те же результаты, что и последняя команда, показанная выше ("найти все детали, в названии которых есть слово 'cylinder', принадлежащие модели 3").

Листинг 12. Вызов поисковой системы из PHP
<?php
  include('sphinx-0.9.7/api/sphinxapi.php');

  $cl = new SphinxClient();
  $cl->SetServer( "localhost", 3312 );
  $cl->SetMatchMode( SPH_MATCH_ANY  );
  $cl->SetFilter( 'model', array( 3 ) );

  $result = $cl->Query( 'cylinder', 'catalog' );

  if ( $result === false ) {
      echo "Query failed: " . $cl->GetLastError() . ".\n";
  }
  else {
      if ( $cl->GetLastWarning() ) {
          echo "WARNING: " . $cl->GetLastWarning() . "
"; } if ( ! empty($result["matches"]) ) { foreach ( $result["matches"] as $doc => $docinfo ) { echo "$doc\n"; } print_r( $result ); } } exit; ?>

Для проверки кода создайте директорию log для Sphinx, запустите searchd, после чего запустите приложение PHP, как показано ниже.

Листинг 13. Приложение PHP
$ sudo mkdir -p /var/log/searchd
$ sudo /usr/local/bin/searchd --config /usr/local/etc/sphinx.conf
$ php search.php 
9
Array
(
    [fields] => Array
        (
            [0] => partno
            [1] => description
        )

    [attrs] => Array
        (
            [assembly] => 1
            [model] => 1
        )

    [matches] => Array
        (
            [9] => Array
                (
                    [weight] => 1
                    [attrs] => Array
                        (
                            [assembly] => 5
                            [model] => 3
                        )

                )

        )

    [total] => 1
    [total_found] => 1
    [time] => 0.000
    [words] => Array
        (
            [cylind] => Array
                (
                    [docs] => 2
                    [hits] => 2
                )

        )
)

Результатом будет 9: правильный первичный ключ единственной строки, удовлетворяющей запросу. Если Sphinx находит совпадение, в ассоциативный массив $result записывается элемент results. Посмотрите на результат работы print_r() и посмотрите, что ещё выводится.

Одно замечание: total_found это общее количество совпадений, найденных в индексе, а found это количество выведенных результатов. Эти две величины могут различаться, поскольку вы можете изменять число результатов, выводимых при каждом запуске, а также то, какие результаты возвращать, что удобно для постраничного вывода длинных списков результатов. Обратите внимание на вызов API SetLimits(). Одним из способов организации постраничного вывода является вызов поисковой системы со следующими параметрами: $cl->SetLimits( ( $page - 1 ) * SPAN, SPAN ) для выдачи первой, второй, третьей (и т.д.) группы результатов SPAN, в зависимости от того, какую страницу вы хотите вывести.


Исследуйте тайны Сфинкса

В Sphinx реализовано множество полезных функций. В этой статье я обрисовал только самые общие моменты, но теперь у вас есть реальный работающий пример, с которого можно начать дальнейшее изучение.

Внимательно ознакомьтесь с примером файла конфигурации Sphinx, /usr/local/etc/sphinx.conf.dist, который входит в комплект поставки. Комментарии, приведенные в этом файле, объясняют, что делает тот или иной параметр Sphinx; показывают, как создать распределенную конфигурацию с резервированием; а также объясняют, как наследовать параметры базы данных, чтобы избежать повторения в источниках и индексах. Файл README Sphinx также является великолепным источником информации, в том числе о том, как встраивать Sphinx непосредственно в MySQL V5 — без демонов.

В следующий раз мы попробуем найти лучшее решение для отладки кода PHP, чем echo() и print_r().

Ресурсы

Научиться

  • Оригинал статьи Build a custom search engine with PHP (EN).
  • Sphinx (EN) это бесплатная поисковая система с открытым исходным кодом, предназначенная для выполнения исключительно быстрого текстового поиска.
  • Познакомьтесь с Endeca и другими крупными коммерческими поисковыми продуктами, или попробуйте Lucene.(EN)
  • PHP.net является центральным ресурсом для PHP-разработчиков.(EN)
  • Познакомьтесь с "Рекомендованным списком литературы по PHP (EN)."
  • Ознакомьтесь со всей информацией о PHP на сайте developerWorks.
  • Расширьте ваши навыки в PHP, ознакомившись с ресурсами проекта PHP IBM developerWorks.(EN)
  • Прослушать интересные интервью и беседы разработчиков программного обеспечения можно в подкастах developerWorks.(EN)
  • Работаете с базами данных на PHP? Познакомьтесь с Zend Core for IBM, готовой, прозрачной и простой в установке среде разработки и внедрения PHP, которая поддерживает IBM DB2 V9.(EN)
  • Следите на последними новостями на портале Web-трансляций и технических мероприятий developerWorks.(EN)
  • Узнайте о предстоящих конференциях, выставках, Web-трансляциях и других событиях, представляющих интерес для разработчиков программного обеспечения с открытым исходным кодом IBM.(EN)
  • Посетите Раздел Open Source сайта developerWorks и найдите множество инструкций, инструментов, а также новостей проектов, которые помогут вам разрабатывать приложения с открытым исходным кодом и использовать их вместе с продуктами IBM.
  • Узнайте больше об IBM и о возможностях и технологиях продуктов с открытым исходным кодом из бесплатных демонстраций developerWorks "по требованию".(EN)

Получить продукты и технологии

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Open source
ArticleID=264064
ArticleTitle=Создание собственной поисковой системы с помощью PHP
publish-date=10232007