Google tiene muchas API’s para hacer que para poder interactuar con todos los productos. Una de ellas es la posibilidad de tener un buscador personalizado, y hacer llamadas con AJAX para obtener los resultados más relevantes.

Demo

Demo del uso de la API Google Custom Search
Resultado final del experimento con la API Google Custom Search

Cómo agregarlo

Sé que os encanta ir a lo práctico, así que empezaremos por ello:

1- Activar la API

Vamos a la consola de apis de Google, iniciamos sesión y hacemos click en Services › Custom Search API.

Activando la API de Google Custom Search
Activando a api de Google Custom Search. Click para ampliar.

2- Consiguiendo una API Key

Google usa API Keys para evitar abusos con las API. Para obtener una, debes de entrar en API Access, y hacer click en Create a new Browser Key. Allí deberás de introducir los referers válidos (los sitios desde los que google de dejará llamar a tu API). Lo normal es usar algo como tusitio.com/* y *.tusitio.com/* (tusitio.com también puede ser tusitio.blogspot.com).

Creando una API Key desde Google APIs Console
Creando una API Key desde Google APIs Console. Click en la imagen para ampliar.

3- Creando un motor de búsqueda personalizado

Como último paso, deberemos de crear un motor de búsqueda personalizado, con los sitios personalizados que queramos.

Crear un motor de búsqueda personalizado
Algunos ejemplos de URLs que se pueden usar en los motores de búsqueda personalizados. Click en la imagen para ampliar.

4- El código

Ahora ya tenemos nuestra clave y la ID de nuestro motor de búsqueda, así que sólo falta el código HTML, que dependerá si usamos Blogger, wordPress, o otro sitio web.

Para no tener que basar toda la funcionalidad en JavaScript, vamos a partir de un formulario de búsqueda.

Nota: En todos los casos URL_A_TU_SCRIPT.js es un script como éste, pero con vuestra API key y la ID de vuestro buscador (las dos primeras variables), y que deberéis de subir a dropbox, o a algún lugar similar.

Para blogger

Tendréis que añadirlo en un widget HTML/Javascript.

<link rel="stylesheet" type="text/css" href="//emiliocobos.net/demos/search-api/style.css"></link>
<form action="/search/" method="GET">
	<input type="text" name="q" id="search-submit">
	<input type="submit" value="Buscar">
</form>
<script type="text/javascript" src="URL_A_TU_SCRIPT.js"></script>

Para wordPress

En este caso deberás de añadir el código en un widget Texto:

<link rel="stylesheet" type="text/css" href="//emiliocobos.net/demos/search-api/style.css"></link>
<form action="/" method="GET">
	<input type="text" name="s" id="search-submit">
	<input type="submit" value="Buscar">
</form>
<script type="text/javascript" src="URL_A_TU_SCRIPT.js"></script>

Para cualquier otro sitio

Para sitios sin un sistema de búsqueda, recurriremos a google otra vez más. En este caso, hará falta cambiar un dato más (tusitio.com):

<link rel="stylesheet" type="text/css" href="//emiliocobos.net/demos/search-api/style.css"></link>
<form action="http://google.com/search" method="GET">
	<input type="text" name="q" id="search-submit">
	<input type="submit" value="Buscar">
	<input type="hidden" name="site" value="tusitio.com">
</form>
<script type="text/javascript" src="URL_A_TU_SCRIPT.js"></script>

Cómo funciona

De forma breve: Hacemos una solicitud AJAX a googleCreamos un script JSONP para evitar problemas con IE, mostramos los datos en un elemento HTML, y lo mantenemos abierto y animamos gracias a CSS. (el selector ~ junto con las pseudoclases :hover y :focus).

Aquí podéis ver el script con las explicaciones. Se podría hacer más (crear un botón para mostrar más resultados, por ejemplo), pero bueno …;):

(function () {
    'use strict';
	// Tu API Key
	var KEY = "AIzaSyBN_cAlr_Jr_a_mWraqzKqkzWHRNIzorxQ",
		// La ID de tu motor de búsqueda
		ENGINE_ID = "018301372644929241302:rcfsvgrmp5m",
		// Número de resultados
		NUM_RESULTS = 5,
		// Calculamos la url que servirá de base
		BASE_URL = "https://www.googleapis.com/customsearch/v1?key=" + KEY + "&cx=" + ENGINE_ID,
		// Cogemos el elemento del DOM
		input = document.getElementById('search-input'),
		// Mandar datos a la consola
		log = function (datos) {
			return window.console && window.console.log(datos);
		},
        
        // Crear un script JSONP para evitar problemas de origen de dominios en IE
        // El callback está generado dinámicamente para que ninguna función externa se vea alterada
		loadScript = function (url, opciones) {
            var fjs = document.getElementsByTagName('script')[0],
                js = document.createElement('script'),
                opciones = opciones || {},
                callbackName = "gsearchCB_" + +new Date();
            
            window[callbackName] = function (data) {
                log(data);
                if( opciones.callback ) {
                    opciones.callback.call(null, data);
                }
                js.parentNode.removeChild(js);
                window[callbackName] = null;
            }
            
            js.src = url + "&callback=" + callbackName;
            js.type = "text/javascript";
            js.async = true;
            
            js.onerror = function () {
                js.parentNode.removeChild(js);
                window[callbackName] = null;
                opciones.error && opciones.error();
            }
            
            fjs.parentNode.insertBefore(js, fjs);
            
            return true;
		},
		results,
		timer;

	if (!input) // Si no hay elemento del DOM, no podemos hacer nada
		return;

	// Evitamos autocompletar
	input.setAttribute('autocomplete', 'off');

	function search(query, count, startIndex) {
		var url = BASE_URL + "&q=" + query,
			count = count || NUM_RESULTS; 

		// Hecho por si implementaba paginación
		if( startIndex )
			url += "&start=" + startIndex;

		// Lo mismo, si no usamos número de items por defecto
		url += "&num=" + count;


		loadScript(url, {
            error: function(){
                results.innerHTML = "<p>Ha habido algún error, probablemente el límite haya sido excedido, prueba mañana</p>";
            },
            callback: function(data) {
                results.innerHTML = parse(data)
            }
        })

    }
	function parse(data){
		// Obtenemos los datos
		var totalResults = parseInt(data.searchInformation.totalResults, 10),
			// Los resultados en sí
			items = data.items,
			// Sólo un contador
			i = 0,
			// El número de items
			len = items ? items.length : 0,
			// Variable para usar en el loop que recorrerá los ítems
			item,
			// La salida en HTML
			output;

		log("Resultados de la consulta:")
		log(data);

		// Si no hay nada, lo decimos y salimos
		if( ! items )
			return "<p>No se ha encontrado nada</p>";

		// Si hay algo, mostramos el número total de resultados
		output = "<p class=\"total-results\">" + totalResults + " resultados</p><ul>";

		for( ; i < len; i++ ){
			item = items[i];
			// Mostramos un título, una url y un resumen por cada resultado obtenido
			output += "<li><h3><a title=\"" + item.snippet.replace(/[<>"]/g, function(a){ return {"\"": "&quot;", "<": "&lt;", ">": "&gt;"}[a]}) + "\" href=\"" + item.link + "\">" + item.htmlTitle + "</a></h3>";

			output += "<small>" + item.htmlFormattedUrl + "</small><p class=\"snippet\">" + item.htmlSnippet + "</p></li>";
			output += "</li>"
		}


		output += "</ul>";

		return output;
	}

	/*
	 * Cuando levantamos una tecla del teclado, se ejecuta ésta función,
	 * que deja un intervalo de 0.5 segundos, y luego busca
	 */
	function onKeyUp(e){
		// log("Keyup");

		// Evitar que se solicite cada vez que tocas el teclado
		if( timer ) {
			clearTimeout(timer);
		}
		timer = setTimeout(function(){
			// Si hemos escrito tres caracteres o menos todavía no hay nada que mostrar
			if( input.value.length <= 3 )
				return;

			// Si no hay elemento donde mostrar los resultados, lo creamos
			if( !results ){
				results = document.createElement('div');
				results.className = "goog-ajax-results";
				input.parentNode.appendChild(results);
			}
			results.innerHTML = "<p>Cargando...</p>";
			// Buscamos
			search(encodeURIComponent(input.value));
		}, 500);
	}

	// Añadimos el evento
	input.addEventListener ?
		input.addEventListener('keyup', onKeyUp, false): input.attachEvent('onkeyup', onKeyUp);
})()

Demo y posibles contraindicaciones

Demo

El mayor inconveniente es que Google sólo nos deja hacer 100 solicitudes gratis. No sé si es mucho o poco (a mí me parece suficiente para un sitio, pero muy justo). De todas maneras se podría hacer que no hubiera rastro si hay algún error (en la demo lo muestro)… Así que si en la demo recibís un error, símplemente pasaros mañana.

Aparte de eso, me parece una funcionalidad con mucha utilidad (si perdonáis mi CSS hecho en media hora ;))