Automatiza el análisis de tu Google Search Console sin pagar ni un euro. Este script extrae las URLs y keywords con más clics, compara el período actual con el anterior, y envía el resumen a tu email — todo con Google Apps Script.
¿Por qué Google Apps Script para automatizar GSC?
Google Apps Script es la plataforma de automatización nativa de Google. Corre en la nube, se integra con GSC, Gmail y Drive, y es completamente gratuita.
- Sin servidor propio — no necesitas hosting ni infraestructura adicional
- Datos dentro del ecosistema Google — sin terceros, sin problemas de privacidad
- Funciona mientras duermes — trigger automático diario o semanal
- Cero coste mensual — al contrario que herramientas como SEMrush o Ahrefs
Si tienes una estrategia de SEO activa, este script te da visibilidad diaria sobre qué keywords y páginas ganan o pierden posiciones — sin abrir el navegador cada mañana.
Paso 1: Crear el proyecto en Apps Script
- Ve a script.google.com
- Haz clic en Nuevo proyecto
- Verás un archivo
Code.gscon una función vacía - Haz clic en Guardar (💾) y nómbralo: GSC Automated Reports
Paso 2: Habilitar la API de Search Console
Apps Script no incluye GSC preinstalado — necesitas habilitarla manualmente en Google Cloud:
- Ve a console.cloud.google.com
- Selecciona o crea un proyecto
- Ve a APIs y servicios → Biblioteca
- Busca Google Search Console API y haz clic en Habilitar
Luego vincula el proyecto Cloud a tu Apps Script:
- Vuelve a Apps Script → Configuración del proyecto (⚙️)
- En Google Cloud Platform (GCP) Project, haz clic en Cambiar proyecto
- Introduce el número de proyecto de Cloud Console y guarda
Paso 3: El código completo del script
Selecciona todo el contenido de Code.gs y sustituye con este código:
// ============================================
// CONFIGURACIÓN — EDITAR ESTOS VALORES
// ============================================
const CONFIG = {
siteUrl: 'https://tu-dominio.com/',
emailTo: 'tu-email@gmail.com',
frequency: 'daily',
daysToAnalyze: 7,
topResults: 10
};
function runGSCReport() {
try {
const report = generateReport();
sendEmail(report);
Logger.log('Reporte enviado correctamente');
} catch (error) {
Logger.log('Error: ' + error.toString());
MailApp.sendEmail({ to: CONFIG.emailTo, subject: '❌ Error en GSC Report', body: error.toString() });
}
}
function generateReport() {
const endDate = new Date();
const startDate = new Date();
startDate.setDate(endDate.getDate() - CONFIG.daysToAnalyze);
const startStr = formatDate(startDate);
const endStr = formatDate(endDate);
const currentData = fetchGSCData(startStr, endStr);
const prevEndDate = new Date(startDate);
const prevStartDate = new Date(prevEndDate);
prevStartDate.setDate(prevEndDate.getDate() - CONFIG.daysToAnalyze);
const prevData = fetchGSCData(formatDate(prevStartDate), formatDate(prevEndDate));
return {
current: currentData, previous: prevData,
period: startStr + ' a ' + endStr,
prevPeriod: formatDate(prevStartDate) + ' a ' + formatDate(prevEndDate)
};
}
function fetchGSCData(startDate, endDate) {
const url = 'https://www.googleapis.com/webmasters/v3/sites/' +
encodeURIComponent(CONFIG.siteUrl) + '/searchAnalytics/query';
const payload = { startDate, endDate, dimensions: ['query', 'page'], rowLimit: CONFIG.topResults * 2 };
const options = {
method: 'post', contentType: 'application/json',
payload: JSON.stringify(payload), muteHttpExceptions: true,
headers: { Authorization: 'Bearer ' + ScriptApp.getOAuthToken() }
};
const response = UrlFetchApp.fetch(url, options);
if (response.getResponseCode() !== 200) throw new Error('Error GSC API: ' + response.getContentText());
return JSON.parse(response.getContentText());
}
function formatDate(date) {
return Utilities.formatDate(date, Session.getScriptTimeZone(), 'yyyy-MM-dd');
}
function sendEmail(report) {
const currentRows = report.current.rows || [];
const prevRows = report.previous.rows || [];
let cClicks = 0, cImpr = 0, pClicks = 0, pImpr = 0;
currentRows.forEach(r => { cClicks += r.clicks||0; cImpr += r.impressions||0; });
prevRows.forEach(r => { pClicks += r.clicks||0; pImpr += r.impressions||0; });
const chg = pClicks > 0 ? ((cClicks - pClicks) / pClicks * 100).toFixed(1) : 0;
const ichg = pImpr > 0 ? ((cImpr - pImpr) / pImpr * 100).toFixed(1) : 0;
let html = `<h2>📊 Google Search Console Report</h2>
<p><strong>Site:</strong> ${CONFIG.siteUrl} | <strong>Period:</strong> ${report.period}</p>
<h3>📈 Summary</h3>
<table border="1" cellpadding="8" style="border-collapse:collapse;">
<tr style="background:#f0f0f0;"><th>Metric</th><th>Current</th><th>Previous</th><th>Change</th></tr>
<tr><td>Clicks</td><td>${cClicks}</td><td>${pClicks}</td>
<td style="color:${chg>=0?'green':'red'}">${chg>=0?'+':''}${chg}%</td></tr>
<tr><td>Impressions</td><td>${cImpr}</td><td>${pImpr}</td>
<td style="color:${ichg>=0?'green':'red'}">${ichg>=0?'+':''}${ichg}%</td></tr>
</table>`;
const urlMap = new Map();
currentRows.forEach(r => { const p=r.keys[1]; if(!urlMap.has(p)) urlMap.set(p,r); });
html += `<h3>🔍 Top URLs</h3><table border="1" cellpadding="8" style="border-collapse:collapse;">
<tr style="background:#f0f0f0;"><th>URL</th><th>Clicks</th><th>Impressions</th><th>CTR</th><th>Pos</th></tr>`;
let uc=0;
urlMap.forEach((r,page)=>{ if(uc++>=CONFIG.topResults) return;
const ctr=r.impressions>0?((r.clicks/r.impressions)*100).toFixed(2):0;
html+=`<tr><td>${page}</td><td>${r.clicks}</td><td>${r.impressions}</td><td>${ctr}%</td><td>${r.position?.toFixed(1)||'-'}</td></tr>`;
});
html += `</table>`;
const kwMap = new Map();
currentRows.forEach(r => { const q=r.keys[0]; if(!kwMap.has(q)) kwMap.set(q,r); });
html += `<h3>🎯 Top Keywords</h3><table border="1" cellpadding="8" style="border-collapse:collapse;">
<tr style="background:#f0f0f0;"><th>Keyword</th><th>Clicks</th><th>Impressions</th><th>CTR</th><th>Pos</th></tr>`;
let kc=0;
kwMap.forEach((r,q)=>{ if(kc++>=CONFIG.topResults) return;
const ctr=r.impressions>0?((r.clicks/r.impressions)*100).toFixed(2):0;
html+=`<tr><td>${q}</td><td>${r.clicks}</td><td>${r.impressions}</td><td>${ctr}%</td><td>${r.position?.toFixed(1)||'-'}</td></tr>`;
});
html += `</table><hr><p style="font-size:12px;color:#666;">Generated by GSC Automated Reports</p>`;
MailApp.sendEmail({ to: CONFIG.emailTo, subject: `📊 GSC Report: ${CONFIG.siteUrl} | ${report.period}`, htmlBody: html, name: 'GSC Report Bot' });
}
function setupTrigger() {
ScriptApp.getProjectTriggers().forEach(t => ScriptApp.deleteTrigger(t));
if (CONFIG.frequency === 'daily') {
ScriptApp.newTrigger('runGSCReport').timeBased().everyDays(1).atHour(8).nearMinute(0).create();
} else {
ScriptApp.newTrigger('runGSCReport').timeBased().onWeekDay(ScriptApp.WeekDay.MONDAY).atHour(8).create();
}
runGSCReport();
}
Paso 4: Configurar el archivo Manifest
- Ve a Configuración del proyecto (⚙️)
- Activa “Mostrar el archivo manifest appsscript.json en el editor”
- Abre
appsscript.jsony pega:
{
"timeZone": "Europe/Madrid",
"dependencies": {},
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8",
"oauthScopes": [
"https://www.googleapis.com/auth/webmasters.readonly",
"https://www.googleapis.com/auth/script.external_request",
"https://www.googleapis.com/auth/script.send_mail",
"https://www.googleapis.com/auth/script.scriptapp"
]
}
Guarda con Ctrl + S.
Paso 5: Ejecutar y autorizar
- Edita la sección
CONFIGal inicio con tu dominio y email - En el dropdown de funciones, selecciona setupTrigger
- Haz clic en Ejecutar (▶️)
Aparecerá la pantalla de autorización de Google:
- Haz clic en Revisar permisos → selecciona tu cuenta
- En “Google no ha verificado esta aplicación”: Avanzado → Ir a [nombre] (no seguro)
- Marca todos los permisos y haz clic en Permitir
Esta autorización es una única vez. Después el script funciona sin intervención.
Paso 6: Verificar el trigger
Ve a Triggers (⏰ en el panel izquierdo) y comprueba que aparece:
- Function: runGSCReport
- Event: Time-driven
- Frequency: Day timer o Week timer
Revisa tu email — el primer reporte de prueba debería llegar en minutos.
¿Qué incluye el reporte?
| Sección | Contenido |
|---|---|
| Summary | Clics e impresiones totales vs período anterior, con % de cambio |
| Top URLs | Las 10 páginas con más clics, con CTR y posición media |
| Top Keywords | Las 10 keywords con más clics, con CTR y posición media |
| Cambios | Verde/rojo indicando subidas o bajadas vs el período anterior |
Los datos de Top Keywords son especialmente útiles para detectar qué términos merecen optimización adicional — el mismo análisis de nuestro servicio de keyword research profesional.
Limitaciones a tener en cuenta
- Retraso de datos: GSC tiene 2-3 días de delay. No verás datos de ayer.
- Límite de filas: 25.000 por consulta. Para sitios grandes necesitas paginación adicional.
- Autorización manual: La primera vez requiere clic en “Permitir”. No es evitable.
¿Necesitas ayuda para configurarlo?
En Brandgrowth configuramos este tipo de automatizaciones para nuestros clientes de SEO. Si prefieres que lo hagamos por ti — o integrarlo en tu estrategia de SEO técnico — cuéntanos tu proyecto y te respondemos en menos de 24 horas.