De Google Sheets a un Data Lake Consultable en 3 Comandos
Toda empresa tiene esa hoja de cálculo. El rastreador de presupuesto de marketing. El pronóstico de ventas. La hoja de ruta del producto con 47 pestañas. Vive en Google Sheets porque ahí es donde los equipos no técnicos trabajan.
Necesitas esos datos en tu data lake. No cómo una exportación CSV que recuerdas descargar los viernes. Como una tabla Parquet apropiada que puedas cruzar con tus datos de producción y consultar con SQL.
DataSpoc Pipe tiene un tap de Google Sheets integrado. Sin paquetes extra, sin danza de OAuth para hojas públicas, sin archivos de configuración de Singer. Tres comandos.
El Escenario
Tu equipo de marketing mantiene una hoja de Google Sheets con datos de gasto en campañas:
https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms/editTiene dos hojas (pestañas):
- campaigns — nombre de campaña, canal, presupuesto, gasto, fecha_inicio, fecha_fin
- daily_metrics — fecha, campaña, impresiones, clics, conversiónes, costo
Quieres esto en tu data lake cómo Parquet, consultable con SQL, cruzable con tus datos de ingresos de Postgres.
Comando 1: Inicializar el Proyecto
dataspoc-pipe init marketing-lake --bucket s3://company-marketingSalida:
Created project: marketing-lake/ dataspoc-pipe.yaml pipelines/Comando 2: Agregar el Pipeline de Google Sheets
dataspoc-pipe add sheets-marketing \ --source google_sheets \ --url "https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms" \ --target s3://company-marketingEsto genera la configuración del pipeline:
pipeline: sheets-marketingsource: type: google_sheets url: "https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms" sheets: - campaigns - daily_metrics
target: type: s3 bucket: s3://company-marketing prefix: raw/google_sheetsEso es todo. Sin claves de API necesarias para hojas públicas. Para hojas privadas, configura GOOGLE_SERVICE_ACCOUNT_JSON cómo variable de entorno.
Comando 3: Ejecutar
dataspoc-pipe run sheets-marketingSalida:
[sheets-marketing] Starting extraction...[sheets-marketing] Extracting sheet: campaigns (156 rows)[sheets-marketing] Extracting sheet: daily_metrics (4,380 rows)[sheets-marketing] Writing: s3://company-marketing/raw/google_sheets/campaigns/20260415_070000.parquet[sheets-marketing] Writing: s3://company-marketing/raw/google_sheets/daily_metrics/20260415_070000.parquet[sheets-marketing] Done. 2 tables, 4,536 rows, 3.2sTu data lake ahora tiene:
s3://company-marketing/ .dataspoc/ manifest.json state/sheets-marketing/state.json raw/google_sheets/ campaigns/ 20260415_070000.parquet daily_metrics/ 20260415_070000.parquetConsultar con DataSpoc Lens
Ahora conecta Lens y empieza a consultar:
dataspoc-lens add-bucket marketing s3://company-marketingdataspoc-lens shellAhora estás en un shell SQL de DuckDB con tus datos de Sheets montados:
-- See available tablesSHOW TABLES;┌─────────────────────────────────────┐│ name │├─────────────────────────────────────┤│ raw__google_sheets__campaigns ││ raw__google_sheets__daily_metrics │└─────────────────────────────────────┘-- Total spend by channelSELECT channel, COUNT(*) as campaigns, SUM(spend) as total_spend, SUM(budget) as total_budget, ROUND(SUM(spend) / SUM(budget) * 100, 1) as pct_usedFROM raw__google_sheets__campaignsGROUP BY channelORDER BY total_spend DESC;┌──────────┬───────────┬─────────────┬──────────────┬──────────┐│ channel │ campaigns │ total_spend │ total_budget │ pct_used │├──────────┼───────────┼─────────────┼──────────────┼──────────┤│ google │ 45 │ 125,400 │ 150,000 │ 83.6 ││ meta │ 38 │ 98,200 │ 120,000 │ 81.8 ││ linkedin │ 22 │ 67,800 │ 80,000 │ 84.8 ││ twitter │ 51 │ 34,500 │ 50,000 │ 69.0 │└──────────┴───────────┴─────────────┴──────────────┴──────────┘-- Cost per conversion by campaign (last 30 days)SELECT c.campaign_name, c.channel, SUM(d.cost) as total_cost, SUM(d.conversions) as total_conversions, ROUND(SUM(d.cost) / NULLIF(SUM(d.conversions), 0), 2) as cpaFROM raw__google_sheets__daily_metrics dJOIN raw__google_sheets__campaigns c ON d.campaign = c.campaign_nameWHERE d.date >= CURRENT_DATE - INTERVAL '30 days'GROUP BY c.campaign_name, c.channelHAVING SUM(d.conversions) > 0ORDER BY cpa ASCLIMIT 10;┌──────────────────────┬──────────┬────────────┬──────────────────┬───────┐│ campaign_name │ channel │ total_cost │ total_conversions│ cpa │├──────────────────────┼──────────┼────────────┼──────────────────┼───────┤│ spring_retargeting │ meta │ 2,340 │ 156 │ 15.00 ││ brand_search │ google │ 4,200 │ 245 │ 17.14 ││ lookalike_q2 │ meta │ 3,800 │ 198 │ 19.19 ││ dev_tools_launch │ linkedin │ 5,600 │ 112 │ 50.00 │└──────────────────────┴──────────┴────────────┴──────────────────┴───────┘AI Ask: Consultas en Lenguaje Natural
Para exploración rápida, usa AI Ask:
dataspoc-lens ask "Which campaign had the best conversion rate last week?"Salida:
SQL: SELECT c.campaign_name, c.channel, SUM(d.clicks) as clicks, SUM(d.conversions) as conversions, ROUND(SUM(d.conversions)::FLOAT / NULLIF(SUM(d.clicks), 0) * 100, 2) as conversion_rate FROM raw__google_sheets__daily_metrics d JOIN raw__google_sheets__campaigns c ON d.campaign = c.campaign_name WHERE d.date >= CURRENT_DATE - INTERVAL '7 days' GROUP BY c.campaign_name, c.channel ORDER BY conversion_rate DESC LIMIT 1
Result: campaign_name: spring_retargeting channel: meta conversion_rate: 12.4%dataspoc-lens ask "Compare Google vs Meta spend efficiency this month"SQL: SELECT c.channel, SUM(d.cost) as spend, SUM(d.conversions) as conversions, ROUND(SUM(d.cost) / NULLIF(SUM(d.conversions), 0), 2) as cost_per_conversion FROM raw__google_sheets__daily_metrics d JOIN raw__google_sheets__campaigns c ON d.campaign = c.campaign_name WHERE d.date >= DATE_TRUNC('month', CURRENT_DATE) AND c.channel IN ('google', 'meta') GROUP BY c.channel
Result: ┌─────────┬────────┬─────────────┬─────────────────────┐ │ channel │ spend │ conversions │ cost_per_conversion │ ├─────────┼────────┼─────────────┼─────────────────────┤ │ meta │ 12,400 │ 523 │ 23.71 │ │ google │ 18,900 │ 687 │ 27.51 │ └─────────┴────────┴─────────────┴─────────────────────┘Mantenerlo Actualizado
Configura un cron para refrescar diariamente:
# Refresh sheets data every morning at 6am0 6 * * * cd /opt/marketing-lake && dataspoc-pipe run sheets-marketingO usa el horario integrado de Pipe:
pipeline: sheets-marketingschedule: "0 6 * * *"source: type: google_sheets url: "https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms" sheets: - campaigns - daily_metricstarget: type: s3 bucket: s3://company-marketing prefix: raw/google_sheetsHojas Privadas
Para hojas que no están compartidas públicamente, usa una Cuenta de Servicio de Google:
export GOOGLE_SERVICE_ACCOUNT_JSON=/path/to/service-account.jsondataspoc-pipe run sheets-marketingComparte la hoja con la dirección de correo de la cuenta de servicio, y funcióna exactamente igual.
Cruzar Datos de Sheets con Otras Fuentes
El verdadero poder es combinar datos de hojas de cálculo con tus datos de producción. Si también tienes un pipeline de Postgres:
-- Join marketing spend with actual revenueSELECT c.channel, SUM(d.cost) as marketing_spend, SUM(o.amount) as revenue, ROUND(SUM(o.amount) / NULLIF(SUM(d.cost), 0), 2) as roasFROM raw__google_sheets__daily_metrics dJOIN raw__google_sheets__campaigns c ON d.campaign = c.campaign_nameJOIN raw__postgres__orders o ON o.utm_source = c.channel AND o.created_at::DATE = d.dateWHERE d.date >= CURRENT_DATE - INTERVAL '30 days'GROUP BY c.channelORDER BY roas DESC;Por Qué Esto Funciona para Equipos Pequeños
- Sin infraestructura — sin Airflow, sin Fivetran, sin dbt Cloud
- Sin código — el equipo de negocio sigue editando su hoja de cálculo, tú obtienes Parquet
- SQL sobre todo — una vez que es Parquet, es solo otra tabla
- Listo para IA — AI Ask funcióna sobre datos de hojas de cálculo igual que sobre datos de producción
- Gratis — DataSpoc Pipe es código abierto, la API de Google Sheets es gratis para uso razonable
Tres comandos. De hoja de cálculo pública a data lake consultable. Esa es la promesa de las herramientas de datos modernas.