--- 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.