Pełnotekstowe wyszukiwanie w Pythonie z Solr i Lucene

Solr to serwer pełnotekstowego wyszukiwania oparty o Lucene. Solr poprzez interfejs XML/JSON/HTTP umożliwia indeksowanie, wyszukiwanie, aktualizowanie i kasowanie danych. Zastosowana biblioteka Lucene zapewnia bogaty zestaw funkcjonalności. Zarówno Lucene jak i Solr napisane są w Javie, lecz niezależny od języka interfejs umożliwia korzystanie z serwera z poziomu praktycznie każdego języka programistycznego. Jeżeli rozwiązania takie jak Sphinx stają się zbyt "słabe" to Solr może okazać się dobrą alternatywą.

Zalety Solr

  • Wieloplatformowy i łatwo dostępny (Java, REST)
  • Aktywnie rozwijany (Apache Foundation)
  • Replikacja, import CSV, obsługa synonimów, podświetlanie trafień
  • Szybki i wydajny serwer
  • Używany m.in. przez digg.com, wikipedię, sourceforge, gamespot
Wadą jest fakt iż to 100% Javy i wymaga serwera servletów i całej "infrastruktury" oprogramowania związanej z nią.

Instalacja i uruchomienie Solr

  • By solr działał potrzebna jest zainstalowana wirtualna maszyna Javy
  • Pobieramy najnowszą wersję Solr
  • Rozpakuj archiwum i przjedź do apache-solr-*/example i wykonaj polecenie:
    java -jar start.jar
  • Jeżeli wszystkie zależności są spełnione i nie wystąpią nieoczekiwane problemy to serwer Solr zbudowany z serwerem Jetty powinien się uruchomić. Panel admin Solr powinien być dostępny pod adresem http://0.0.0.0:8983/solr/admin/.
    solr1

Indeksowanie i wyszukiwanie

Schemat indeksu

Do tworzenia określonych indeksów Solr używa schematów określających strukturę i sposób obsługi indeksowanych danych. Oto przykład prostego schematu:
<fields>
  <field name="id" type="string" indexed="true" stored="true" required="true" />
  <field name="title" type="text" indexed="true" stored="true"/>
  <field name="description" type="text" indexed="true" stored="true"/>
  <field name="text" type="text" indexed="true" stored="true"/>
  </fields>
  <uniqueKey>id</uniqueKey>
Określamy pola nadając im nazwy, typ, a także dodatkowe parametry jak indexed określające czy pole ma być indeksowane, stored daje możliwość pobierania wartości danego pola w wynikach wyszukiwania.

Edytuj solr/conf/schema.xml i znajdź:
 <fields>
   <!-- Valid attributes for fields:
     name: mandatory - the name for the field
     type: mandatory - the name of a previously defined type from the <types> section
     indexed: true if this field should be indexed (searchable or sortable)
     stored: true if this field should be retrievable
     compressed: [false] if this field should be stored using gzip compression
       (this will only apply if the field type is compressable; among
       the standard field types, only TextField and StrField are)
     multiValued: true if this field may contain multiple values per document
     omitNorms: (expert) set to true to omit the norms associated with
       this field (this disables length normalization and index-time
       boosting for the field, and saves some memory).  Only full-text
       fields or fields that need an index-time boost need norms.
   -->

.....
 <uniqueKey>id</uniqueKey>
Zastąp całość:
<fields>
  <field name="id" type="string" indexed="true" stored="true" required="true" />
  <field name="title" type="text" indexed="true" stored="true"/>
  <field name="description" type="text" indexed="true" stored="true"/>
  <field name="text" type="text" indexed="true" stored="true"/>
  </fields>
  <uniqueKey>id</uniqueKey>
Znajdź i usuń definicje starego schematu:
   <copyField source="id" dest="sku"/>

   <copyField source="cat" dest="text"/>
   <copyField source="name" dest="text"/>
   <copyField source="name" dest="nameSort"/>
   <copyField source="name" dest="alphaNameSort"/>
   <copyField source="manu" dest="text"/>
   <copyField source="features" dest="text"/>
   <copyField source="includes" dest="text"/>

   <copyField source="manu" dest="manu_exact"/>
Zrestartuj serwer Solr.

Dodawanie i aktualizowanie danych

Aby dodać dane należy wysłać POSTem pod /solr/update/ dane w postaci XML:
<add><doc>
<field name="nazwapola">wartość</field>
<field name="nazwapola">wartość</field>
<field name="nazwapola">wartość</field>
</doc></add>
Oczywiście elementów ("doc") może być więcej niż jeden. Aktualizacja następuję w momencie wysłania dokumentu o istniejącej już w indeksie wartości unikalnego pola (zazwyczaj "id"). Dodatkowo by wszystkie zmiany "weszły w życie" należy wykonać operację wysyłając
<commit/>

Wyszukiwanie

By pobrać wyniki wyszukiwania należy wykonać żądanie GET adresu /solr/select z parametrami takimi jak:
  • q=NAZWA_POLA:SZUKANA_FRAZA - szukanie podanej frazy w danych z podanego pola
  • fl=POLE1,POLE2,POLEn - jakie pola mają być zwrócone wraz z wynikami

Kasowanie rekordów z indeksu

Aby usunąć rekord należy wysłać POSTem pod /solr/update/ dane w postaci XML:
<delete>
<id>WARTOŚC</id>
</delete>
Gdzie id to pole o unikalnej wartości. Istnieje możliwość też kasowania dla różnych warunków, a nie pojedynczych rekordów.

Obsługa Solr z poziomu Pythona

Oto prosty skrypt dodający dane i wyszukujący frazy dla podanego wcześniej schematu:
from httplib import HTTPConnection

def add(item_id, title, description, text):
	DATA = '''<add><doc>
<field name="id">%d</field>
<field name="title">%s</field>
<field name="description">%s</field>
<field name="text">%s</field>
</doc></add>
<commit/>''' % (item_id, title, description, text)
	con = HTTPConnection('0.0.0.0:8983')
	con.putrequest('POST', '/solr/update/')
	con.putheader('content-length', str(len(DATA)))
	con.putheader('content-type', 'text/xml; charset=UTF-8')
	con.endheaders()
	con.send(DATA)
	r = con.getresponse()
	if str(r.status) == '200':
		print r.read()
	else:
		print r.status
		print r.read()

def search(key):
	con = HTTPConnection('0.0.0.0:8983')
	con.putrequest('GET', '/solr/select?q=title:%s&start=0&rows=2&fl=id,title,description' % key)
	con.endheaders()
	con.send('')
	r = con.getresponse()
	if str(r.status) == '200':
		print r.read()
	else:
		print r.status
		print r.read()

add(1, 'Django search engine', 'blaaaa', 'text')
add(2, 'Google search', 'blaaa', 'text')
add(3, 'Solr fulltext search engine', 'blaaaa', 'text')
add(4, 'Car engine', 'blaaaa', 'text')

search('engine')
Wynik jego działania to:
<?xml version="1.0" encoding="UTF-8"?>
<response>
<lst name="responseHeader"><int name="status">0</int><int name="QTime">70</int></lst>
</response>

<?xml version="1.0" encoding="UTF-8"?>
<response>
<lst name="responseHeader"><int name="status">0</int><int name="QTime">2</int></lst>
</response>

<?xml version="1.0" encoding="UTF-8"?>
<response>
<lst name="responseHeader"><int name="status">0</int><int name="QTime">2</int></lst>
</response>

<?xml version="1.0" encoding="UTF-8"?>
<response>
<lst name="responseHeader"><int name="status">0</int><int name="QTime">3</int></lst>
</response>

<?xml version="1.0" encoding="UTF-8"?>
<response>
<lst name="responseHeader"><int name="status">0</int><int name="QTime">77</int><lst name="params"><str name="fl">id,title,description</str><str name="start">0</str><str name="q">title:engine</str><str name="rows">2</str></lst></lst>
<result name="response" numFound="3" start="0">
	<doc>
		<str name="description">blaaaa</str>
		<str name="id">4</str><str name="title">Car engine</str>
	</doc>
	<doc>
		<str name="description">blaaaa</str>
		<str name="id">1</str><str name="title">Django search engine</str>
	</doc>
</result>
</response>

W sieci

Enterprise search with PHP and Apache Solr
Search smarter with Apache Solr, Part 1: Essential features and the Solr schema
Search smarter with Apache Solr, Part 2: Solr for the enterprise
Prezentacje na slideshare
RkBlog

Podstawy Pythona, 28 July 2008

Comment article
Comment article RkBlog main page Search RSS Contact