MongoDB – Buscar termo em todos os campos

Ao desenvolver uma API, certamente você se deparou com a missão de ter que fazer uma busca genérica em todos os campos do seu banco.

A primeira vista, essa missão pode assustar, pois, você deve pensar, como eu vou fazer isso? E se eu tiver vários campos, vou ter que especificar tudo na query?

Nesse artigo eu te mostro uma forma bem tranquila de fazer isso e ainda te digo os prós e contras.

O problema

Você está desenvolvendo uma API para o seu aplicativo e quer fazer uma busca “simples”, onde o usuário tem apenas um campo “buscar por…” e ao digitar um termo qualquer, a sua API tem que “se virar” para encontrar os resultados, seja o nome do usuário, idade, sexo, etc. não importa o que o usuário tenha digitado.

Recebendo o termo de busca

Normalmente, em uma API REST você vai receber esse termo pela querystring.

Seria alguma coisa como isso:

GET https://api.seuapp.com.br/v1/endpoint?search=termo

Se você estiver usando express do Node.js para escrever sua API, você vaii obter esse valor da seguinte forma:

app.get('endpoint', (request, response) => {
   const searchTerm = request.query.search;
});

Bacana, agora você já sabe obter o termo para a pesquisa, mas antes de nós fazermos a pesquisa no banco, vou te mostrar o pulo do gato para que isso tudo funcione de uma forma simples.

Criando o índice Text Search

Para resolver esse problema, o MongoDB nos proporciona a funcionalidade Text Search.

O Text Search nada mais é do que um índice múltiplo que quando a gente fizer a busca por ele. Veremos isso em breve.

Para criar esse índice, devemos rodar o seguinte comando no terminal do MongoDB.

db.suacolecao.createIndex( { campo1: "text", campo2: "text" } )

Para acessar o terminal do MongoDB (Mongo Client) você precisa estar com o daemon do banco de dados iniciado e digitar “mongo” no terminal do seu computador.

Pronto! Dessa forma você configurou que quando você fizer sua busca genérica, você vai pesquisar o termo nos campos: “campo1” e “campo2”.

Fique atento!

Digamos que sua coleção tenha 4 campos e você configurou no índice apenas 2 deles, fique atento, pois o termo será procurado apenas nos 2 campos configurados.

Como fazer a query?

Com a confiugração do índice, você “disse” para o MongoDB onde ele deve pesquisar o termo quando for procurado no índice text e para fazer essa busca, você precisa usar esse código.

mongoClient.find({ $text: { $search: searchTerm }});

Bônus: Como melhorar a sua experiência com a busca em índices

É possível que você quera excluir algum termo da busca ou até mesmo pesquisar por um termo exato.

Você pode excluir termos da sua busca, apenas colocando um sinal de menos na frente do termo.

db.suaColecao.find( { $text: { $search: "gol fusca -brasilia" } } )

Essa busca retorna tudo que contenha gol, fusca e que não contenha brasilia.

Você também pode procurar por um termo específico.

db.stores.find( { $text: { $search: "\"brasilia amarela\"" } } )

Esse código vai trazer apenas os resultados que contenham “brasilia amarela”.

Você também pode ordenar os resultados conforme o score da sua busca. O score é uma espécie de pontuação na qual identifica o quanto o termo encontrado é parecido ou igual ao termo pesquisado.

db.suaColecao.find(
   { $text: { $search: "gol fusca puma" } },
   { score: { $meta: "textScore" } }
).sort( { score: { $meta: "textScore" } } )

Outra forma

Existe ainda um outra forma de fazer algo parecido com isso, talvez seja a forma mais comum, entretanto, crio que não é a forma mais performática.

Essa forma não cria um índice específico pra isso, apenas adiciona os valores que devem ser pesquisados em um único campo, exemplo:

db.latasVelhas.insert(
   [
     { _id: 1, name: "Fusca", color: "azul porrada", tags: "Fusca azul porrada" },
     { _id: 2, name: "Bianco", color: "vermelho montana", tags: "Bianco vermelho montana" },
     { _id: 3, name: "Puma", color: "verde", tags: "Puma verde" },
     { _id: 4, name: "Brasilia", color: "amarela", tags: "Brasilia amarela" },
     { _id: 5, name: "Rural", color: "azul e branca", tags: "Rural azul e branca" }
   ]
)

E a busca seria algo como:

db.latasVelhas.find({ tags: /Puma/ });

O que muda nessa coleção é que não temos um índice configurado para fazer o trabalho pra gente, nesse caso a gente faz o trabalho do índice manualmente colocando todos os termos relevantes em um campo dentro da coleção.

Apesar de eu achar que é uma “gambiarra”, funciona! Mas veremos a seguir os prós e contras de ambas as formas.

Prós Vs. Contras

Tudo na vida tem prós e contras e com essa funcionalidade não é diferente.

Se você optar por criar o índice Text Search, a cada vez que houver a necessidade de adicionar um novo campo à busca, você terá que recriar esse índice e isso pode ser bem custoso, entretanto, seu código fica mais limpo, você não precisa escrever nada a mais ao inserir ou editar um dado, isto é, a manutenção é bem menos custosa, fora a velocidade que um índice traz para a sua aplicação.

Se você optar por ter um campo de tags com os termos relevantes, você vai aumentar o tamanho da sua base de forma desnecessária, fora toda a manutenção que esse campo vai exigir quando você inserir, mas principalmente quando precisar editar esses dados. Também sabemos que os índices foram criados para melhorar a performance e nesse caso você não os tem.

Mas nem pense em criar o campo de tags e ainda colocar um índice nele. Essa seria sua pior opção, tendo em vista que a cada modificação você precisara recriar os índices.

Exemplos

Considere a coleção abaixo:

db.latasVelhas.insert(
   [
     { _id: 1, name: "Fusca", color: "azul porrada" },
     { _id: 2, name: "Bianco", color: "vermelho montana" },
     { _id: 3, name: "Puma", color: "verde" },
     { _id: 4, name: "Brasilia", color: "amarela" },
     { _id: 5, name: "Rural", color: "azul e branca" }
   ]
)

Configure o índice Text Search:

db.latasVelhas.createIndex({ name: "text", color: "text" });

Busque por algum termo

db.latasVelhas.find( { $text: { $search: "fusca amarela" } } )

Busque por um termo específico

db.latasVelhas.find( { $text: { $search: "\"Bianco\"" } } )

Referência

Esse artigo foi escrito baseado na documentação do MongoDB que pode ser consultada no link abaixo (em inglês).

https://docs.mongodb.com/manual/text-search/

robots.txt – Restrições aos buscadores

Por quê?

Muitas vezes precisamos “esconder” determinados diretórios, arquivos e até mesmo sites inteiros dos gulosos robôs de busca, também conhecidos como robots e webcrawlers. Com um simples arquivo texto chamado robots.txt, podemos fazer isso!

Esse arquivo deve ser salvo no diretório ROOT do seu servidor web com o nome de robots.txt de forma que possa ser acessível a todos como esse exemplo:

http://www.seusite.com.br/robots.txt

O que são Robot’s?

Robot (ou robô) é um programa de computador que percorre automaticamente as páginas da Internet em busca de documentos, a fim de indexá-los, validá-los ou monitorar alterações de conteúdo.
Hoje em dia, alguns robôs estão tão avançados que podem aceitar um sitemap e até preencher um formulário para ver a saída no navegador, como é o caso do GoogleBot, motor de busca do Google.
O arquivo robots.txt foi proposto como um padrão para esses mecanismos, onde nós, desenvolvedores podemos informar diretórios e arquivos que possam ou não ser indexados por eles. Não podemos confundir com possibilidade de acesso, esse arquivo, simplesmente faz como que o conteúdo não seja indexado. Para proibir acesso, utilizamos as permissões de arquivos e/ou o arquivo .htaccess.

O arquivo robots.txt

Existem alguns sites que geram esse arquivo conforme o preenchimento de um formulário, mas vou explicar aqui como você mesmo pode criar o seu.

Basicamente ele é dividido em três partes:

  1. Definição dos motores
  2. Permissões
  3. Sitemap (somente para Yahoo, Google e MSN)
Você pode criar esse arquivo em qualquer editor de texto, como o NOTEPAD ou VI por exemplo.
1. Definições de motores
É possível setar diferentes permissões para motores de busca distintos, por exemplo, deixar o motor do Yahoo ver o diretório “produtos” mas o Google não. Essas definições são feitas com a diretiva:
User-agent

Possíveis valores para User-agent, são:

  • Google: User-agent: Googlebot
  • Yahoo: User-agent: Slurp
  • MSN: User-agent: msnbot
  • Todos os mecanismos: User-agent: *

Seguindo nosso exemplo, nosso robots.txt ficaria da seguinte forma:

User-agent:Slurp
Disallow: /
Allow: /produtos

User-agent:Googlebot
Allow: /
Disallow: /produtos

2. Permissões

As permissões são facilmente definidas pelas diretivas Allow e Disallow, para permitir e negar, respectivamente os diretórios ou arquivos sequentes.

User-agent:*
Allow:/
Disallow: /admin
Disallow: /fotos
Allow: /produtos
Allow: /artigos

O Exemplo acima utiliza o caractere “/” para definir todo o site e nega a indexação dos diretórios admin e fotos, liberando os diretórios produtos e artigos.

Assim como em expressões regulares, temos os caractéres “*” (asterísco) e “$” (sifrão), que indicam,  “qualquer coisa” e “terminado com” respectivamente, alguns exemplos abaixo:

User-agent:*
Disallow:produtos/*.png$
Allow:produtos/
Disallow:admin/*.js
Disallow:fotos/*?*cat=cordas

O Googlebot, preenche os formulários contidos na página para verificar a resposta obtida. Para bloquearmos esse tipo de ação, caso estejamos utilizando o método GET, podemos fazer da seguinte forma.

</pre>
<form action="search.php?s=" method="get">...</form>
<pre>

Nesse caso precisamos negar a indexação de “search.php?s” para isso acrescentaremos a seguinte linha na seção do googlebot no nosso robots.txt

User-agent: Googlebot
Disallow: /search.php?s=*

Quando especificar mais de um motor ele ignora o resto e passa a utilizar somente as especificações daquele motor, isto é, NÃO HÁ HERANÇA!

Meta-tag’s e links

Também é possível especificar uma META-TAG para fazer a proibição diretamente na página:

<meta name="robots" content="noindex,nofollow">
<a href="foo.php" rel="nofollow">Foo</a>

O “parâmetro” noindex, diz ao bot que o site não deve ser indexado e o parâmetro nofollow, que o site no link não deve ser seguido.
Nofollow é muito útil em seções de comentários, onde os spans abusam da nossa paciência. Essa forma não é eficaz para acabar com essa praga, mas pelo menos o eles não ganham pontos com links no Googlebot por exemplo.

Links para testar

É possível testar se os caminhos especificados no robots.txt realmente existem. Fora a identificação de prováveis erros de sintaxe em arquivos muito grandes e gerados manualmente, não vejo muita utilidade nesses caras. Para testar utilize o próprio arquivo robots do Google, até nele eles apontam erros, principalmente relacionados ao ‘*’.

http://www.searchenginepromotionhelp.com/m/robots-text-tester/robots-checker.php
http://www.frobee.com/robots-txt-check

Exemplos

Podemos visualizar alguns bons exemplos nos links abaixo:

http://www.google.com.br/robots.txt
http://www.facebook.com.br/robots.txt (leia os direitos)
http://www.marketingdebusca.com.br/robots.txt
http://www.mestreseo.com.br/robots.txt (bem humorado hehe)

Segundo o site BrPoint, o seguinte robots.txt é ideal para quem utiliza WordPress.

User-agent: *
# remova os diretorios
Disallow: /cgi-bin/
Disallow: /tag/
Disallow: /wp-admin/
Disallow: /wp-includes/
Disallow: /trackback/
Disallow: /feed/
Disallow: /tags/
User-agent: Googlebot
# remover scrips css e afins
Disallow: /*.php$
Disallow: /*.js$
Disallow: /*.inc$
Disallow: /*.css$
Disallow: /*.gz$
Disallow: /*.wmv$
Disallow: /*.cgi$
Disallow: /*.xhtml$
# qualquer endereco que contenha ?
Disallow: /*?*
# permitir o adsense em qualquer url
User-agent: Mediapartners-Google*
Disallow:
Allow: /*
#Sitemap
Sitemap: http://www.brpoint.net/sitemap.xml

Geradores de robots.txt

Fontes: