Como crear tu propio buscador.
Instalación y configuración de SPHINX ( II )
Continuando el post de instalación de sphinx vamos a ver un ejemplo práctico, que es lo que estamos desenado todos.
He descargado los RFC desde la web del IETF con un script en PHP y algo de expresiones regulares, e insertados en una tabla. En total son unos 4400 documentos con mucho texto para juguetear.
La base de datos tiene una sola tabla bastante simple, aquí está el código SQL para crearla:
-
CREATE TABLE `rfc_en` (
-
`id` int(11) NOT NULL AUTO_INCREMENT,
-
`titulo` varchar(255) collate latin1_spanish_ci NOT NULL,
-
`autor` varchar(255) collate latin1_spanish_ci NOT NULL,
-
`enlace` varchar(255) collate latin1_spanish_ci NOT NULL,
-
`contenido` mediumblob NOT NULL,
-
`fecha` date NOT NULL,
-
PRIMARY KEY (`id`),
-
KEY `fecha` (`fecha`)
-
) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_spanish_ci AUTO_INCREMENT=1;
El archivo de configuracion se va a mostrar por partes.
Apartado source
-
source rfcsrc
-
{
-
type = mysql
-
sql_host = host
-
sql_user = miuser
-
sql_pass = mipass
-
sql_db = rfc
-
sql_port = 3306
-
-
sql_query_pre =
-
sql_query = \
-
SELECT id, titulo FROM rfc_en
-
sql_query_post =
-
}
-
-
source rfc2src : rfcsrc
-
{
-
sql_query =\
-
SELECT id, titulo,contenido FROM rfc_en
-
}
Se han definido dos índices, en uno se va a tener en cuenta el título de los RFC y en el otro vamos a hacer un poco el bruto indizando todo el contenido del RFC (llegan a ser 300KB o más) además del título. En el primer source se ve la información de conexión a base de datos y la sentencia sql de construcción del índice.
La sentencia se construye igual que cualquier SELECT de SQL pero con la peculiaridad de que el primer campo a recoger debe ser la clave primaria (recordar que esta DEBE ser un entero único de 32 bits). Todos los campos que aparezcan a continuación serán utilizados para el índice. De momento solo usaremos datos de texto, más adelante se detallará como introducir otro tipo de campos para búsquedas más avanzadas.
El segundo source esta definido mediante una herencia del primero, lo cual nos evita escribir 10 líneas ( los desarrolladores somos vagos y podemos estar un día escribiendo un script para evitar repetir la misma acción 2 veces
). Solo sobreescribe la sentencia SQL y se mantiene el resto.
Apartado index
-
index rfc1
-
{
-
source = rfcsrc
-
stopwords =
-
min_word_len = 1
-
charset_type = sbcs
-
path = ruta-de-instalacion/var/data/rfc1
-
morphology = none
-
}
-
-
-
index rfc2 : rfc1
-
{
-
source = rfc2src
-
path = ruta-de-instalacion/var/data/rfc2
-
}
Cada índice hace referencia a su fuente de datos definida más arriba.
Apartado indexer: simplemente forzamos a 32 megas el máximo usado por el indizador.
-
indexer
-
{
-
mem_limit = 32M
-
}
Apartado search: se ponen las rutas de los log y el descriptor de proceso. Se define el puerto asociado al demonio y otros parámetros de control. Si no se está seguro de que poner en algun sitio, dejarlo tal cual viene en el fichero esqueleto.
La directiva address es un mecanismo de seguridad, podemos forzar al demonio a que solo conteste las peticiones dirigidas a cierta ip. Si tenemos el script php en la misma máquina que sphinx se puede restringir a la direccion de loopback 127.0.0.1 y nos cubriremos las espaldas por si legan peticiones externas. Si sphinx esta instalado en una máquina diferente al origen de la consulta, pero se dispone de red local se puede asociar el demonio a la ip privada en vez de la pública.
-
searchd
-
{
-
-
# address = 127.0.0.1
-
# address = 192.168.0.1
-
port = 3312
-
log = ruta-de-instalacion/var/log/searchd.log
-
query_log = ruta-de-la-instalacion/var/log/query.log
-
read_timeout = 5
-
max_children = 30
-
pid_file =ruta-de-instalacionvar/log/searchd.pid
-
max_matches = 1000
-
}
Con esto ya tenemos fichero de configuración listo.
Así que al lio, lanzamos el indexer:
$ bin/indexer --config etc/sphinx.conf --all
indexing index 'rfc'...
collected 4398 docs, 0.2 MB
sorted 0.0 Mhits, 100.0% done
total 4398 docs, 222430 bytes
total 0.297 sec, 749621.68 bytes/sec, 14821.90 docs/sec
indexing index 'rfc2'...
collected 4398 docs, 214.1 MB
sorted 27.6 Mhits, 100.0% done
total 4398 docs, 214094137 bytes
total 22.821 sec, 9381369.37 bytes/sec, 192.72 docs/sec
En var/data estarán ahora los dos índices generados y tendrán dos tamaños bastante diferentes: 200 KB frente a 68 MB. Hay que decir que los índices tan grandes pueden ser habituales pero, al contrario que este caso, lo serán por el hecho de que las tablas indizadas tengan una cantidad de registros bastante alta en vez de tener una media de 150 KB de texto por registro.
$ ls -lh var/data/
total 70M
-rw-r--r-- 1 root root 0 2007-03-23 20:49 rfc2.spa
-rw-r--r-- 1 root root 68M 2007-03-23 20:49 rfc2.spd
-rw-r--r-- 1 root root 79 2007-03-23 20:49 rfc2.sph
-rw-r--r-- 1 root root 1,1M 2007-03-23 20:49 rfc2.spi
-rw-r--r-- 1 root root 0 2007-03-23 20:49 rfc.spa
-rw-r--r-- 1 root root 203K 2007-03-23 20:49 rfc.spd
-rw-r--r-- 1 root root 62 2007-03-23 20:49 rfc.sph
-rw-r--r-- 1 root root 23K 2007-03-23 20:49 rfc.spi
Y ahora lanzamos el demonio searchd:
$ bin/searchd --config etc/sphinx.conf
Sphinx 0.9.7-RC2
Copyright (c) 2001-2006, Andrew Aksyonoffusing config file 'etc/sphinx.conf'...
Llegados a este punto ya estamos en posicion para empezar a lanzar consultas al demonio y para ello usaremos el API PHP.
Un poquito de código:
-
function querySPHINX($tag,$off,$contenido)
-
{
-
$port = 3312;
-
-
if($contenido)
-
{
-
$indice = "rfc2";
-
}
-
else
-
{
-
$indice = "rfc";
-
}
-
if ($off == "")
-
{
-
$off = 0;
-
}
-
-
$cl = new SphinxClient ();
-
$cl->SetServer ( "localhost", $port );
-
$cl->SetLimits ($off, 10 );
-
$cl->SetMatchMode ( SPH_MATCH_ALL );
-
$resultado = $cl->Query ( $tag, $indice );
-
$num_encontrados = $resultado['total_found'];
-
-
if($num_encontrados == 0)
-
{
-
$cl->SetMatchMode ( SPH_MATCH_ANY );
-
$resultado = $cl->Query ( $tag, $indice );
-
$num_encontrados = $resultado['total_found'];
-
}
-
}
Con esta función se hace una consulta muy simple basada en la keyword a buscar, el offset de resultados a devolver (para paginar) y un booleano para decidir si consultar el contenido o no. Primero creamos el objeto y se le asocia el servidor y puerto del demonio. A continuación se le indica que queremos obtener 10 resultados a partir del offset $off (0 por omisión), se selecciona el modo de consulta y se lanza la misma.
Los modos de consulta son:
- SPH_MATCH_ALL: el documento devuelto debe contener todas las palabras de la búsqueda
- SPH_MATCH_ANY: el documento devuelto debe contener alguna de las palabras de la búsqueda
- SPH_MATCH_PHRASE: el documento devuelto debe contener todas las palabras de la búsqueda y en el mismo orden
La variable $resultado devuelve bastante información pero de momento nos quedamos con dos elementos:
- $resultado['total_found'] : numero de resultados encontrados para la búsqueda
- $resultado['matches'] : array con los identificadores de documento como índice. Por eso se recuperan las claves con array_keys
Espero que esta guía haya sido de utilidad y que la gente se anime a instalarlo y cacharrear un poco.
Podeis probar la demo. Me gustaría no estar en un hosting compartido ni tener un ping tal alto para que vierais que esto es realmente rápido. Si alguien quiere el ejemplo le puedo facilitar los scripts de construcción de la base de datos, el resto está practicamente en las entradas.
Actualizacion
He cambiado la demo de sitio, porque la gente de dreamhost me tiraba el sphinx (ya decia yo que era demasiado chollo para un plan cutre de hosting compartido instalar un servicio como si nada ). Ahora está en una máquina con buenos recursos. Así que ya no hay excusa, los tiempos deberían ser bastante buenos.
Enlaces de interés
- sphinxsearch: web oficial de la herramienta
- Martin Porter: un referente en la técnica de stemming
- Quien usa SPHINX: un buen argumento para usarlo
Septiembre 24th, 2007 at 18:22 pm
hola espero que esten bien necesito aprender de sus conocimientos y experiencias lo agradesco..atte
alejandro
Diciembre 13th, 2007 at 17:31 pm
He entendido mal o esta parte…
index rfc2 : rfc1
{
path = ruta-de-instalacion/var/data/rfc2
}
debería indicar también un
source = rfc2src
¿?
¿ En caso contrario indexaría de nuevo la misma consulta que el otro, me equivoco ?
Un saludo, y un placer la lectura y los ejemplos, asi da gusto meter mano a cosas nuevas
Diciembre 20th, 2007 at 14:47 pm
Tienes razón, marcos.
Muchas gracias por las apreciaciones y la rectificación.
Un saludo
Marzo 15th, 2008 at 0:59 am
Hola, muy buenos estos posts sobre Sphinx, te comento una duda a ver si puedes orientarme porque aun no he usado Sphinx y no quiero empezar haciendo las cosas mal.
Tengo un listado de URLS, paginas sueltas de varios dominios. Quiero indexarlas, He pensado en extraer el texto a partir del HTML, pero como es habitual, las etiquetas strong, H1, H2, em, b, etc deberian dar mas \
Marzo 23rd, 2008 at 5:26 am
Hola,
Gracias a tus artículos en español, me he decidido a probar Sphinx. Con tu ayuda más la documentación y foros de sphinxsearch.com he logrado instalarlo y que funcione correctamente, pero me falta un detalle para poder usarlo en mis sitios web: no he conseguido que, además del ID de los documentos, los resultados de la búsqueda muestren el título, breve descripción u otros datos de los mismos (ignorancia PHP).
¿Podrías orientarme al respecto? Me gustaría saber qué hay que añadirle al siguiente ejemplo (u otro similar) para que, al menos, muestre el título y descripción de los registros que se correspondan con la búsqueda realizada: http://www.sphinxsearch.com/forum/view.html?id=660
Gracias en cualquier caso y saludos cordiales:
Manuel