Como automatizo a coleta de métricas de playlists do YouTube com Python
Passo a passo prático para extrair IDs, títulos e visualizações via YouTube Data API, consolidar em CSV e agendar rotinas diárias para decisões de conteúdo.

Em muitos freelas e projetos de growth, eu preciso levantar dados de conteúdo com rapidez, confiabilidade e zero clique manual. Um caso clássico, extrair títulos, IDs e visualizações de todos os vídeos de uma ou várias playlists do YouTube para alimentar um dashboard, validar hipóteses de SEO, medir retenção por série e priorizar próximos roteiros. Abaixo está meu passo a passo completo, com código de produção, boas práticas e dicas para agendar a automação.
Visão geral do que vamos construir
- Autentico no YouTube Data API com OAuth em modo desktop e salvo o token para evitar relogar a cada execução.
- Busco todosos vídeos de uma playlist, com paginação.
- Consulto em lote as estatísticasde cada vídeo, em especial 'viewCount', e trago também título, data e link.
- Entrego um CSV consolidado, pronto para Power BI, Google Sheets ou Metabase, e explico como agendar a rotina.
Pré-requisitos
- Python 3.10+
- Instalar dependências:
-
- pip install google-api-python-client google-auth-httplib2 google-auth-oauthlib python-dateutil
No Google Cloud Console, crie um projeto, ative YouTube Data API v3e gere credenciais OAuth Client IDdo tipo Desktop app. Baixe o JSON e salve na raiz do projeto como 'client_secret.json'.
Adicione ao '.gitignore': 'client_secret.json' e 'token.json'.
Código completo, pronto para usar (Aceita URL da playlistou ID. Faz paginação automática, busca estatísticas em lote e grava CSV.):
from _future_ import annotations
import csv
import os
from typing import List, Dict, Iterable
from datetime import datetime
from dateutil import tz
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
# Escopo somente leitura
SCOPES = ["https://www.googleapis.com/auth/youtube.readonly"]
def authenticate(api_name: str = "youtube", api_version: str = "v3"):
creds = None
if os.path.exists("token.json"):
creds = Credentials.from_authorized_user_file("token.json", SCOPES)
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file("client_secret.json", SCOPES)
# Em ambiente local, uso um servidor local para o fluxo OAuth
creds = flow.run_local_server(port=0)
with open("token.json", "w") as token:
token.write(creds.to_json())
return build(api_name, api_version, credentials=creds)
def extract_playlist_id(url_or_id: str) -> str:
if "list=" in url_or_id:
from urllib.parse import urlparse, parse_qs
q = parse_qs(urlparse(url_or_id).query)
pl = q.get("list", [""])[0]
if not pl:
raise ValueError("Não foi possível extrair o parâmetro 'list' da URL.")
return pl
return url_or_id.strip()
def chunked(iterable: Iterable[str], n: int) -> Iterable[List[str]]:
bucket = []
for item in iterable:
bucket.append(item)
if len(bucket) == n:
yield bucket
bucket = []
if bucket:
yield bucket
def get_playlist_video_ids(youtube, playlist_id: str) -> List[str]:
video_ids: List[str] = []
page_token = None
while True:
resp = youtube.playlistItems().list(
part="contentDetails",
playlistId=playlist_id,
maxResults=50,
pageToken=page_token
).execute()
for item in resp.get("items", []):
vid = item["contentDetails"]["videoId"]
video_ids.append(vid)
page_token = resp.get("nextPageToken")
if not page_token:
break
# remove duplicados preservando a ordem
seen = set()
deduped = []
for v in video_ids:
if v not in seen:
deduped.append(v)
seen.add(v)
return deduped
def get_video_stats(youtube, video_ids: List[str]) -> List[Dict]:
results: List[Dict] = []
for batch in chunked(video_ids, 50):
resp = youtube.videos().list(
part="statistics,snippet",
id=",".join(batch),
maxResults=50
).execute()
for item in resp.get("items", []):
stats = item.get("statistics", {})
snippet = item.get("snippet", {})
results.append({
"video_id": item["id"],
"title": snippet.get("title", "").strip(),
"views": int(stats.get("viewCount", 0)),
"published_at": snippet.get("publishedAt", ""),
"url": f"https://www.youtube.com/watch?v={item['id']}"
})
return results
def save_to_csv(rows: List[Dict], out_path: str) -> None:
if not rows:
print("Nenhum dado para salvar.")
return
fieldnames = ["video_id", "title", "views", "published_at", "url"]
with open(out_path, "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(rows)
def br_time_iso_to_local(iso: str) -> str:
if not iso:
return ""
utc = datetime.fromisoformat(iso.replace("Z", "+00:00"))
local = utc.astimezone(tz.gettz("America/Sao_Paulo"))
return local.strftime("%Y-%m-%d %H:%M:%S")
def run(playlists: List[str], out_csv: str = "youtube_playlist_stats.csv") -> None:
youtube = authenticate()
all_rows: List[Dict] = []
for raw in playlists:
pl_id = extract_playlist_id(raw)
print(f"Coletando IDs da playlist {pl_id}...")
ids = get_playlist_video_ids(youtube, pl_id)
if not ids:
print(f"Playlist {pl_id} sem vídeos públicos.")
continue
print(f"Consultando estatísticas de {len(ids)} vídeos...")
rows = get_video_stats(youtube, ids)
for r in rows:
r["published_at"] = br_time_iso_to_local(r["published_at"])
all_rows.extend(rows)
# Ordeno por views desc para facilitar análise
all_rows.sort(key=lambda r: r["views"], reverse=True)
save_to_csv(all_rows, out_csv)
print(f"Concluído, {len(all_rows)} vídeos salvos em {out_csv}")
if _name_ == "_main_":
# Exemplos, pode misturar URLs e IDs
playlists_de_trabalho = [
# "https://www.youtube.com/playlist?list=PLxxxxxxxxxxxxxxxx",
# "https://www.youtube.com/watch?v=XXXXXXXX&list=PLyyyyyyyyyyyyyyyy",
# "PLzzzzzzzzzzzzzzzzzz"
]
if not playlists_de_trabalho:
print("Defina suas playlists em 'playlists_de_trabalho' e rode novamente.")
else:
try:
run(playlists_de_trabalho)
except HttpError as e:
print(f"Erro na API, detalhe: {e}")
Como uso no dia a dia
Colei a URL da playlist no array 'playlists_de_trabalho'.
Rodei o script uma vez, fiz login no fluxo OAuth e o 'token.json' ficou guardado, então nas próximas execuções o login é automático.
O CSV sai ordenado por visualizações, com título e link, pronto para pivot, análise de pareto ou enriquecimento em um BI.
Pontos de atenção que sempre considero
Paginação. A API devolve no máximo 50 itens por página, então é obrigatório usar 'nextPageToken', como no meu código.
Lote máximo de IDs. Em 'videos().list', mando até 50 IDs por vez, por isso uso a função 'chunked'.
Quotas e backoff. Em picos, a API pode responder 'quotaExceeded' ou 'rateLimitExceeded'. Eu capturo 'HttpError' e, em cenários de operação 24x7, uso backoff exponencial com 'time.sleep' progressivo.
Privacidade. Vídeos privados ou removidos não retornam dados completos, e podem reduzir o total coletado.
Segurança. Nunca commito 'client_secret.json' nem 'token.json'. Ambos ficam fora do repositório e protegidos por permissões de arquivo.
API Key x OAuth. Para leitura pública simples, uma API key funciona, porém eu padronizo OAuth, já que me permite escalar para casos que exigem escopos de leitura de dados do canal do cliente, quando ele me autoriza.
Extendendo o freela para gerar mais valor
Título no lugar do ID. Meu código já inclui 'title', então dá para gerar ranking de vídeos por desempenho por período.
Filtro temporal. Basta filtrar 'published_at' por intervalo, por exemplo, últimos 90 dias para olhar tração recente.
Exportação. Já entrego CSV, mas frequentemente jogo direto para Google Sheets com 'gspread', o que facilita o trabalho do time de conteúdo.
Múltiplas playlists. É comum o cliente ter séries por produto, então aceito várias playlists de uma vez e consolido no mesmo CSV.
BI. O CSV alimenta um dashboard com métricas chave, como visualizações por série, top 10 vídeos por período, e tendência semanal.
Como eu agendo a automação
Escolho conforme o ambiente do cliente.
Windows, Agendador de Tarefas
1. Abra o Agendador de Tarefas, crie uma tarefa básica.
2. Acionador diário às 9h.
3. Ação, Iniciar um programa.
Programa: caminho do Python, por exemplo 'C:\Python312\python.exe'
Argumentos: caminho do script, por exemplo '"C:\projetos\yt\coleta.py"'
Iniciar em: pasta do projeto, para garantir que encontre os JSONs.
Linux e macOS, cron:
crontab -e
# Executa às 09:00 todos os dias
0 9 /usr/bin/python3 /home/usuario/projetos/yt/coleta.py >> /home/usuario/projetos/yt/log.txt 2>&1
Se prefiro dentro do próprio Python, uso a biblioteca 'schedule' para intervalos simples, lembrando que o processo precisa ficar em execução.
Check rápido do resultado
O CSV gerado terá colunas:
video_id,title,views,published_at,url
dQw4w9WgXcQ,Minha Aula 01,12345,2025-08-01 10:05:10,https://www.youtube.com/watch?v=dQw4w9WgXcQ
Com isso, eu atendo o requisito do freela em minutos, padronizo a coleta para qualquer playlist e ainda deixo o cliente com um processo agendado e auditável.