Sphinx: Różnice pomiędzy wersjami

 
(Nie pokazano 12 wersji utworzonych przez 3 użytkowników)
Linia 1: Linia 1:
''Sphinx'' to otwarty, w pełni tekstowy serwer wyszukiwania stworzony w C++ i udostępniony na licencji GPLv2. Najczęściej jest wykorzystywany do indeksowania danych z baz [[MySQL]], [[PostreSQL]] i specjalnie sformatowanych plików xml.
+
''Sphinx'' to otwarty, w pełni tekstowy serwer wyszukiwania stworzony w C++ i udostępniony na licencji GPLv2. Najczęściej jest wykorzystywany do indeksowania danych z baz [[MySQL]], [[PostgreSQL]] i specjalnie sformatowanych plików xml.
  
 
=== Elementy Sphinxa ===
 
=== Elementy Sphinxa ===
Linia 11: Linia 11:
 
* <code>indextool</code> - program narzędziowy do zrzucania rozmaitych;
 
* <code>indextool</code> - program narzędziowy do zrzucania rozmaitych;
 
* <code>wordbreaker</code> - program służący do rozdzielania połączonych wyrazów na oddzielne.
 
* <code>wordbreaker</code> - program służący do rozdzielania połączonych wyrazów na oddzielne.
 +
 +
=== Przykład ===
 +
 +
Poniżej znajduje się przykład indeksowania wiadomości.
 +
 +
==== Indeksowanie ====
 +
 +
===== Konfiguracja cz. 1=====
 +
 +
Dla wygodniejszej pracy zalecane jest stworzenie paru katalogów, na przykład:
 +
* <code class="directory">~/wyszukiwanie/dane/wiadomosci</code> - wszystkie artykuły;
 +
* <code class="directory">~/wyszukiwanie/index/wiadomosci</code> - wszystkie pliki stworzone podczas indeksowania;
 +
* <code class="directory">~/wyszukiwanie/conf</code> - pliki konfiguracyjne;
 +
* <code class="directory">~/wyszukiwanie/bin/wiadomosci</code> - skrypty potrzebne do indeksowania.
 +
 +
Plik konfiguracyjny <code class="directory">~/wyszukiwanie/conf/sphinx.conf</code>:
 +
<syntaxhighlight lang="text">
 +
source wiadomosci
 +
{
 +
    type                    = xmlpipe
 +
    xmlpipe_command        = ~/wyszukiwanie/bin/wiadomosci/xmlout.sh
 +
    xmlpipe_field          = content
 +
    xmlpipe_attr_string    = url
 +
    xmlpipe_attr_uint      = date
 +
    xmlpipe_fixup_utf8      = 1s
 +
}
 +
</syntaxhighlight>
 +
 +
* <code>source wiadomosci</code> - definicja źródła indeksowania identyfikowana jako ''wiadomosci''.
 +
* <code>type = xmlpipe</code> - definicja typu źródła indeksowania, tutaj to dokument XML, który jest przekazywany za pomocą pipe do indexera.
 +
* <code>xmlpipe_command = ~/wyszukiwanie/bin/news/xmlout.sh</code> - jest to komenda, która zostanie uruchomiana aby wypisała na standardowe wyjście wszystkie dokumenty XML jako jeden dokument.
 +
 +
===== Pola i atrybuty =====
 +
 +
Zawartość dokumentu, która potrzebuje być zindeksowana nazywana jest polem (field). Intuicyjnie dla wiadomości (newsów) cała treść artykułu jest polem.
 +
 +
Atrybut jest informacją powiązaną z artykułem.
 +
 +
Gdy jedno z wyszukiwań dopasuje szukane frazy do fraz zindeksowanych we wszystkich dokumentach to dla każdego dopasowanego artykułu zwracany jest sam dokument wraz z jego atrybutami.
 +
 +
<syntaxhighlight lang="text">
 +
xmlpipe_field          = content
 +
xmlpipe_attr_string    = url
 +
xmlpipe_attr_uint      = date
 +
</syntaxhighlight>
 +
 +
Powyższa zawartość określa:
 +
* Tekst w tagu XML ''content'' będzie traktowany jako pole (field), więc będzie indeksowane
 +
* Tekst w tagu XML ''url'' będzie traktowany jak atrybut tekstowy (string).
 +
* Tekst w tagu XML ''date'' będzie traktowany jak atrybut numeryczny (numerical).
 +
 +
Numeryczny atrybut daty wydaje się nieintuicyjny. Natomiast podczas szukania można zdefiniować nie tylko konkretne wartości atrybutów, ale i ich zakresy, które nie działają najlepiej z atrybutem tekstowym.
 +
 +
<syntaxhighlight lang="text">
 +
xmlpipe_fixup_utf8      = 1
 +
</syntaxhighlight>
 +
 +
Powyższa linijka sprawi, że indexer da sobie radę ze streamem XML w UTF-8.
 +
 +
===== XML =====
 +
 +
Poniżej są 2 przykładowe artykuły z następującą treścią:
 +
 +
<syntaxhighlight lang="text">
 +
Gmina Ostrowice w Zachodniopomorskiem może zostać zlikwidowana za długi. Jej zadłużenie trzykrotnie przewyższa roczne dochody.
 +
</syntaxhighlight>
 +
i
 +
<syntaxhighlight lang="text">
 +
Miasto Słupsk popadło w kłopoty finansowe, po przegranym procesie, który miastu wytoczył wyrzucony z budowy słupskiego aquaparku wykonawca. Miasto ma zapłacić firmie Termochem 24 miliony złotych.
 +
</syntaxhighlight>
 +
 +
Indexer będzie się spodziewał takiego streamu:
 +
 +
<syntaxhighlight lang="xml">
 +
<?xml version="1.0" encoding="utf-8"?>
 +
<sphinx:docset>
 +
    <sphinx:document id="1">
 +
        <url>url</url>
 +
        <date>data</date>
 +
        <content>Gmina Ostrowice w Zachodniopomorskiem może zostać zlikwidowana za długi. Jej zadłużenie trzykrotnie przewyższa roczne dochody.</content>
 +
    </sphinx:document>
 +
    <sphinx:document id="2">
 +
        <url>url</url>
 +
        <date>data</date>
 +
        <content>Miasto Słupsk popadło w kłopoty finansowe, po przegranym procesie, który miastu wytoczył wyrzucony z budowy słupskiego aquaparku wykonawca. Miasto ma zapłacić firmie Termochem 24 miliony złotych.</content>
 +
    </sphinx:document>
 +
</sphinx:docset>
 +
</syntaxhighlight>
 +
 +
Rzeczy warte zauważenia:
 +
* każdy dokument musi mieć numeryczne id,
 +
* wszystkie tagi niezaczynające się od sphinx: zostały zdefiniowane w pliku konfiguracyjnym.
 +
 +
Kolejnym etapem jest stworzenie skryptu <code class="directory">~/wyszukiwanie/bin/wiadomosci/xmlout.sh</code>, który wygeneruje powyższy XML. Poniżej znajduje się przykładowy:
 +
<syntaxhighlight lang="bash">
 +
#!/bin/bash
 +
echo "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
 +
echo "<sphinx:docset>"
 +
counter=0
 +
for filename in `find ~/wyszukiwanie/dane/wiadomosci/ -type f`
 +
do
 +
  url=`head -n 2 $filename | tail -n 1`
 +
  url=`echo $url | sed 's/&/%26/g' `
 +
  date=`head -n 1 $filename | sed 's/\-//g'`
 +
  counter=`expr $counter + 1`
 +
  lines=`wc -l $filename | awk '{print $1}'`
 +
  lines=`expr $lines - 2`
 +
  echo "<sphinx:document id=\"$counter\">"
 +
  echo "<url>$url</url>"
 +
  echo "<date>$date</date>"
 +
  echo -n "<content>"
 +
  tail -n $lines $filename | sed 's/&/and/g'
 +
  echo "</content>"
 +
  echo "</sphinx:document>"
 +
done
 +
echo "</sphinx:docset>"
 +
</syntaxhighlight>
 +
 +
===== Konfiguracja cz. 2 =====
 +
 +
Druga część konfiguracji w pliku <code class="directory">~/wyszukiwanie/conf/sphinx.conf</code>:
 +
 +
<syntaxhighlight lang="text">
 +
index wiadomosci
 +
{
 +
    source                  = wiadomosci
 +
    path                    = ~/wyszukiwanie/index/wiadomosci/idx
 +
    docinfo                = extern
 +
    mlock                  = 0
 +
    morphology              = stem_en, soundex
 +
    min_word_len            = 1
 +
    charset_type            = utf-8
 +
    html_strip              = 1
 +
}
 +
</syntaxhighlight>
 +
 +
Ta część odpowiada za definicję indeksowania wiadomości. Określa w jaki sposób indeks ma być stworzony, gdzie przetrzymywany itd.
 +
 +
* <code>source = wiadomosci</code> - definicja źródła indeksowania. Źródło ''wiadomosci'' zostało określone w pierwszej części konfiguracji.
 +
* <code>path = ~/wyszukiwanie/index/wiadomosci/idx</code> - definicja ścieżki w której zostaną zapisane wszystkie pliki stworzone przez indexera z dodanym prefixem idx.
 +
 +
Konfiguracja indexera z maksymalnym limitem na pamięć 128MB:
 +
 +
<syntaxhighlight lang="text">
 +
indexer
 +
{
 +
    mem_limit              = 128M
 +
}
 +
</syntaxhighlight>
 +
 +
 +
===== Uruchamianie indeksowania =====
 +
 +
Poniższa komenda uruchomi indeksowanie
 +
indexer --config ~/wyszukiwanie/conf/sphinx.conf --verbose wiadomosci
 +
 +
Co każde 1000 zindeksowanych dokumentów indexer wypisze powiadomienie. Po zakończeniu indeksowania zostaną wyświetlone jego statystyki.
 +
 +
==== Wyszukiwanie ====
 +
 +
Za wyszukiwanie odpowiada daemon <code>searcgd</code>. Poniżej znajduje się przykład konfiguracji oraz sposób jego uruchomienia.
 +
 +
===== Konfiguracja =====
 +
 +
Konfiguracja tak jak we wcześniejszych częściach znajduje się w pliku <code class="directory">~/wyszukiwanie/conf/sphinx.conf</code>:
 +
<syntaxhighlight lang="text">
 +
searchd
 +
{
 +
    listen                  = /home/login/wyszukiwanie/searchd.sock
 +
    log                    = /home/login/wyszukiwanie/log/searchd.log
 +
    query_log              = /home/login/wyszukiwanie/log/query.log
 +
    read_timeout            = 5
 +
    client_timeout          = 300
 +
    max_children            = 30
 +
    pid_file                = /home/login/wyszukiwanie/log/searchd.pid
 +
    max_matches            = 1000
 +
    seamless_rotate        = 1
 +
    preopen_indexes        = 1
 +
    unlink_old              = 1
 +
    mva_updates_pool        = 1M
 +
    max_packet_size        = 8M
 +
    max_filters            = 256
 +
    max_filter_values      = 4096
 +
    max_batch_queries      = 32
 +
    workers                = 4
 +
    dist_threads            = 4
 +
}
 +
</syntaxhighlight>
 +
 +
Większość z opcji jest ustawiona standardowo i nie wymaga zrozumienia.
 +
 +
===== Uruchamianie daemona =====
 +
 +
Aby uruchomić daemon <code>searchd</code> należy skorzystać z polecenia:
 +
searchd -c ~/wyszukiwanie/conf/sphinx.conf
 +
 +
===== Wyszukiwanie za pomocą PHP =====
 +
 +
Sphinx search ma bardzo dużo klientów, jednym z nich jest PHP API. Poniżej znajduje się przykład:
 +
<syntaxhighlight lang="php">
 +
<?php header('Content-Type: text/plain; charset=iso-8859-1');
 +
include('sphinxapi.php');
 +
$cl = new SphinxClient();
 +
$cl->SetServer("/home/login/wyszukiwanie/searchd.sock");
 +
$cl->SetSortMode(SPH_SORT_RELEVANCE);
 +
$results = (int)$_GET['results'];
 +
$offset = (int)$_GET['offset'];
 +
$cl->SetLimits($offset, $results);
 +
$min_date = (int)$_GET['mindate'];
 +
$max_date = (int)$_GET['maxdate'];
 +
$cl->SetFilterRange('date', $min_date, $max_date);
 +
if ($_GET['mode'] == 'All') {
 +
        $cl->SetMatchMode(SPH_MATCH_ALL);
 +
}
 +
else if ($_GET['mode'] == 'Any') {
 +
        $cl->SetMatchMode(SPH_MATCH_ANY);
 +
}
 +
else if ($_GET['mode'] == 'Phrase') {
 +
        $cl->SetMatchMode(SPH_MATCH_PHRASE);
 +
}
 +
else if ($_GET['mode'] == 'Extended') {
 +
        $cl->SetMatchMode(SPH_MATCH_EXTENDED);
 +
}
 +
$keywords = preg_replace("/&quot;/", "\"", $_GET['keywords']);
 +
$result = $cl->Query( $keywords, $_GET['index'] );
 +
if ( $result === false ) {
 +
    echo "ERROR|Query failed: " . $cl->GetLastError() . "\n";
 +
}
 +
else {
 +
    if ( $cl->GetLastWarning() ) {
 +
        echo "WARNING|" . $cl->GetLastWarning() . "\n";
 +
    }
 +
    if ( ! empty($result['matches']) ) {
 +
        print_r($result['matches']);
 +
    }
 +
}
 +
?>
 +
</syntaxhighlight>
 +
 +
i przykładowe użycie za pomocą curl:
 +
curl "http://''login''.usermd.net/sphinx/search.php?keywords=Anna+Team&results=20&offset=0&mindate=20090101&maxdate=20140806&mode=Phrase&index=news"
  
 
=== Odnośniki Zewnętrzne ===
 
=== Odnośniki Zewnętrzne ===
 
* [http://sphinxsearch.com/ Sphinx Search]
 
* [http://sphinxsearch.com/ Sphinx Search]
  
[[Kategoria:Bazy Danych]]
+
[[Kategoria:Bazy danych]]

Aktualna wersja na dzień 14:02, 20 wrz 2017

Sphinx to otwarty, w pełni tekstowy serwer wyszukiwania stworzony w C++ i udostępniony na licencji GPLv2. Najczęściej jest wykorzystywany do indeksowania danych z baz MySQL, PostgreSQL i specjalnie sformatowanych plików xml.

Elementy Sphinxa

Sphinx składa się z następujących elementów:

  • indexer - narzędzie do tworzenia pełnotekstowych indeksów (indices);
  • searchd - demon do przeszukiwania indeksów przez zewnętrzne aplikacji (np. skrypty www komunikujące się przez API, MySQ z SphinxSE itp.), jest uruchomiony na serwerze;
  • sphinxapi - zbiór bibliotek udostępniających API Sphinxa dla PHP, Pythona, Javy, Perla, czy Rubiego;
  • spelldump - proste narzędzie do wydobywania pozycji ze słownika ispell lub MySpell służące do dostosowania indexu;
  • indextool - program narzędziowy do zrzucania rozmaitych;
  • wordbreaker - program służący do rozdzielania połączonych wyrazów na oddzielne.

Przykład

Poniżej znajduje się przykład indeksowania wiadomości.

Indeksowanie

Konfiguracja cz. 1

Dla wygodniejszej pracy zalecane jest stworzenie paru katalogów, na przykład:

  • ~/wyszukiwanie/dane/wiadomosci - wszystkie artykuły;
  • ~/wyszukiwanie/index/wiadomosci - wszystkie pliki stworzone podczas indeksowania;
  • ~/wyszukiwanie/conf - pliki konfiguracyjne;
  • ~/wyszukiwanie/bin/wiadomosci - skrypty potrzebne do indeksowania.

Plik konfiguracyjny ~/wyszukiwanie/conf/sphinx.conf:

source wiadomosci
{
    type                    = xmlpipe
    xmlpipe_command         = ~/wyszukiwanie/bin/wiadomosci/xmlout.sh
    xmlpipe_field           = content
    xmlpipe_attr_string     = url
    xmlpipe_attr_uint       = date
    xmlpipe_fixup_utf8      = 1s
}
  • source wiadomosci - definicja źródła indeksowania identyfikowana jako wiadomosci.
  • type = xmlpipe - definicja typu źródła indeksowania, tutaj to dokument XML, który jest przekazywany za pomocą pipe do indexera.
  • xmlpipe_command = ~/wyszukiwanie/bin/news/xmlout.sh - jest to komenda, która zostanie uruchomiana aby wypisała na standardowe wyjście wszystkie dokumenty XML jako jeden dokument.
Pola i atrybuty

Zawartość dokumentu, która potrzebuje być zindeksowana nazywana jest polem (field). Intuicyjnie dla wiadomości (newsów) cała treść artykułu jest polem.

Atrybut jest informacją powiązaną z artykułem.

Gdy jedno z wyszukiwań dopasuje szukane frazy do fraz zindeksowanych we wszystkich dokumentach to dla każdego dopasowanego artykułu zwracany jest sam dokument wraz z jego atrybutami.

xmlpipe_field           = content
xmlpipe_attr_string     = url
xmlpipe_attr_uint       = date

Powyższa zawartość określa:

  • Tekst w tagu XML content będzie traktowany jako pole (field), więc będzie indeksowane
  • Tekst w tagu XML url będzie traktowany jak atrybut tekstowy (string).
  • Tekst w tagu XML date będzie traktowany jak atrybut numeryczny (numerical).

Numeryczny atrybut daty wydaje się nieintuicyjny. Natomiast podczas szukania można zdefiniować nie tylko konkretne wartości atrybutów, ale i ich zakresy, które nie działają najlepiej z atrybutem tekstowym.

xmlpipe_fixup_utf8      = 1

Powyższa linijka sprawi, że indexer da sobie radę ze streamem XML w UTF-8.

XML

Poniżej są 2 przykładowe artykuły z następującą treścią:

Gmina Ostrowice w Zachodniopomorskiem może zostać zlikwidowana za długi. Jej zadłużenie trzykrotnie przewyższa roczne dochody.

i

Miasto Słupsk popadło w kłopoty finansowe, po przegranym procesie, który miastu wytoczył wyrzucony z budowy słupskiego aquaparku wykonawca. Miasto ma zapłacić firmie Termochem 24 miliony złotych.

Indexer będzie się spodziewał takiego streamu:

<?xml version="1.0" encoding="utf-8"?>
<sphinx:docset>
    <sphinx:document id="1">
        <url>url</url>
        <date>data</date>
        <content>Gmina Ostrowice w Zachodniopomorskiem może zostać zlikwidowana za długi. Jej zadłużenie trzykrotnie przewyższa roczne dochody.</content>
    </sphinx:document>
    <sphinx:document id="2">
        <url>url</url>
        <date>data</date>
        <content>Miasto Słupsk popadło w kłopoty finansowe, po przegranym procesie, który miastu wytoczył wyrzucony z budowy słupskiego aquaparku wykonawca. Miasto ma zapłacić firmie Termochem 24 miliony złotych.</content>
    </sphinx:document>
</sphinx:docset>

Rzeczy warte zauważenia:

  • każdy dokument musi mieć numeryczne id,
  • wszystkie tagi niezaczynające się od sphinx: zostały zdefiniowane w pliku konfiguracyjnym.

Kolejnym etapem jest stworzenie skryptu ~/wyszukiwanie/bin/wiadomosci/xmlout.sh, który wygeneruje powyższy XML. Poniżej znajduje się przykładowy:

#!/bin/bash
echo "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
echo "<sphinx:docset>"
counter=0
for filename in `find ~/wyszukiwanie/dane/wiadomosci/ -type f`
do
   url=`head -n 2 $filename | tail -n 1`
   url=`echo $url | sed 's/&/%26/g' `
   date=`head -n 1 $filename | sed 's/\-//g'`
   counter=`expr $counter + 1`
   lines=`wc -l $filename | awk '{print $1}'`
   lines=`expr $lines - 2`
   echo "<sphinx:document id=\"$counter\">"
   echo "<url>$url</url>"
   echo "<date>$date</date>"
   echo -n "<content>"
   tail -n $lines $filename | sed 's/&/and/g'
   echo "</content>"
   echo "</sphinx:document>"
done
echo "</sphinx:docset>"
Konfiguracja cz. 2

Druga część konfiguracji w pliku ~/wyszukiwanie/conf/sphinx.conf:

index wiadomosci
{
    source                  = wiadomosci
    path                    = ~/wyszukiwanie/index/wiadomosci/idx
    docinfo                 = extern
    mlock                   = 0
    morphology              = stem_en, soundex
    min_word_len            = 1
    charset_type            = utf-8
    html_strip              = 1
}

Ta część odpowiada za definicję indeksowania wiadomości. Określa w jaki sposób indeks ma być stworzony, gdzie przetrzymywany itd.

  • source = wiadomosci - definicja źródła indeksowania. Źródło wiadomosci zostało określone w pierwszej części konfiguracji.
  • path = ~/wyszukiwanie/index/wiadomosci/idx - definicja ścieżki w której zostaną zapisane wszystkie pliki stworzone przez indexera z dodanym prefixem idx.

Konfiguracja indexera z maksymalnym limitem na pamięć 128MB:

indexer
{
    mem_limit               = 128M
}


Uruchamianie indeksowania

Poniższa komenda uruchomi indeksowanie

indexer --config ~/wyszukiwanie/conf/sphinx.conf --verbose wiadomosci

Co każde 1000 zindeksowanych dokumentów indexer wypisze powiadomienie. Po zakończeniu indeksowania zostaną wyświetlone jego statystyki.

Wyszukiwanie

Za wyszukiwanie odpowiada daemon searcgd. Poniżej znajduje się przykład konfiguracji oraz sposób jego uruchomienia.

Konfiguracja

Konfiguracja tak jak we wcześniejszych częściach znajduje się w pliku ~/wyszukiwanie/conf/sphinx.conf:

searchd
{
    listen                  = /home/login/wyszukiwanie/searchd.sock
    log                     = /home/login/wyszukiwanie/log/searchd.log
    query_log               = /home/login/wyszukiwanie/log/query.log
    read_timeout            = 5
    client_timeout          = 300
    max_children            = 30
    pid_file                = /home/login/wyszukiwanie/log/searchd.pid
    max_matches             = 1000
    seamless_rotate         = 1
    preopen_indexes         = 1
    unlink_old              = 1
    mva_updates_pool        = 1M
    max_packet_size         = 8M
    max_filters             = 256
    max_filter_values       = 4096
    max_batch_queries       = 32
    workers                 = 4
    dist_threads            = 4
}

Większość z opcji jest ustawiona standardowo i nie wymaga zrozumienia.

Uruchamianie daemona

Aby uruchomić daemon searchd należy skorzystać z polecenia:

searchd -c ~/wyszukiwanie/conf/sphinx.conf
Wyszukiwanie za pomocą PHP

Sphinx search ma bardzo dużo klientów, jednym z nich jest PHP API. Poniżej znajduje się przykład:

<?php header('Content-Type: text/plain; charset=iso-8859-1');
include('sphinxapi.php');
$cl = new SphinxClient();
$cl->SetServer("/home/login/wyszukiwanie/searchd.sock");
$cl->SetSortMode(SPH_SORT_RELEVANCE);
$results = (int)$_GET['results'];
$offset = (int)$_GET['offset'];
$cl->SetLimits($offset, $results);
$min_date = (int)$_GET['mindate'];
$max_date = (int)$_GET['maxdate'];
$cl->SetFilterRange('date', $min_date, $max_date);
if ($_GET['mode'] == 'All') {
        $cl->SetMatchMode(SPH_MATCH_ALL);
}
else if ($_GET['mode'] == 'Any') {
        $cl->SetMatchMode(SPH_MATCH_ANY);
}
else if ($_GET['mode'] == 'Phrase') {
        $cl->SetMatchMode(SPH_MATCH_PHRASE);
}
else if ($_GET['mode'] == 'Extended') {
        $cl->SetMatchMode(SPH_MATCH_EXTENDED);
}
$keywords = preg_replace("/&quot;/", "\"", $_GET['keywords']);
$result = $cl->Query( $keywords, $_GET['index'] );
if ( $result === false ) {
    echo "ERROR|Query failed: " . $cl->GetLastError() . "\n";
}
else {
    if ( $cl->GetLastWarning() ) {
        echo "WARNING|" . $cl->GetLastWarning() . "\n";
    }
    if ( ! empty($result['matches']) ) {
        print_r($result['matches']);
    }
}
?>

i przykładowe użycie za pomocą curl:

curl "http://login.usermd.net/sphinx/search.php?keywords=Anna+Team&results=20&offset=0&mindate=20090101&maxdate=20140806&mode=Phrase&index=news"

Odnośniki Zewnętrzne