Recalibrado contra los datos reales del primer día, que desmintieron dos supuestos del diseño. El umbral de 150 impresiones dejaba fuera TODO: el mejor caso de los dos blogs eran las 125 de Varginha en EN. El vigilante se habría callado para siempre, la peor avería en algo que solo habla cuando hay noticias. Baja a 40, y entre 40 y 150 las líneas salen marcadas con «~» porque ahí el CTR es orientativo, no firme. Y el CTR no es la palanca a este volumen: con ~180 impresiones semanales en EN, convertir el 10% de todas serían 78 clics al mes. El informe pasa a liderar con cobertura — impresiones, páginas que reciben alguna, consultas distintas, posición ponderada, y qué páginas empiezan o dejan de aparecer — y avisa también cuando eso se mueve (±25% de impresiones o ±3 páginas), no solo cuando hay CTR desperdiciado. De propina, un fallo que solo se vio ejecutando el servicio dos veces seguidas: avisaba las dos. Las listas nuevas ordenan un set de URLs por impresiones y los empates los desempataba el orden de iteración del conjunto, que Python aleatoriza por proceso; la huella cambiaba sin que cambiara un solo dato. Todas las ordenaciones llevan ya desempate por clave. Verificado: mismo texto con PYTHONHASHSEED 1, 2 y 3, y la segunda ejecución del servicio se calla.
161 lines
6.9 KiB
Markdown
161 lines
6.9 KiB
Markdown
# El marcador — Search Console (`seo_gsc.py`)
|
|
|
|
Lo que faltaba en toda la caja de herramientas: **datos de resultado**. El
|
|
resto mira hacia dentro (enlaces, sitemap, reglas de metas); esto mira lo único
|
|
que decide si el trabajo sirvió — cuánta gente nos encuentra y cuánta pincha.
|
|
|
|
Es de **solo lectura**. No escribe en los blogs ni en Search Console.
|
|
|
|
## Puesta en marcha (cuatro pasos de navegador, una sola vez)
|
|
|
|
La API es gratis. Hace falta una **cuenta de servicio**, no OAuth: en esta
|
|
máquina no hay navegador y el flujo interactivo no funciona headless — la misma
|
|
piedra con la que ya tropezamos en Gemini.
|
|
|
|
1. **Proyecto y API.** En <https://console.cloud.google.com> crea un proyecto
|
|
(o reutiliza uno) y activa **Google Search Console API** en
|
|
*APIs y servicios → Biblioteca*.
|
|
|
|
2. **Cuenta de servicio.** *IAM y administración → Cuentas de servicio → Crear*.
|
|
No necesita ningún rol de IAM: el permiso que importa se da en el paso 4.
|
|
Luego *Claves → Añadir clave → Crear nueva → JSON* y descarga el fichero.
|
|
|
|
3. **La clave, en su sitio.** En esta máquina:
|
|
|
|
```bash
|
|
mkdir -p ~/.config/gsc
|
|
mv ~/Descargas/EL-FICHERO.json ~/.config/gsc/service-account.json
|
|
chmod 600 ~/.config/gsc/service-account.json
|
|
```
|
|
|
|
La herramienta **se niega a arrancar** si el fichero es legible por otros.
|
|
Está fuera de cualquier repo; no la metas en git.
|
|
|
|
4. **Permiso en Search Console.** Este es el paso que se olvida. Para **cada
|
|
una de las dos propiedades** (`theexclusionzone.com` y
|
|
`zonadeexclusion.com`): *Configuración → Usuarios y permisos → Añadir
|
|
usuario* → pega el `client_email` de la credencial (algo como
|
|
`nombre@proyecto.iam.gserviceaccount.com`) → permiso **Restringido**, que
|
|
basta para leer.
|
|
|
|
⚠️ Si `zonadeexclusion.com` no está dada de alta en Search Console, hay que
|
|
verificarla antes. Del blog ES no había ni una sola exportación, así que es
|
|
probable que ni exista la propiedad.
|
|
|
|
5. **Arrancar el vigilante.** El timer se instala **parado a propósito**: sin
|
|
credencial fallaría cada lunes, y un timer que falla siempre enseña a
|
|
ignorar los avisos. Se enciende cuando el canario pasa:
|
|
|
|
```bash
|
|
seo-gsc comprueba && sudo systemctl enable --now seo-gsc-watch.timer
|
|
```
|
|
|
|
Comprobación:
|
|
|
|
```bash
|
|
seo-gsc sitios # tienen que salir las dos propiedades
|
|
seo-gsc comprueba # canario: recorre el camino real eslabón por eslabón
|
|
```
|
|
|
|
Si el nombre que devuelve `sitios` no coincide con el configurado (puede ser
|
|
`sc-domain:x.com` o `https://www.x.com/`, con barra final), corrige el
|
|
diccionario `SITIOS` en `seo_gsc.py` con el nombre exacto.
|
|
|
|
Es lo que pasó al estrenarlo: las dos propiedades **no son del mismo tipo**. EN
|
|
es de dominio y ES es de prefijo de URL (`https://zonadeexclusion.com/`), que
|
|
solo ve lo que cuelga de ese prefijo exacto — ni `www`, ni `http`. Sirve porque
|
|
el canónico del ES es el apex, pero si algún día se publica bajo `www` no
|
|
aparecería aquí. Darla de alta como propiedad de dominio lo arreglaría de raíz.
|
|
|
|
## Uso diario
|
|
|
|
```bash
|
|
seo-gsc pull # descarga y acumula en SQLite (idempotente)
|
|
seo-gsc # sin argumentos = informe de 28 días
|
|
seo-gsc informe --dias 7
|
|
```
|
|
|
|
La primera descarga pide **16 meses**, que es todo lo que Google guarda, pero
|
|
lo que llega es lo que haya. Medido el 2026-07-28: el dato más antiguo es del
|
|
**2026-05-09** en las dos propiedades, porque se dieron de alta entonces. Google
|
|
no guarda nada de antes de que exista la propiedad, así que **no hay histórico
|
|
anterior a mayo de 2026 y nunca lo habrá**. Cuidado con leer «16 meses» y creer
|
|
que se puede comparar contra el año pasado.
|
|
|
|
### La pregunta pendiente
|
|
|
|
El remate de metas fue el **23 de junio** y los únicos datos que había eran del
|
|
17. En cuanto haya credencial:
|
|
|
|
```bash
|
|
seo-gsc antes-despues --corte 2026-06-23 --dias 28
|
|
```
|
|
|
|
## Qué mira el informe
|
|
|
|
Primero **cobertura**, que es la métrica principal:
|
|
|
|
- **Impresiones, páginas que reciben alguna y consultas distintas**, contra el
|
|
periodo anterior, más la **posición media ponderada** (ojo al signo: subir es
|
|
empeorar).
|
|
- **Páginas que empiezan a aparecer** y **páginas que han dejado de aparecer**.
|
|
En un sitio joven eso *es* la noticia: que un artículo entre en el índice útil
|
|
importa más que dos décimas de CTR.
|
|
|
|
Después **conversión**:
|
|
|
|
- **Páginas que posicionan y no se pinchan.** Cada línea estima los clics que
|
|
faltan si el CTR fuese el normal de esa posición.
|
|
- **Consultas donde salimos y no nos pinchan.**
|
|
|
|
El CTR "normal" sale de una curva de referencia del sector, no de una medición
|
|
nuestra: **sirve para ordenar candidatas, no para prometer tráfico**.
|
|
|
|
### Por qué la cobertura va primero
|
|
|
|
Al estrenar la herramienta con datos reales (2026-07-28) el suelo, ya sin el
|
|
pico de mayo, era de ~180 impresiones semanales en EN y ~40 en ES, con 0-1
|
|
clics. Con esas cifras el CTR no es la palanca: **aunque convirtiéramos el 10%
|
|
de todas las impresiones de EN serían unos 78 clics al mes**. El techo lo pone
|
|
cuánta gente nos ve.
|
|
|
|
Los datos también dijeron dónde está el problema de verdad — posicionamos en el
|
|
top-10 de cadenas hiperespecíficas que no busca nadie (`341.roswell.41`) y en la
|
|
página 5-7 de los términos que la gente escribe (`betty and barney` en 55,
|
|
`roswell 1947` en 52,8). Eso es autoridad de dominio, no titulares.
|
|
|
|
### El umbral, y por qué se bajó a 40
|
|
|
|
El primer intento usaba 150 impresiones, razonable para un sitio con tráfico.
|
|
Con datos delante, **nada** lo pasaba: el mejor caso era Varginha en EN con 125.
|
|
El vigilante se habría callado para siempre, que es la peor avería posible en
|
|
algo que solo habla cuando hay noticias. Ahora el corte está en 40 y las líneas
|
|
por debajo de 150 salen marcadas con `~`: orientativas, no firmes.
|
|
|
|
## Automático
|
|
|
|
`seo-gsc-watch.timer` — lunes 06:15 UTC: descarga y manda el informe por
|
|
Telegram, pero **solo si hay algo que contar y el informe ha cambiado** desde el
|
|
último aviso. Misma disciplina que `seo-watch`: si no hay nada que decir, se
|
|
calla. Hay algo que contar si aparecen páginas desperdiciadas **o si la
|
|
cobertura se ha movido** — un ±25% de impresiones o ±3 páginas que reciben
|
|
alguna.
|
|
|
|
```bash
|
|
systemctl status seo-gsc-watch.timer
|
|
journalctl -u seo-gsc-watch -n 40
|
|
```
|
|
|
|
## Notas de implementación
|
|
|
|
- **Sin librerías de Google.** El JWT se firma con PyJWT y se canjea a mano.
|
|
Son treinta líneas y evita arrastrar media docena de dependencias para tres
|
|
peticiones.
|
|
- **Los datos tardan 2-3 días en consolidarse.** Todo termina 3 días atrás y se
|
|
pide `dataState=final`; pedir "ayer" devuelve cifras que luego cambian solas.
|
|
- **La posición se pondera por impresiones.** Promediar posiciones a pelo da el
|
|
mismo peso a un día de 2 impresiones que a uno de 500.
|
|
- **La API pagina de 25.000 en 25.000** y no avisa de que quedan más filas.
|
|
- El almacén es SQLite en `~/.local/state/seo-gsc/gsc.db`; `pull` es idempotente
|
|
y resolapa un día por si el último se consolidó después.
|