BookinglyTech News
Software

El analizador de Whoosh: por qué tu búsqueda devuelve cero resultados

La librería de búsqueda full-text en Python puro ejecuta su analizador al indexar y al consultar. Si las dos pasadas no coinciden, no hay coincidencias.

2 min de lecturaDev.to0 vistas

Whoosh, la librería de búsqueda full-text escrita en Python puro, se instala con un pip install whoosh3 y lleva años en el ecosistema como alternativa sin dependencias a motores como Elasticsearch o Solr. Su pieza menos entendida es también la que más dolores de cabeza causa: el analizador, la tubería que convierte texto en tokens buscables.

El analizador se ejecuta dos veces, al indexar y al consultar. Si las dos pasadas no coinciden en qué es una palabra, la búsqueda no devuelve nada aunque el término esté delante de tus narices. El código vive en el repositorio.

Tokenizer y filtros encadenados

Todo analizador es un tokenizer, que parte la cadena en tokens, seguido de cero o más filtros que los transforman, los descartan o añaden nuevos. Whoosh los compone con el operador |, igual que una tubería de Unix.

El fallo clásico aparece en el ejemplo de manual: dos documentos, uno con "Database connections are pooled" y otro con "Connecting to the server", y una consulta por "connect". Con StandardAnalyzer no vuelve ninguno, porque guarda las formas literales y el término buscado no aparece tal cual. Con StemmingAnalyzer vuelven los dos, porque reduce en indexación y en consulta a la raíz común. Un cambio de una palabra en el esquema separa "nuestra búsqueda está rota" de "nuestra búsqueda simplemente funciona".

Aviso honesto: el stemming es heurístico. El stemmer Porter unifica connections, connecting y connect, pero deja running en runn mientras run se queda en run, así que esos dos no se encuentran entre sí. Conviene probar con el vocabulario real del proyecto.

Acentos, filtros propios y ngrams

Si los datos llevan texto no ASCII, el usuario escribirá la versión sin tilde y esperará que case. CharsetFilter, con el mapa de acentos que viene incluido, pliega las tildes, así que "cafe" encuentra "Café". Va en la definición del campo, no en el código de la aplicación.

Un filtro propio es cualquier callable que recibe un iterador de tokens y devuelve tokens. Cinco líneas bastan para uno que descarte los que no lleguen a cierta longitud, y como es Python puro puedes expandir sinónimos, normalizar SKUs o partir camelCase. Se encaja en la tubería y compone con el resto.

Para coincidencia parcial y tolerancia a errores tipográficos está NgramFilter, que trocea cada token en ventanas de caracteres solapadas: de "search" salen sea, sear, ear y compañía, y una consulta por arch lo encuentra. Cuesta tamaño de índice, así que se reserva a los campos que lo necesitan, no a todos.

Lo que conviene retener: tokenizer primero, filtros después, y los dos lados —índice y consulta— tienen que estar de acuerdo. Cuando los resultados sorprendan, lo primero es pasar el texto por el analizador y leer los tokens antes de tocar nada más.