Sphinx
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("/"/", "\"", $_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"`