Sphinx - Источники данных в формате xml
В предыдущих статьях я рассказал о том, что в качестве источника данных для Sphinx можно использовать базы данных, в частности MS SQL Server. Во многих случаях этого наиболее удобный способ построения индекса, поскольку Sphinx может получить все данные одним запросом. Даже на очень больших объёмах данных этот запрос можно разбить на несколько ranged-запросов и получать данные из базы небольшими порциями.
Но данные в базе могут иметь сложную структуру со множеством связей, и получить их одним запросом может быть сложно. К тому же может потребоваться получать данные из источника, который Sphinx просто не поддерживает. Наиболее универсальным источником данных для Sphinx являются данные в формате xml. Именно об этом я и хочу рассказать в этой статье.
Пример 1: Индексация xml-файла
Для начала рассмотрим самый простой пример, когда индексируемые данные находятся в xml-файле, а схема индекса описывается в файле конфигурации.
Шаг 1. Создадим файл product.xml (я поместил его в папку c:\sphinx\data\source\) с документами, которые собираемся индексировать.
<?xml version="1.0" encoding="utf-8"?>
<sphinx:docset xmlns:sphinx="http://sphinxsearch.com/">
<sphinx:document id="849">
<name>Men's Sports Shorts</name>
<price>24.7459</price>
</sphinx:document>
<sphinx:document id="852">
<name>Women's Tights</name>
<price>30.9334</price>
</sphinx:document>
<sphinx:document id="855">
<name>Men's Bib-Shorts</name>
<price>37.1209</price>
</sphinx:document>
</sphinx:docset>
Как вы видите, корневым элементом xml-файла является элемент sphinx:docset
, в
котором при помощи элементов sphinx:document
описаны индексируемые документы.
Каждый документ имеет уникальный атрибут id
и несколько вложенных элементов,
названия которых соответствуют названиям полей и атрибутов в схеме индекса.
Шаг 2. Опишем источник данных и индекс в файле конфигурации.
searchd
{
listen = 9306:mysql41
pid_file = c:/sphinx/data/searchd.pid
log = c:/sphinx/data/log/log.txt
query_log = c:/sphinx/data/log/query_log.txt
binlog_path = c:/sphinx/data/binlog/
}
source product
{
type = xmlpipe2
xmlpipe_command = type c:\sphinx\data\source\product.xml
xmlpipe_field_string = name
xmlpipe_attr_float = price
}
index product
{
source = product
path = c:/sphinx/data/index/product
charset_type = utf-8
}
Блоки searchd
и index product
вряд ли нуждаются в пояснении. Они
полностью аналогичны тому, что мы делали при индексации базы данных. Основной интерес
представляет блок source product
.
Опция type
указывает тип источника данных. Sphinx поддерживает два типа источников
данных в формате xml: xmlpipe и
xmlpipe2. Однако тип xmlpipe
считается уже устаревшим поэтому мы использует тип xmlpipe2.
Опция xmlpipe_command
указывает команду для получения данных. Эта команда должна
выводить xml-документ в стандартный выходной поток. Мы используем команду type
,
которая выводит содержимое файла, название которого передано ей в качестве аргумента. Убедиться
в этом можно, если ввести эту команду в командную строку.
type c:\sphinx\data\source\product.xml
Опций xmlpipe_field_string
и xmlpipe_attr_float
определяют поле
name
и атрибуты name
и price
, которые будут содержаться
в индексе. Полный список всех опций для определения полей и атрибутов можно найти в
документации.
Шаг 3. Выполним индексацию.
c:\sphinx\bin\indexer product --config c:\sphinx\data\config.txt --rotate
После того, как индексация успешно завершится, можно проверить, что индекс действительно был создан, подключившись к Sphinx и выполнив простой запрос.
select id, name, price from product;
Пример 2: Описание схемы в индексируемом xml-файле
Отличительной особенностью источников данных в формате xml является то, что схема индекса (набор полей и атрибутов) может быть описана не только в файле конфигурации, но и в самих данных. Это весьма полезная возможность, поскольку она позволяет изменять схему индекса без изменения файла конфигурации и последующего перезапуска Sphinx.
Шаг 1. Добавим схему индекса в файл product.xml
<?xml version="1.0" encoding="utf-8"?>
<sphinx:docset xmlns:sphinx="http://sphinxsearch.com/">
<sphinx:schema>
<sphinx:field name="name" attr="string" />
<sphinx:attr name="price" type="float"/>
</sphinx:schema>
<sphinx:document id="849">
<name>Men's Sports Shorts</name>
<price>24.7459</price>
</sphinx:document>
<sphinx:document id="852">
<name>Women's Tights</name>
<price>30.9334</price>
</sphinx:document>
<sphinx:document id="855">
<name>Men's Bib-Shorts</name>
<price>37.1209</price>
</sphinx:document>
</sphinx:docset>
Схема индекса описывается при помощи элемента sphinx:schema
, который может
содержать элементы sphinx:field
и sphinx:attr
.
Элемент sphinx:field
определяет поле с названием, указанным в атрибуте
name
. Также в этом элементе может содержаться необязательный атрибут
attr
, который указывает, что вместе с полем нужно создать одноимённый атрибут.
Значение атрибута attr
при этом указывает тип создаваемого атрибута (обычно это
строковой тип).
Элемент sphinx:attr
определяет атрибут с названием, указанным в атрибуте
name
и типом, указанным в атрибуте type
.
Более подробно про элементы sphinx:field
и sphinx:attr
можно прочитать
в документации.
Шаг 2. Изменим файл конфигурации, убрав определение полей и атрибутов из блока
source
.
source product
{
type = xmlpipe2
xmlpipe_command = type c:\sphinx\data\source\product.xml
}
Тут всё очень просто. Поскольку схема теперь описана в xml-файле, то никакой необходимости дублировать её в файле конфигурации.
Шаг 3. Снова выполним индексацию командой:
c:\sphinx\bin\indexer product --config c:\sphinx\data\config.txt --rotate
Как мы видим, индексация снова прошла успешно.
Пример 3: Индексация потоковых данных
В предыдущих примерах мы индексировали готовые xml-файлы, используя для этого команду
type
. Однако создавать этот файл не обязательно. Вместо команды type
можно указать любую программу, которая будет выводить данные в стандартный выходной поток.
Шаг 1. Воспользуемся Visual Studio и создадим консольное приложение SphinxSourceApp на
языке C#, которое будет выводить данные из предыдущего примера при помощи класса
XmlTextWriter
.
public static class Program
{
private const string sphinxns = "http://sphinxsearch.com";
public static void Main(string[] args)
{
var products = new[]
{
new { ID = 849, Name = "Men's Sports Shorts", Price = 24.7459M },
new { ID = 852, Name = "Women's Tights", Price = 30.9334M },
new { ID = 855, Name = "Men's Bib-Shorts", Price = 37.1209M },
};
Console.OutputEncoding = Encoding.UTF8;
using (var writer = new XmlTextWriter(Console.Out))
{
writer.WriteStartDocument();
writer.WriteStartElement("sphinx", "docset", sphinxns);
// <sphinx:schema>
writer.WriteStartElement("sphinx", "schema", sphinxns);
// <sphinx:field />
writer.WriteStartElement("sphinx", "field", sphinxns);
writer.WriteAttributeString("name", "name");
writer.WriteAttributeString("attr", "string");
writer.WriteEndElement();
// <sphinx:attr />
writer.WriteStartElement("sphinx", "attr", sphinxns);
writer.WriteAttributeString("name", "price");
writer.WriteAttributeString("type", "float");
writer.WriteEndElement();
writer.WriteEndElement();
// </sphinx:schema>
foreach (var product in products)
{
// <sphinx:document>
writer.WriteStartElement("sphinx", "document", sphinxns);
writer.WriteAttributeString("id", product.ID.ToString());
writer.WriteElementString("name", product.Name);
writer.WriteElementString("price", product.Price.ToString());
writer.WriteEndElement();
// </sphinx:document>
}
writer.WriteEndElement();
}
}
}
Скомпилируем это приложение и скопируем его в папку c:\sphinx\data\source\.
Шаг 2. Отредактируем файл конфигурации, изменив опцию xmlpipe_command
.
source product
{
type = xmlpipe2
xmlpipe_command = c:\sphinx\data\source\SphinxSourceApp.exe
}
Шаг 3. Ещё раз выполним индексацию командой:
c:\sphinx\bin\indexer product --config c:\sphinx\data\config.txt --rotate
Индексация снова прошла успешно.
Заключение
Источники данных в формате xml во многом являются более гибкими, чем источники, связанные с базами данных. Они позволяют индексировать как относительно небольшие xml-файлы, так и большие объёмы данных, генерируемые «на лету». При этом они не ограничивают вас жёстко заданной схемой, позволяя вместо этого передавать её вместе с данными.
Скачать материалы к этой статье (скрипты, файлы конфигурации, xml-файлы и приложение SphinxSourceApp)
Игорь, спасибо большое за материал. Очень нужная информация.
ОтветитьУдалитьСпасибо =) Буду продолжать =)
ОтветитьУдалитьв рунете очень скудная информация касательно sphinx. благодаря вам я сэкономлю много времени на поиске информации. спасибо вам за отличный цикл статей.
ОтветитьУдалитьПриятно читать такие отзывы =)
ОтветитьУдалитьДумаю, что 2-3 статьи про Sphinx ещё точно будет.
Только в таком формате. Иначе Sphinx не определит, где документы с их id и какие теги являются полями и атрибутами.
ОтветитьУдалитьЧерез mysql прогонять вряд ли стоит.
ОтветитьУдалитьМожно прогнать через программу которая документ преобразует "на лету" (как в третьем примере). Или вообще использовать какую-нибудь утилиту, которая выполнит xslt преобразование.
Очень неплохо!
ОтветитьУдалитьА ещё можно комбинировать в одном индексе несколько источников. Главное, чтобы схемы совпадали!
Например, основную базу проиндексировать из sql-based источника, а kill-list - из xmlpipe.
В описании индекса в этом случае будет несколько строчек source. И обрабатываться они будут сверху вниз, по порядку.
ну да, xslt неплохой вариант.
ОтветитьУдалитьНо в случае большой базы будет нещадно тормозить (ну, просто подавляющее большинство XSLT-процессоров сперва "всасывают" весь док, строят DOM и лишь потом делают процессинг. Совсем не уверен, умеет ли кто из них работать с потоком, и насколько корректно. А большую коллекцию, пожалуй, только потоком и обработаешь).
Можно написать элементарный потоковый парсер (на питоне, например). А после отладки можно его же переписать на голом C (если критична производительность)
мда. вот про это: xmlns:sphinx="http://sphinxsearch.com/" в доке ничего нет :(
ОтветитьУдалитьпри чем под линуксом работает без этого, а я под виндой мучался пока сам не понял как это прописать. непонятно правда почему. может там какой-то другой парсер используется?
Честно говоря, я добавлял схему только из-за того, что Visual Studio выдаёт предупреждения при редактировании xml-файлов, если там используется необъявленная схема. Ну и по хорошему, схему всё-таки нужно объявлять.
ОтветитьУдалитьТем не менее, без неё у меня под Windows 7 всё правильно индексировалось и работало.
у меня не проходила индексация.. никак. выдавало сообщениеоб ошибке примерно такого содержания: "Namespace prefix sphinx on docset is not defined in Entity". но потом, когда я добавил Namespace - проиндексировалось. самое интересное - я сейчас убрал его - и о чудо, все равно индексируется...
ОтветитьУдалитьи еще, чтобы два раза не вставать...
ОтветитьУдалитьпоток формирую из php.
файл выводится в поток с помощью echo $content;
где $content - содержимое xml- файла
так вот, в консоле при выводе перед содержимым выводится 'Content-type:
и соотетсвенно сфинкс выдает ошибку:
ERROR: index 'ad': xmlpipe: expected '', got 'Content-type:
раньше выводилась инфа еще и о генераторе потока: X-Powered-By
это я нашел где отключить в php.ini
там же обнулил дефолтный Content-type
теперь если раньше выводилось 'Content-type: text/html, то теперь просто 'Content-type:
если перед выводом в поток принудительно указать 'Content-type: через header(...) то он и выводится.
уже и не знаю в какую сторону копать и где плясать с бубном.
третий день мучаюсь, не могу запустить проект.. :(
1. Не понял при чём тут Content-Type, в xml файле нет никаких заголовков, на индексацию нужно передавать только контент (без заголовков), но в формате utf8
ОтветитьУдалить2. тег document - это похоже на формат xmlpipe. Он сейчас уже устаревший и не рекомендуется для использования. Лучше попробовать индексировать в xmlpipe2 (http://sphinxsearch.com/docs/2.1.1/xmlpipe2.html)
1. я знаю, я так и отправляю. вот начало документа:
ОтветитьУдалить... другие атрибуты
1
1
какой-то тайтл
9000
какой-то техт
809
810
2146
2179
2216
2227
2243
2297
2299
... еще документы
дело в том. что в поток выводится из php командой echo
и вот сам php или апач вставляет перед началом файла
X-Powered-By и Content-type
как отключить добавление X-Powered-By - я нашел в php.ini а вот с Content-type - не могу побороть никак :(
2. см.п.1 - по структуре видно что это и есть xmlpipe2
а xmlpipe_command какая?
ОтветитьУдалитьxmlpipe_command = z:\home\bixti.loc\www/localyiic sphinx XML
ОтветитьУдалитьвызывается localyiic.bat содержащим:
@echo off
rem -------------------------------------------------------------
rem Yii command line script for Windows.
rem This is the bootstrap script for running yiic on Windows.
rem -------------------------------------------------------------
@setlocal
set BIN_PATH=%~dp0
if "%PHP_COMMAND%" == "" set PHP_COMMAND=z:\usr\bin\php.exe
"%PHP_COMMAND%" "%BIN_PATH%localyiic.php" %*
@endlocal
Блин, даже не знаю, никогда так не извращался. По идее php.exe всегда будет отдавать заголовки, а не только контент. (Ну первая строка всегда будет с методом и урлом).
ОтветитьУдалитьМожно попробовать дополнительно прогонять вывод через curl для винды (вроде есть)
хорошо, а есть пример вывода данных в поток на php?
ОтветитьУдалитьпри чем данные формируются текстовой строкой а не с помощью dom или xmlwriter
Я не знаком с php.
ОтветитьУдалитьПодозреваю, что нужно использовать поток stdout как-то так:
xmlpipe_command = c:\your_path_to_php\bin\php.exe your_php_script.php
Лучше в интернете поискать, т.к. про php мало что посоветую (.NET forever =)
ну он почти так и используется :)
ОтветитьУдалитьбатник запускает z:\usr\bin\php.exe с параметром localyiic.php который принимает параметры sphinx XML, который в свою очередь вызывает файл с классом SphinxCommand и его метод XML(), и вот он уже формирует xml и выводит в поток стандартной командой echo.
а вот в поток выводит впереди эту гадость либо сам интерпретатор, либо апач. но я так и не нашел.
проблему не решил, но для разработки временно поставил костыль: сформировал и сохранил xml в файл, а в сфинксе указал этот файл источником. в общем для разработки пока пойдет.
что касается .NET я теорию знаю, сделал кучу учебных проектов, однако без опыта в .NET никто нормальных денег платить не хочет :)
вот и приходится заниматься php так там опыта больше :)
а так бы я с удовольствием работал бы с .NET :)
можно вопрос по sphinx?
ОтветитьУдалитькак получить список атрибутов, у которых есть значение?
уточню:
есть схема, описанная в xml, допустим там описано 100 атрибутов.
но реально в записанных и проиндексированных документах используются только 30 реквизитов, остальные не использовались.
вопрос: как получить список использованных атрибутов?
Странный вопрос.
ОтветитьУдалить1. Не выгружать неиспользуемые атрибуты в индекс.
2. Выбирать нужные атрибуты в select-е
3. Использовать where
Зависит от того, что значит "список использованных атрибутов"
они описаны в схеме. они динамические. на данный момент около 400 но может быть и больше. грубо говоря это параметры объявления. некоторые из них ни разу не использовались пользователями. вот мне и надо получить список атрибутов, которые использовались при подаче объявлений.
ОтветитьУдалитьselect из БД, sphinx тут не причём.
ОтветитьУдалитьмне тоже так кажется, но вот мой senior утверждает, что если нужные данные находятся в сформированном XML файле, который подается сфинксу для индексирования, то и искать надо в нем с помощью сфинкса.
ОтветитьУдалитья просмотрел api сфинкса, но ничего подобного там не нашел, кроме возможности подать строку запроса в стиле sql, но не уверен что он будет искать так как надо по xml файлу. если таки он не прав, а я прав - как ему это доказать?
Ну и пусть сам сделать попробует.
ОтветитьУдалитьспасибо за отклик.
ОтветитьУдалитьИгорь, спасибо за статью. Только вот подскажите такую вещь - сделал конфиги, сделал xml, все индексируется и ищет нормально. Но сфинкс при поиске мне возвращает только ID документов. Может ли он возвращать другие поля? Например у меня есть поле content, вот его хотелось бы достать из индекса.
ОтветитьУдалитьМожет, но для этого поле нужно ещё объявить как атрибут с тем же названием через attr="string", тогда по нему можно не только искать, но и возвращать в запросах:
ОтветитьУдалитьПотоковых парсеров полно готовых.
ОтветитьУдалитьВ Perl - XML::SAX (который по-сути является фабрикой)
XML::LibXML::SAX