Crea una extensión de Chrome para automatizar una parte de tu rutina

En esta entrada hablo de:

COMPARTE ESTA ENTRADA:

Si utilizas Google Chrome, instalar extensiones suele ser una buena manera de mejorar tu flujo de trabajo y trabajar de forma más eficiente. Para nombrar sólo una, me encanta Robots exclusion checker, que permite comprobar visualmente si una página es indexable. Antes de utilizarla, la comprobación de la etiqueta canonical, la meta robots, el archivo robots.txt y la cabecera HTTP requería bastante tiempo, aunque obviamente tenemos herramientas (como Screaming Frog) para acelerar el proceso.

Genial, pero ¿qué pasa si tu necesidad no está cubierta por una extensión existente? En el post de hoy, quiero explicar cómo crear y publicar una extensión de Chrome desde cero desde el punto de vista de un novato (como yo).

La idea

Como la mayoría de los profesionales del sector, suelo utilizar Google Search Console para mis proyectos. El informe de consultas sí tiene muestra, pero la herramienta nos da los datos más completos que podemos encontrar. Sin embargo, cuando estoy realizando un keyword research para encontrar nuevas ideas de contenido o para mejorar mis contenidos existentes, el proceso suele ser el siguiente:

  • Analizar los datos desde la UI de GSC o utilizando la API
  • Extraer el volumen de búsqueda utilizando una herramienta de terceros (Google Ads, Semrush….)
  • Combinar todo usando Excel o Sheets


¿Pero no existirá una manera de mostrar el volumen de búsqueda directamente en la API de GSC? Reduciría el tiempo para llevar a cabo esta tarea de forma significativa porque todos los datos estarían en la misma UI.

Cuando me di cuenta de que ninguna otra extensión (que yo conociera) ofrecía esta funcionalidad, decidí crear la mía.

¿Cómo funciona una extensión de Chrome?


Si aún no lo sabes, las extensiones de Chrome usan JavaScript. Según la complejidad de la que quieras crear, puedes necesitar varios archivos para que funcione, pero necesitas al menos

  • manifest.json: un archivo que incluye varias informaciones clave sobre la extensión, como su nombre, su descripción y cuándo debe ejecutarse una vez instalada. Para más información, consulta la documentación oficial.
  • code.js: el nombre puede ser diferente (lo especifica en su archivo manifest.json), pero este archivo JavaScript incluye el código que debe ejecutar su extensión. En mi caso, todas las instrucciones están ahí.


Si tienes curiosidad y quieres saber cómo funcionan tus extensiones favoritas, puedes seguir este tutorial.

¿Cómo funciona mi extensión?


Si utilizas la extensión, puede hacer dos cosas:

  • Mostrar el volumen de búsqueda directamente en la interfaz de usuario de GSC
  • Descargar estos volúmenes (si quieres) junto con los datos de tus palabras clave


El código completo está disponible en mi repositorio de GitHub, pero déjame explicar cómo funciona, parte por parte.

JavaScript vs Python


Si has leído otros artículos que he publicado en mi blog, sabrás que uso habitualmente Python en mi trabajo. En otras palabras: JavaScript no es lo mío. Por lo tanto, ¿cómo he creado una extensión basada en este lenguaje?

  • Colaboración: el sector SEO me encanta más cada día. Si pides ayuda, siempre hay alguien dispuesto a ayudarte. En mi caso, hablé con José Luis Hernando que es un crack del JS y me ayudó mucho en el proyecto. También le pedí a Fede Gómez que me solucionara un problema concreto que tuve al final del proyecto, que explicaré más adelante.
  • Transferencia de conocimientos: aunque JavaScript tiene algunas diferencias clave con Python, algunos conceptos son básicamente los mismos. Sólo tienes que buscar en Google rápidamente cómo escribir un bucle, pero no tienes que entender la lógica porque ya la has aprendido antes.


La lógica


La lógica que sigue la extensión es bastante simple:

  • Descargar las palabras clave de la UI de GSC
  • Extraer sus volúmenes de una API
  • Añadirlas a la UI

Descargar las palabras clave desde GSC

function get_keywords() {
  //array where we will store our keyword
  let arr = [];
  //gets table
  //there are several tables with the same class
  var oTable = document.getElementsByClassName('i3WFpf')[0];
  //gets rows of table
  var rowLength = oTable.rows.length;
  //loops through rows
  for (i = 0; i < rowLength; i++) {
    //gets cells of current row
    var oCells = oTable.rows.item(i).cells;
    //loops through each cell in current row{
    arr.push(oCells.item(0).innerText);
  }
  // Remove "Top Queries" header from array
  arr.shift();
  return arr;
}

Los datos que queremos extraer están incluidos en una tabla cuya clase es «i3WFpf». ¡Realmente espero que Google no cambie eso pronto, de lo contrario mi extensión tendrá que ser actualizada!

Para extraer las palabras clave, recorro esta tabla y extraigo la primera fila (la palabra clave). Termino con un array (una lista) de hasta 1.000 palabras clave, que es el límite de la UI de GSC. Este paso fue bastante fácil porque honestamente creí que el código fuente iba a estar más desordenado, como en Amazon por ejemplo. Afortunadamente, no fue el caso en absoluto.

Extraer sus volúmenes de una API

Antes de explicar el código, quiero destacar qué API he utilizado y por qué. Mi extensión utiliza la de Keyword Surfer (que quizá conozcas por su extensión Keyword Surfer) porque:

  • Es gratuita
  • No requiere ni autentificación ni clave API (al menos ahora)
  • Permite hacer peticiones masivas: con una sola petición, puedes recuperar el volumen de hasta 50 palabras clave.


La última ventaja fue decisiva, porque tener que ejecutar hasta 1.000 peticiones (para hasta 1.000 palabras clave) habría impactado MUCHO en la velocidad a la que la extensión puede recuperar volúmenes de búsqueda. También significa que no voy a sobrecargar su API con mi extensión, que es lo último que quiero.

Dicho esto, la API está lejos de ser perfecta:

  • Algunos volúmenes no se actualizan muy a menudo
  • Las palabras clave de bajo volumen se reportan muy mal
  • Como es habitual, las palabras clave están agrupadas. «Comprar coche» y «comprar coches» devolverán el mismo volumen (o uno devolverá 0) cuando tenemos métricas separadas en GSC. Sin embargo, esta es una limitación muy conocida para la mayoría de las API, no sólo la de Keyword Surfer.
  • Si el Keyword Surfer no tiene datos para una palabra clave específica, no devuelve 0 sino que nada. Algo que tuvimos que tener en cuenta en nuestra lógica.

Dicho esto, echemos un vistazo al código:

async function get_search_vol(chunk, url) {
  const requestUrl = url; // URL to request
  const response = await fetch(requestUrl); // Make request to Keyword Surfer
  const json = await response.json(); // Transform response to JSON
  //loop the response and return an array with volumes
  let keywords = {};
  for (i = 0; i < chunk.length; i++) {
    keywords[chunk[i]] = json[chunk[i]]?.search_volume ?? 0; // If keyword has data get the data else return 0 (optional chaining operator (?.) + Nullish coalescing operator (??))
  }
  return keywords;
}

//function to divide an array in X arrays
//Source: https://ourcodeworld.com/articles/read/278/how-to-split-an-array-into-chunks-of-the-same-size-easily-in-javascript
function chunkArray(myArray, chunk_size) {
  var index = 0;
  var arrayLength = myArray.length;
  var tempArray = [];

  for (index = 0; index < arrayLength; index += chunk_size) {
    var myChunk = myArray.slice(index, index + chunk_size);
    // Do something if you want with the group
    var finalChunk = [];
    for (y = 0; y < myChunk.length; y++) {
      finalChunk.push(myChunk[y].replace('"', '').replace('"', '').replace('&',''));
    }
    tempArray.push(finalChunk);
  }

  return tempArray;
}

//generate the Keyword Surfer's URLs for our chunks
function generate_urls(chunks, country) {
  //output
  const arr = [];

  //Base URL to generate our list of urls
  const base_url = `https://db2.keywordsur.fr/keyword_surfer_keywords?country=${country}&keywords=[%22`;

  // Loop through chunk to create array of keywords in request
  for (i = 0; i < chunks.length; i++) {
    var url = base_url.concat(chunks[i].join('%22,%22'), '%22]');
    arr.push(url);
  }
  return arr;
}

Para extraer los volúmenes, hay varios pasos:

  • Una función llamada chunkArray divide nuestra lista de palabras clave en trozos de X elementos. Necesitamos realizar esta acción porque podemos solicitar hasta 50 palabras clave a la API de Keyword Surfer.
  • Una función llamada generate_urls crea una lista de URLs de peticiones utilizando nuestros trozos. La estructura es bastante simple: https://db2.keywordsur.fr/keyword_surfer_keywords?country=us&keywords=[%22buy%20car%22,%22buy%20cars%22] (ejemplo con 2 palabras clave), aunque los caracteres especiales (ver este problema reportado en GitHub) pueden romper la API y son actualmente la principal razón por la que los usuarios no pueden utilizar la extensión correctamente a veces.
  • Una función llamada get_search_vol recupera el volumen de búsqueda. La API de Keyword Surfer nos proporciona más que el volumen de búsqueda en la respuesta, por lo que necesitamos procesar el resultado para extraer sólo la información que necesitamos.

A continuación, añadimos estas funciones juntas dentro de otra para obtener el resultado esperado: una lista de palabras clave (de GSC) con su volumen asociado (de la API de Keyword Surfer).

async function getData(country) {
  const kws = get_keywords(); // Get all keywords from GSC
  const chunks = chunkArray(kws, 50); // transform keyword in multiple arrays of 50 keywords
  const urls = generate_urls(chunks, country); // Build Request URL
  const allKeywords = {}; // Store future reponses in hashmap

  // Loop through GSC set of keywords and request keywoFrd surfer data
  for (let i = 0; i < urls.length; i++) {
    var sv = await get_search_vol(chunks[i], urls[i]);
    var keys = Object.keys(sv);
    for (let y = 0; y < keys.length; y++) {
      allKeywords[Object.keys(sv)[y]] = sv[Object.keys(sv)[y]];
    }
  }
  // console.log(allKeywords); // Just to check the output
  return allKeywords;
}

¡Genial! Lo único que necesitamos ahora es mostrar esta información al usuario.

Añadirlos a la UI

function createCell(text) {
  var cell = document.createElement('td');
  var cellText = document.createTextNode(text);
  cell.appendChild(cellText);
  cell.setAttribute(
    'style',
    'font-size:12px;font-weight:bold;text-align:center;padding:18px 28px;'
  );
  return cell;
}

async function addVolumes(country) {
  const volumes = await getData(country); // Wait to get hasmap of search volumes
  const tbl = document.getElementsByClassName('i3WFpf')[0]; // Select table
  // Future CSV
  let csvExport = '';

  // Loop through rows
  for (let i in tbl.rows) {
    // For some reason there is an undefined row at the end
    if (i === 'length') {
      break;
    } else {
      // Select each row
      let row = tbl.rows[i];
      // Create line for future CSV
      const line = [];
      // Select first cell (query)
      const query = row.cells[0].textContent;
      // Add header
      if (query === tbl.rows[0].cells[0].textContent) {
        row.appendChild(createCell('Search Volumes'));
      } else {
        // If there is search volume data add it
        if (volumes[query]) row.appendChild(createCell(volumes[query]));
        // If not add 0 search volume
        else row.appendChild(createCell(0));
      }
      // Loop through cells
      for (const cell of row.cells) {
        const text = cell.textContent;
        line.push(text.replace(/,/g, ''));
      }
      // Create each filled line for future CSV
      csvExport = csvExport.concat(line.join(','), '\n');
    }
  }
  createDownloadButton(csvExport);
}

La lógica es bastante simple aquí:

  • Una función llamada createCell se utiliza para crear celdas en nuestra tabla GSC. El CSS añadido (inline) en esta función se puede ver en tu código fuente una vez que los volúmenes han sido recuperados por la extensión.
  • Una función llamada addVolumes añade la columna de volumen a toda nuestra tabla. Si la palabra clave no tiene datos en Keyword Surfer (como se mencionó anteriormente), añadimos 0 por defecto.

Información proporcionada por los usuarios


Si has utilizado mi extensión, recordarás que tienes que especificar tu país para utilizarla.

Almacenar un dato proporcionado por un usuario para reutilizarla más tarde fue en realidad la parte más difícil de todo el proceso. El código puede parecer sencillo pero:

  • La guía no es muy clara y entender por qué tu código no funciona tampoco es fácil, ya que a menudo no provoca ningún error 🙁 .
  • Google Chrome tiene sus propios estándares en lugar de utilizar los de JavaScript


Dicho esto, el siguiente código permite que mi extensión guarde la elección del usuario, para que posteriormente sea utilizada por el código principal para recuperar el volumen de búsqueda de un país determinado. En mi ejemplo, recuperaría el volumen de Francia.

const optionsStatus = document.getElementById('status');
const countryList = document.getElementById('country');
const optionsSaveBtn = document.getElementById('save');

function save_options() {
  var country = document.getElementById('country').value;
  chrome.storage.local.set({ country: country }, function () {
    // Update status to let user know options were saved.
    var status = document.getElementById('status');
    status.textContent = 'Options saved.';
    setTimeout(function () {
      status.textContent = '';
    }, 750);
  });
}

// Restores select box and checkbox state using the preferences
// stored in chrome.storage.
function restore_options() {
  // Use default value color = 'red' and likesColor = true.
  chrome.storage.local.get(
    {
      country: 'fr',
    },
    function (items) {
      document.getElementById('country').value = items.country;
    }
  );
}
document.addEventListener('DOMContentLoaded', restore_options);
document.getElementById('save').addEventListener('click', save_options);

¿Cómo depurar una extensión?


Cuando creas un código JavaScript para utilizarlo en una extensión, necesitas probarlo primero. Tienes dos opciones:

  • Usar el código directamente en Chrome: puedes ejecutar directamente el JavaScript usando la pestaña de la consola de Chrome DevTools. Bastante práctico al principio (porque no tienes que instalar nada) pero te obliga a copiar-pegar tu código con bastante frecuencia.
  • Instala tu extensión: puedes instalar una extensión no verificada (sigue estos pasos) y utilizarla como lo haría un usuario normal. Sin embargo, requiere que tengas el manifest.json, algo que quizás no quieras crear al principio del proceso.


Tal vez haya formas más eficientes de depurar una extensión, pero como novato sólo usé estas dos, y fue más que suficiente para mi proyecto.

¿Cómo publicar tu extensión?


Si quieres publicar tu extensión, puedes seguir la documentación oficial que lo explica bastante bien. Para resumir el proceso, tendrás que

  • Crear una cuenta de desarrollador y pagar la cuota de 5$ (aunque tu extensión sea gratuita)
  • Publica tu extensión facilitando la información requerida, como su finalidad. Ten en cuenta que debes justificar los permisos que requiere tu extensión. De hecho, mi segunda solicitud fue rechazada porque requería demasiados permisos cuando no eran todos necesarios. Así que ten cuidado con eso, porque ralentizará todo el proceso.
  • No incluyas la palabra «Google» en el nombre de tu extensión (o cualquier otra marca). Mi primer rechazo se debió a este error de novato, ya que Google considera que te haces pasar por otra empresa (Google en mi caso) al utilizar su nombre.
Aparte de estas recomendaciones, el proceso de validación es bastante rápido (menos de una semana). Si quieres actualizar su extensión, el proceso es aún más rápido, ya que obviamente no tienes que rellenar todo de nuevo.

Conclusión

Mejorar mi flujo de trabajo es siempre una de mis prioridades y, a pesar de tener un background Python, el desarrollo de esta extensión no fue tan difícil como pensaba. Sin embargo, no lo habría logrado sin la ayuda y los consejos de las personas que mencioné al principio del artículo, seamos honestos aquí 🙂. Las extensiones son fáciles de crear y pueden ahorrarnos mucho tiempo, así que definitivamente es algo que miraría si sabes un poco de JavaScript.


Python es popular ahora mismo, pero no podemos negar que JavaScript tiene sus ventajas.

COMPARTE ESTA ENTRADA: