Introducción

Un buscador interno de un sitio es una utilidad para que los usuarios puedan localizar la información que necesitan en nuestro sitio. Por ejemplo, Google oferta un Site Search con un precio anual de 100 $USD para un límite de 20.000 consultas anuales (en Junio 2011). Por otro lado una simple consulta en Internet nos dará un montón de estas utilidades incluso gratis o limitadas a un número pequeño de consultas.

Mis primeras experiencias con el desarrollo web fueron a finales del 2007. Inicialmente empecé usando un editor de texto cualquiera y probando mis primeros HTML's en un navegador. Luego use unentorno de desarrollo integrado Un entorno de desarrollo integrado (o IDE: integrated development environment) es un programa que agrupa un conjunto de herramientas para facilitar la tarea del diseño y desarrollo de sistemas informáticos. Los que se relacionan con el desarrollo web se componen en esencia de un editor de código que permite escribir HTML, CSS o lenguajes de script. Especialmente significativo es que van depurando los errores que encuentran, algo muy importante si queremos escribir, por ejemplo, XHTML que luego sea validado con los estándares W3C. pero aún comprobando con un navegador en forma local los resultados. Cuando digo en "forma local" me refiero a sin ni siquiera usar un servidor local, sino tirando directamente de los documentos con el navegador. Así completé el glosario XHTML+CSS. Al final pensé que podía hacer un buscador para facilitar la tarea de localizar términos en ese glosario. Y de paso seguir avanzando con el lenguaje Javascript. El siguiente paso fue instalar un servidor local (para funcionar en modo localhost) y seguir aprendiendo los scripts de servidor como PHP o JSP y el funcionamiento de un servidor. Luego en mayo 2010 subí este sitio a un servidor real.

Digo esto porque cuando hice ese buscador nunca pensé que podría ser útil cuando tuviera un servidor. Creía entonces que un buen buscador interno debería apoyarse en lenguajes de servidor, algo así como una lista de términos de búsqueda almacenados en una base de datos (como MySql) y getionados por un script de servidor (como PHP). Además ese buscador que hice para Javascript tiene algunas limitaciones que luego comentaremos, pero al final resulta que hasta ahora no he podido (o no he sabido) hacer otro apoyado en el servidor y aquel sigue funcionando.

Las limitaciones del buscador interno

Podemos apreciar la ejecución de un programa que busca coincidencias en documentos como esta captura de pantalla:

Rendimiento CPU

Se trata del administrador de tareas de Windows XP, que en este caso nos da el uso de la CPU. Mientras no hay tareas en ejecución la gráfica nos indica poco uso de la CPU. Pero en el momento en que lanzamos el buscador del Explorador de carpetas de Windows XP para buscar algún texto en un archivo, el uso de la CPU asciende vertiginosamente. Y se mantiene alto mientras prosiga esa búsqueda. Un servidor tiene más prestaciones que mi ordenador, pero al final el problema se reduce a lo mismo: el coste de la ejecución en la búsqueda.

Además para plantearse un buscador interno con un servidor Apache y usando PHP hay que tener en cuenta la limitación en los tiempos de ejecución. Por un lado PHP dispone de la opción de configuración


; Maximum execution time of each script, in seconds
; http://php.net/max-execution-time
; Note: This directive is hardcoded to 0 for the CLI SAPI
max_execution_time = 30

Se trata del tiempo durante el cual puede estar ejecutándose un script PHP. Al alcanzar ese límite se lanzará un error. Suele establecerse por defecto en 30 segundos. Es evidente que hay que limitar esa ejecución pues en otro caso se impediría o reduciría los recursos de ejecución a otros usuarios del servidor.

Ese máximo tiempo de PHP puede modificarse con set_time_limit. E incluso puede establecerse a cero, en cuyo caso no hay límite en este tiempo, pero no es aconsejable por varios motivos. Por un lado si por alguna razón el script entra en un bucle infinito, seguirá indefinidamente ejecutándose. A menos que entre en juego el tiempo máximo de ejecución del servidor. En la ayuda de Apache para la directiva TimeOut que se establece por defecto en 300 segundos, vemos que sólo es modificable en los contextos server config, virtual host, es decir, en los archivo de configuración httpd.conf y httpd-vhosts.conf para los dominios virtuales.

Por lo tanto el primer paso es fijarse un mecanismo para que nuestro buscador interno consuma la menor cantidad de recursos posibles y, al mismo tiempo, procure alcanzar la finalidad del mismo. Así que nos olvidamos de alterar los tiempos de ejecución de PHP o el servidor y vamos a montar un buscador lo más "liviano" que podamos.

Buscador interno basado en índices

En primer lugar limitamos el alcance de nuestro buscador sólo a información estática o semiestática. Es decir, a documentos HTML o PHP que contengan la información. Otra cosa es cuando esa información se encuentra almacenada en otro soporte como bases de datos o archivos de texto y, en el momento de enviársela al usuario generamos un HTML con esa información. Para esto las soluciones que propongo aquí no nos sirve. En el caso de este sitio tengo la información en documentos HTML/PHP que sí contienen información estática. Veámos algunas soluciones que inicialmente podríamos plantearnos.

Podríamos usar un algoritmo recursivo que, en cada lanzamiento de una búsqueda, realice un recorrido por el árbol de carpetas y archivos. Así podríamos limitarlo a que busque documentos con extensiones html y php, abrir el contenido de texto de esos archivos y buscar con expresiones regulares la aparición en ese texto de una coincidencia con la cadena de búsqueda. Esta solución tiene como ventaja que siempre realizará una busqueda sobre el contenido de los documentos actuales. Pero en cuestión de recursos deja mucho que desear. Por un lado el algoritmo recursivo consume mucha memoria al usar mecanismos como las pilas de memoria para almacenar las llamadas. Por otro lado no es cómodo para manejar los tiempos máximos de ejecución que expusimos en el apartado anterior.

Los algoritmos recursivos

Un algoritmo recursivo es una función o método que se llama a sí misma para llegar a la solución. Por ejemplo supongamos que queremos resolver la operación (1+(2*3))/(4-2) con una función recursiva llamada calcula(expresion). El algoritmo sería tal que cada vez que encuentre algo entre paréntesis volvería a llamarse a sí misma para calcularlo. Por lo tanto tras la primera llamada calcula((1+(2*3))/(4-2)) se producen 2 llamadas: calcula(1+(2*3)) y calcula(4-2). Las llamadas finalizan cuando ya no hay más paréntesis, por lo que la segunda devolvería el resultado 2. Mientras que la primera tendría que seguir para calcular 2*3. En los retornos de las llamadas se van realizando las operaciones y al final la primera llamada devuelve el resultado final. Aunque este ejemplo es muy simple, los algoritmos recursivos son muy eficaces para recorrer estructuras de árbol como la de las carpetas y archivos de una web. Aunque eficaces, no siempre son eficientes pues a veces se plantean los problemas de volcado de pila al no haber capacidad para almacenar todas las llamadas.

La otra solución pasa por construir previamente un archivo de índices que podemos considerarlo como estático. Si agregamos algún nuevo documento a nuestro sitio podemos también incluirlo en ese índice. Entonces las búsquedas se realizan sobre este archivo y así podemos controlar que el tiempo máximo de ejecución en el peor de los casos no supere cierto límite.

El índice podría ser una lista (quizás muy larga) de palabras claves, las cuáles podemos extraer de los elementos <meta name="keywords" content="..." />, probablemente también agregando otras que formen parte de la URL del documento, de otros elementos como <meta name="description" ... /> o de los encabezados <h1>, <h2>, etc. Podríamos almacenarlas en una estructura donde cada palabra clave apuntara al documento o documentos donde puede encontrarse. Quizás ordenaríamos esa estructura alfabéticamente por la palabra clave para hacer más rápida la búsqueda. El archivo de índices podrían ser representado como líneas de texto, por ejemplo:

...
table       /abc/def/ghi.html
tbody       /abc/def/ghi.html, /ijk/lmn.html
...

Así si la cadena de búsqueda contiene "table" o "tbody" devolveríamos vínculos a las URL de ese índice. Como una URL puede pertenecer a más de una clave, podríamos aligerar el índice de tal forma que estas URL se identificaran con una clave númerica:

...
table       86
tbody       86,12
...

Así tendríamos otro archivo de URL's donde resolveríamos los índices encontrados. Todo esto podría estar en una base de datos y usar la facilidad de las consultas SQL. Pero si no sabemos o no queremos basarnos en bases de datos podemos usar archivos de texto que contengan los índices. Y esta es la solución que he seguido, por el momento.

Los buscadores y los vínculos internos al documento

Una de las cosas que se hecha de menos en los buscadores es que sólo te dan un enlace al documento pero no a sus vínculos internos a elementos del propio documento. No me refiero a vínculos o enlaces internos a otros documentos del sitio, sino a vínculos que apuntan al propio documento a un elemento identificado con un atributo id, especialmente los encabezados <h1>, <h2>, .... Estos vínculos serían de la forma <a href="xxx#interno">.... ¿Porqué los buscadores no usan estos vínculos?. Supongamos que deseamos buscar el término tabla "reformateo dinámico" en un buscador (Google, Yahoo, etc.). Si lo buscamos en español nos aparece algo como esto en Google:

Buscar reformateo dinámico en google

El término "reformateo dinámico" aparece en el documento del W3C sobre la especificación de HTML4.01 Dynamic Reformating. Este vínculo realmente nos llevaría a un encabezado interno del documento:

http://www.w3.org/TR/1999/REC-html401-19991224/appendix/notes.html#h-B.5.1.1

Lo que aparece en la imagen en primer lugar es una traducción al español de esa especificación. A continuación está mi página sobre tablas. Aunque en mi página si existe ese término "reformateo dinámico" como palabra clave (keywords), es posible que Google no siga exactamente este elemento de metainformación. Porque la primera página no contiene palabras claves y también está indexada por ese término. Sea como fuere el caso es que ese documento es bastante largo. El vínculo sólo nos lleva al documento, por lo que a continuación tenemos que buscarlo a pulso (o usando el buscador del navegador).

No es fácil encontrar resultados de búsqueda en Google con enlaces internos. Pero si los ofrece, como se puede comprobar haciendo la búsqueda de estos términos:

  • "unicode utf-8", con un resultado de unicode.org
  • "w3c table", con un resultado de w3c.org
  • "google code table", con un resultado de code/google.com, un subdominio de google.com
  • "wikipedia table", con un resultado de wikipedia.org

Por ejemplo, la búsqueda de "w3c table" nos dará el primer resultado, donde se puede comprobar que los tres vínculos inferiores contienen un enlace a un elemento del documento (un "#"):

Buscar table en google

Por ejemplo, el primero apunta a http://www.w3.org/TR/html4/struct/tables.html#h-11.1. Manejar vínculos internos para todos los resultados puede suponer una exceso de procesamiento para estos buscadores. Pero para un sitio web donde la información de los documentos se estructura en torno a los encabezados (como sucede con éste sitio), parece razonable pensar que el buscador interno debe darle la facilidad al usuario de ir exactamente al lugar del documento que solicitó en su búsqueda.

Buscadores internos que procesan en el cliente o en el servidor

En este sitio ya tenía en funcionamiento un buscador para el tema del glosario XHTM+CSS:

Buscar reformateo dinámico en wextensible 1

Se trata de un buscador hecho sólo con Javascript y funcionando única y exclusivamente en el navegador del cliente. El vínculo del resultado lleva a un encabezado identificado, es decir, la ruta contiene el vínculo interno:

/temas/xhtml-css/tabla.html#reformateo-dinamico

Ahora he implementado un nuevo buscador con PHP que procesa la búsqueda en el servidor y remite los resultados al navegador:

Buscar reformateo dinámico en wextensible 2

Ofrece también un enlace a los vínculos internos pero además expone otra información, como el título de la página, su URL, la descripción y las palabras claves (obtenidas de los elementos <meta name="description"> y <meta name="keywords">).

En los capítulos siguientes se exponen los pormenores de ambos casos. Pero en este tema de buscadores aún tengo mucho que aprender. Especialmente en lo ya señalado de almacenar la información de búsqueda en una base de datos y usar consultas SQL para recuperar la información, auque por ahora es un tema pendiente.