---
name: scrape_google_trends
kind: function
lang: py
domain: datascience
version: "1.0.0"
purity: impure
signature: "def scrape_google_trends(keywords: list[str], geo: str = \"ES\", timeframe: str = \"now 7-d\", include_related: bool = True) -> list[dict]"
description: "Capta interes de busqueda de Google Trends por keyword/nicho via pytrends. El interes es relativo 0-100, NUNCA volumen absoluto. Aplana interest_over_time + related_queries (rising/top) en filas con schema fijo que casa 1:1 con la tabla Postgres google_trends. Backoff/retry ante 429."
tags: [google-trends, pytrends, trends, market-intel, datascience]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [pytrends, time]
params:
  - name: keywords
    desc: "lista de terminos/nichos a consultar (max 5 por payload, limite de Google Trends). Cada elemento es una keyword string."
  - name: geo
    desc: "codigo de pais ISO-3166 (ej. 'ES', 'US', '' para mundial). Default 'ES'."
  - name: timeframe
    desc: "ventana temporal en sintaxis pytrends (ej. 'now 7-d', 'today 3-m', 'today 12-m', '2024-01-01 2024-12-31'). Default 'now 7-d'."
  - name: include_related
    desc: "si True anade filas metric='rising' y metric='top' de related_queries por keyword. Si False solo interest_over_time. Default True."
output: "lista de dicts con claves EXACTAS {geo, timeframe, keyword, metric, point_date, value, related_query}. Tres tipos de fila segun metric: 'interest_over_time' (point_date=fecha ISO, value=0-100, related_query=None), 'rising' (related_query=query, value=valor rising o BREAKOUT_SENTINEL, point_date=None), 'top' (related_query=query, value=0-100, point_date=None). No incluye id/snapshot_date/scraped_at (los anade el ingest)."
tested: false
tests: []
test_file_path: ""
file_path: "python/functions/datascience/scrape_google_trends.py"
---

## Ejemplo

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

# Interes de busqueda en Espana, ultimos 7 dias, con related queries
rows = scrape_google_trends(
    ["coche electrico", "panel solar"],
    geo="ES",
    timeframe="now 7-d",
    include_related=True,
)

# Cada fila tiene el mismo schema, listo para insertar en Postgres google_trends:
# {"geo": "ES", "timeframe": "now 7-d", "keyword": "coche electrico",
#  "metric": "interest_over_time", "point_date": "2026-06-12", "value": 73,
#  "related_query": None}
#
# {"geo": "ES", "timeframe": "now 7-d", "keyword": "coche electrico",
#  "metric": "rising", "point_date": None, "value": 999999,   # "Breakout"
#  "related_query": "ayudas coche electrico 2026"}
#
# {"geo": "ES", "timeframe": "now 7-d", "keyword": "panel solar",
#  "metric": "top", "point_date": None, "value": 100,
#  "related_query": "placas solares precio"}

interes = [r for r in rows if r["metric"] == "interest_over_time"]
print(len(interes), "puntos de interes temporal")
```

## Cuando usarla

Cuando necesites medir el interes/momentum de un nicho o keyword en el tiempo (market intelligence, deteccion de tendencias, validacion de demanda de producto) y vayas a persistirlo en la tabla Postgres `google_trends`. Usala antes del ingest: devuelve filas crudas con el schema exacto de la tabla, sin los campos que pone el ingest (id, snapshot_date, scraped_at). Pon `include_related=False` si solo te interesa la serie temporal y quieres minimizar la superficie de rate-limit.

## Gotchas

- **API no oficial + rate-limit (429).** pytrends scrapea una API interna de Google que NO es publica. Google la limita agresivamente: rafagas de llamadas devuelven HTTP 429. La funcion reintenta con backoff incremental (5s, 15s, 30s) ante 429; si tras esos reintentos sigue limitada, lanza `RuntimeError` mencionando explicitamente el rate-limit. En entornos de CI/headless es habitual recibir 429 a la primera — no es un bug de la funcion.
- **Puede romperse sin aviso.** Al depender de un endpoint interno, Google puede cambiarlo y romper pytrends en cualquier momento. Trata los fallos como esperados y cachea resultados aguas arriba.
- **Interes relativo, NO volumen absoluto.** Los valores 0-100 estan normalizados DENTRO del payload consultado (mismo geo + timeframe + conjunto de keywords). 100 = el pico del conjunto, no "100 busquedas". No son comparables entre payloads distintos. Cambiar el set de keywords reescala todos los valores.
- **"Breakout" en rising.** Google marca como la cadena literal `"Breakout"` (en vez de un %) las related_queries rising cuyo crecimiento supera ~5000%. Para mantener la columna `value` numerica en Postgres se mapea al sentinel `BREAKOUT_SENTINEL = 999999`. Si necesitas distinguir un breakout real de un valor 999999 legitimo (imposible en la practica para %), filtra por ese sentinel.
- **Maximo 5 keywords por payload.** Limite de Google Trends. Pasar mas keywords hace que pytrends falle o ignore las extra. Trocea en lotes de <=5 y llama varias veces (espaciando para no disparar el 429).
- **DataFrames vacios.** `interest_over_time()` puede volver vacio (keyword sin datos en la ventana) y `related_queries()` devuelve un dict `{keyword: {'top': df|None, 'rising': df|None}}` con valores None. La funcion maneja ambos casos sin petar: simplemente no genera filas para esas combinaciones.
- **Columna `isPartial`.** `interest_over_time()` incluye una columna `isPartial` que marca el ultimo punto como provisional. Se ignora por completo (solo se leen las columnas que coinciden con las keywords).
