---
name: pull_gsc_search_analytics
kind: function
lang: py
domain: datascience
version: "1.0.0"
purity: impure
signature: "def pull_gsc_search_analytics(service: object, site_url: str, start_date: str, end_date: str, dimensions: list = None, row_limit: int = 25000, max_total_rows: int = 0, search_type: str = 'web') -> list"
description: "Extrae datos de la Search Analytics API de Google Search Console (GSC): impresiones, clicks, CTR y posicion por las dimensiones pedidas (query, page, date, country, device, searchAppearance). Recibe un objeto service GSC ya autenticado (el que devuelve gsc_auth, inyectado) y llama a service.searchanalytics().query(siteUrl, body).execute(). Pagina automaticamente con startRow en pasos de row_limit (tope duro 25000 filas/request) hasta que una pagina devuelve menos de row_limit filas o se alcanza max_total_rows. Aplana cada fila mapeando el array keys posicionalmente a los nombres de dimensions y añade clicks, impressions, ctr y position. Si la API no devuelve filas (rows ausente), retorna lista vacia sin error. Es el extractor principal de datos SEO para alimentar un pipeline hacia DuckDB/Postgres."
tags: [seo, gsc, datascience, search-console, google, extractor]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [typing.Any]
params:
  - name: service
    desc: "objeto service autenticado de la Google Search Console API (el que devuelve gsc_auth_py_infra). Se inyecta ya construido; esta funcion NO lo crea ni llama a gsc_auth internamente. Debe exponer .searchanalytics().query(siteUrl=..., body=...).execute()."
  - name: site_url
    desc: "propiedad de Search Console. Formato 'sc-domain:ejemplo.com' para propiedad de dominio, o URL completa 'https://ejemplo.com/' para propiedad de prefijo. El formato importa: usar el que coincida con como la propiedad esta dada de alta en GSC."
  - name: start_date
    desc: "fecha inicial inclusiva en formato YYYY-MM-DD."
  - name: end_date
    desc: "fecha final inclusiva en formato YYYY-MM-DD. La API tiene ~2-3 dias de lag; el caller deberia pedir hasta hoy-3 para datos completos."
  - name: dimensions
    desc: "lista de dimensiones a desglosar. Por defecto ['query', 'page']. Otras validas: 'date', 'country', 'device', 'searchAppearance'. El orden define el orden de las keys en cada fila."
  - name: row_limit
    desc: "filas por request y tamaño de paso de la paginacion. Rango 1..25000 (se clampa al tope duro de la API). Por defecto 25000."
  - name: max_total_rows
    desc: "tope total de filas acumuladas en todas las paginas. 0 = sin tope (trae todo lo disponible). Si >0, recorta el resultado al llegar a ese numero."
  - name: search_type
    desc: "tipo de busqueda: 'web' | 'image' | 'video' | 'news' | 'discover' | 'googleNews'. Va en el body de la API como 'type'. Por defecto 'web'."
output: "list de dicts aplanados. Cada dict tiene una clave por cada dimension (con su nombre real, ej. query, page) mas clicks, impressions, ctr y position. Ejemplo con dimensions=['query','page']: {'query': '...', 'page': '...', 'clicks': 5, 'impressions': 100, 'ctr': 0.05, 'position': 12.3}. Lista vacia si la API no devuelve filas."
tested: true
tests:
  - "test_aplanado_mapea_keys_a_nombres_de_dimension"
  - "test_paginacion_recorre_varias_paginas_y_para_en_pagina_corta"
  - "test_max_total_rows_recorta"
  - "test_rows_ausente_retorna_lista_vacia"
  - "test_dimension_unica_date"
test_file_path: "python/functions/datascience/pull_gsc_search_analytics_test.py"
file_path: "python/functions/datascience/pull_gsc_search_analytics.py"
---

## Ejemplo

```python
import sys, os
sys.path.insert(0, os.path.join("python", "functions"))
from infra import gsc_auth
from datascience import pull_gsc_search_analytics

# 1. Autenticar (service account JSON via env var GSC_SA_JSON o ruta explicita)
service = gsc_auth()  # o gsc_auth("/ruta/fuera/del/repo/sa.json")

# 2. Extraer datos SEO por query + page de los ultimos dias (hasta hoy-3 por el lag)
rows = pull_gsc_search_analytics(
    service,
    site_url="sc-domain:ejemplo.com",      # propiedad de dominio
    # site_url="https://ejemplo.com/",     # alternativa: propiedad de prefijo
    start_date="2026-06-01",
    end_date="2026-06-17",
    dimensions=["query", "page"],
)

print(len(rows), "filas")
# rows[0] -> {'query': 'comprar zapatillas', 'page': 'https://ejemplo.com/zapatillas',
#             'clicks': 5, 'impressions': 100, 'ctr': 0.05, 'position': 12.3}

# Desglose temporal (1 fila por dia) con tope de filas:
serie = pull_gsc_search_analytics(
    service, "sc-domain:ejemplo.com", "2026-06-01", "2026-06-17",
    dimensions=["date"], max_total_rows=1000,
)
```

## Cuando usarla

Cuando necesites ingerir datos SEO de Google Search Console (impresiones, clicks,
CTR, posicion por query/pagina/fecha/pais/dispositivo) para volcarlos a DuckDB o
Postgres. Es el paso de extraccion del pipeline SEO: primero `gsc_auth` para
construir el `service`, luego esta funcion para traer las filas paginadas y
aplanadas, listas para upsert en una tabla.

## Gotchas

- **Lag de 2-3 dias**: GSC no tiene los datos del dia actual ni los 1-2 previos
  completos. Pide `end_date` = hoy-3 para evitar dias parciales que luego cambian.
- **Privacy threshold (anonimizacion)**: las queries de baja frecuencia se ocultan
  por privacidad de Google. La suma de clicks/impressions por `query` NO cuadra con
  el total agregado sin dimension `query` — falta la "cola" anonimizada. Para totales
  exactos, pide tambien una consulta sin la dimension `query` (ej. solo `["date"]`).
- **Formato de site_url**: `sc-domain:ejemplo.com` para propiedad de dominio; URL
  completa con esquema y barra final `https://ejemplo.com/` para propiedad de prefijo.
  Si no coincide con como esta dada de alta la propiedad, la API devuelve 403/permission.
- **Tope 25000 filas/request**: `row_limit` se clampa a 25000. Para propiedades grandes
  la paginacion puede dar muchas requests; vigila los rate limits de la API (la funcion
  no reintenta — el error de quota se propaga al caller).
- **Permisos**: la service account debe estar añadida como usuario (al menos lectura)
  en la propiedad de GSC; si no, error de permisos al ejecutar el query.

## Notas

`service` se inyecta ya construido (separacion auth/extraccion), por eso esta funcion
no aparece acoplada a `gsc_auth` en `uses_functions`: no la importa ni la llama. El
test ejercita la logica de paginado y aplanado con un service mock, sin red ni
credenciales. Funcion impura: hace I/O de red contra la API de Google; cualquier error
HTTP (auth, permisos, quota) se propaga.
