De Google Sheets para um Data Lake Consultável em 3 Comandos
Toda empresa tem aquela planilha. O rastreador de orçamento de marketing. A previsão de vendas. O roadmap de produto com 47 abas. Ela vive no Google Sheets porque é onde times não-técnicos trabalham.
Você precisa desses dados no seu data lake. Não como um export CSV que você lembra de baixar nas sextas. Como uma tabela Parquet adequada que você pode fazer JOIN com seus dados de produção e consultar com SQL.
O DataSpoc Pipe tem um tap Google Sheets integrado. Sem pacotes extras, sem dança OAuth para sheets públicas, sem arquivos de configuração Singer. Três comandos.
O Cenário
Seu time de marketing mantém um Google Sheet com dados de gasto em campanhas:
https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms/editEle tem duas planilhas (abas):
- campaigns — nome da campanha, canal, orçamento, gasto, data_início, data_fim
- daily_metrics — data, campanha, impressões, cliques, conversões, custo
Você quer isso no seu data lake como Parquet, consultável com SQL, com JOIN possível com seus dados de receita do Postgres.
Comando 1: Inicializar o Projeto
dataspoc-pipe init marketing-lake --bucket s3://company-marketingSaída:
Created project: marketing-lake/ dataspoc-pipe.yaml pipelines/Comando 2: Adicionar o Pipeline Google Sheets
dataspoc-pipe add sheets-marketing \ --source google_sheets \ --url "https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms" \ --target s3://company-marketingIsso gera a configuração do 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_sheetsPronto. Sem chaves de API necessárias para sheets públicas. Para sheets privadas, defina GOOGLE_SERVICE_ACCOUNT_JSON como variável de ambiente.
Comando 3: Executar
dataspoc-pipe run sheets-marketingSaída:
[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.2sSeu data lake agora tem:
s3://company-marketing/ .dataspoc/ manifest.json state/sheets-marketing/state.json raw/google_sheets/ campaigns/ 20260415_070000.parquet daily_metrics/ 20260415_070000.parquetConsultando com DataSpoc Lens
Agora conecte o Lens e comece a consultar:
dataspoc-lens add-bucket marketing s3://company-marketingdataspoc-lens shellVocê agora está em um shell SQL DuckDB com seus dados do 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: Queries em Linguagem Natural
Para exploração rápida, use AI Ask:
dataspoc-lens ask "Which campaign had the best conversion rate last week?"Saída:
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 │ └─────────┴────────┴─────────────┴─────────────────────┘Mantendo Atualizado
Configure um cron para atualizar diariamente:
# Refresh sheets data every morning at 6am0 6 * * * cd /opt/marketing-lake && dataspoc-pipe run sheets-marketingOu use o agendamento integrado do 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_sheetsSheets Privadas
Para sheets que não são compartilhadas publicamente, use uma Google Service Account:
export GOOGLE_SERVICE_ACCOUNT_JSON=/path/to/service-account.jsondataspoc-pipe run sheets-marketingCompartilhe a sheet com o email da service account, e funciona exatamente da mesma forma.
Fazendo JOIN de Dados do Sheets com Outras Fontes
O verdadeiro poder é combinar dados de planilha com seus dados de produção. Se você também tem um pipeline 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 Que Isso Funciona para Times Pequenos
- Sem infraestrutura — sem Airflow, sem Fivetran, sem dbt Cloud
- Sem codificação — o time de negócio continua editando sua planilha, você ganha Parquet
- SQL em tudo — uma vez que é Parquet, é apenas mais uma tabela
- Pronto para IA — AI Ask funciona com dados de planilha igual aos dados de produção
- Grátis — DataSpoc Pipe é open source, Google Sheets API é grátis para uso razoável
Três comandos. Planilha pública para data lake consultável. Essa é a promessa das ferramentas de dados modernas.