- README reescrito: ejemplos CLI corregidos (flags van en el subcomando scrape), webapp+doctor documentados, requisitos por OS, cookies, datos generados, migracion de maquina - CLAUDE.md actualizado: conteo de tests, refs de linea reparadas, scripts nuevos, checklist de actualizacion de docs - AGENTS.md nuevo: entrada estandar para cualquier agente de codigo (mapa de modulos, comandos, reglas de oro, punteros a docs) - docs/GETTING-STARTED.md: de cero a webapp por OS con troubleshooting - docs/CONFIG.md: referencia completa de las ~25 claves de config - docs/COOKIES.md: adquisicion, import (web/navegador/CLI), rotacion y seguridad - docs/backlog.md reemplaza OPPORTUNITIES.md (distilado: shipped vs pendiente) - auditorias movidas a docs/audits/ y CLIPPER-COMPARISON-AUDIT.md versionado
yt-channel-scraper
Scraper local de canales de YouTube: descubre los vídeos de cada canal con yt-dlp, extrae metadatos y transcripciones, guarda todo en SQLite (con búsqueda full-text) y genera una nota Markdown por vídeo, lista para un vault de Obsidian. Se maneja desde el CLI yt-scraper o desde una webapp local (FastAPI + Alpine) con gestión de canales, jobs en vivo, lector de transcripciones y vault de cookies.
100% local: sin API keys, sin deploy remoto, sin telemetría.
Arquitectura
┌───────────────────────────────────────────────┐
│ webapp (FastAPI) │
│ canales · jobs SSE · lector · cookies · UI │
└───────────────────────┬───────────────────────┘
│
CLI (Click) ▼
yt-scraper ──► pipeline: discover ─► extract ─► parse ─► store ─► render
(yt-dlp (player + (JSON3/ (SQLite (Jinja2
flat) subtítulos) VTT) + FTS5) → .md)
│
data/state.db ◄───────────────────────┘
data/markdown/<Canal>/<fecha>_<slug>.md
discoverlista el canal (1 petición por página);extractbaja metadatos + subtítulos (~2 peticiones por vídeo);parseconvierte JSON3/VTT en segmentos;storepersiste en SQLite con FTS5;renderescribe el.mdcon frontmatter YAML.- CLI, webapp y
watchson fachadas sobre el mismo pipeline: una feature se implementa una vez y se expone en ambas.
Requisitos
- Python >= 3.10 (3.12 recomendado).
- ffmpeg (opcional): solo para
yt-scraper audioy el job de audio de la webapp. - Un runtime JS (node, deno o bun; opcional pero recomendado): yt-dlp lo usa para resolver los challenges de YouTube.
- Plataforma: Windows, macOS (incl. Apple Silicon; todas las dependencias publican wheels arm64) o Linux.
| Herramienta | Windows (winget) | macOS (brew) | Debian/Ubuntu (apt) |
|---|---|---|---|
| Python 3.12 | winget install Python.Python.3.12 |
brew install [email protected] |
sudo apt install python3 python3-venv |
| ffmpeg | winget install Gyan.FFmpeg |
brew install ffmpeg |
sudo apt install ffmpeg |
| node | winget install OpenJS.NodeJS.LTS |
brew install node |
sudo apt install nodejs |
Instalación rápida
Ejecuta siempre los comandos desde la raíz del repo: las rutas de config.yaml (data/, templates/, la BD) se resuelven relativas al directorio de trabajo.
# Opción A: uv (recomendado, respeta uv.lock)
uv sync --all-extras
# Opción B: pip
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e ".[dev,web,analysis]"
Extras: dev (pytest, pytest-cov, httpx), web (fastapi, uvicorn, sse-starlette, python-multipart), analysis (matplotlib, wordcloud).
Máquina nueva desde cero: docs/GETTING-STARTED.md (paso a paso por OS, con troubleshooting).
Arranque
Webapp
Windows — doble clic en start-server.bat:
- La primera vez pasa por
scripts/doctor.ps1, que prepara el entorno si falta (Python 3.12 vía winget +pip install -e ".[web]"). scripts/start-server.ps1busca un puerto libre (8000–8100), arranca uvicorn, registra el proceso en.run/server.info(reutilizable), comprueba/healthzy abre el navegador.- Para parar:
stop-server.bat(mata el proceso registrado y libera el puerto).
macOS / Linux:
make setup # primera vez: scripts/bootstrap.sh (python/ffmpeg/node vía brew si faltan + deps)
make serve # scripts/start-server.sh
make stop # scripts/stop-server.sh
# O directamente, sin Makefile:
scripts/bootstrap.sh && scripts/start-server.sh
Manual (debug):
python -m uvicorn yt_scraper.webapp.app:app --host 127.0.0.1 --port 8000
CLI
El entry point es yt-scraper. OJO: sin subcomando ejecuta un scrape completo del canal de config.yaml. Los flags de scrape van en el subcomando:
yt-scraper scrape # scrape del canal de config.yaml (incremental)
yt-scraper -c "https://www.youtube.com/@Nostal-Vlad/videos" scrape # otro canal sin tocar config
# Discovery sin descargar nada (ver qué encontraría)
yt-scraper scrape --dry-run --limit 10
# Solo vídeos desde una fecha / limitar la tirada
yt-scraper scrape --since 2025-01-01 --limit 5
# Reintentar vídeos en error/no_subtitles y continuar
yt-scraper scrape --reset-errors
# Recorrer el canal entero (ignora la ventana incremental)
yt-scraper scrape --full
Flags del grupo raíz (aplican a todo): --config, --channel/-c, --all-channels, --cookies, --cookies-from-browser, --verbose/-v.
Canales (multi-canal):
yt-scraper channels add "https://www.youtube.com/@Fazt"
yt-scraper channels list
yt-scraper channels remove "@Fazt"
yt-scraper --all-channels scrape # aplica el subcomando a todos los canales
Búsqueda y export (sobre lo ya scrapeado, sin tocar la red):
yt-scraper search "vaporwave" # FTS5 en transcripciones, con timestamp
yt-scraper -c "@Fazt" search "typescript" -l 50
yt-scraper export --format json # json | csv | srt | html → data/exports/
yt-scraper --all-channels export --format srt
Recuperación (DB ↔ disco):
yt-scraper reset # error/no_subtitles → pending (pregunta antes)
yt-scraper reset --status error --yes
yt-scraper reconcile # re-escanea data/markdown y cuadra la DB con el disco
yt-scraper reconcile --prune # además devuelve a pending los done sin .md
yt-scraper re-render # regenera .md desde segmentos guardados (0 descargas)
yt-scraper re-render --backfill # antes importa segmentos desde los .md existentes
Audio, monitoreo y análisis:
yt-scraper audio --limit 5 # MP3 de los done (requiere ffmpeg) → data/audio/
yt-scraper watch --interval 6h # discovery periódico y procesa lo nuevo
yt-scraper watch --once # una sola pasada
yt-scraper analyze --top-words 50 # frecuencia → data/analysis/top_words.{csv,png}
yt-scraper analyze --wordcloud # + data/analysis/wordcloud.png
yt-scraper analyze --timeline "ia" # menciones de un término por mes
Configuración
Copia la plantilla y edita:
cp config.example.yaml config.yaml # Windows: copy config.example.yaml config.yaml
config.yaml es privado y está gitignored; la plantilla versionada (config.example.yaml) documenta todas las claves. Bloques principales:
| Bloque | Qué controla |
|---|---|
raíz (channel_url, languages, include_shorts, ...) |
canal por defecto, idiomas de subtítulos (modo manual/auto/any por idioma), filtros de shorts/live/duración |
delay |
pacing: pausas entre vídeos, backoff ante rate-limit, circuit breaker, intervalo mínimo global entre peticiones |
yt_dlp |
reintentos, pausa entre sub-peticiones, timeouts de yt-dlp |
sync |
discovery incremental: ventana inicial, máximo y solapamiento que da la sincronización por alcanzada |
database_path, output_dir, template_path, filename_template |
rutas (relativas al CWD) y patrón del nombre de las notas |
Referencia completa de cada clave con tipos y defaults: docs/CONFIG.md.
Cookies
Las cookies de sesión de YouTube (formato Netscape) sirven para acceder a vídeos de membresía y reducir los bot-checks. El vault (cookies/) admite varias, con exactamente una activa; se pueden importar:
- Webapp: arrastrar y soltar un
cookies.txt, o importar directamente desde el navegador local (Brave). - CLI:
yt-scraper --cookies ruta/cookies.txt scrapeo--cookies-from-browser brave(chrome|firefox|edge|brave).
Cómo exportarlas desde tu navegador y cómo rotarlas: docs/COOKIES.md. Son equivalentes a tu contraseña: nunca las commitees (están en .gitignore).
Datos generados
data/
├── state.db # SQLite (WAL): vídeos, canales, segmentos, FTS5, jobs, cookies_meta
├── markdown/<Canal>/ # una nota .md por vídeo: <fecha>_<slug>.md con frontmatter YAML
├── thumbnails/<id>.jpg # miniaturas
├── avatars/ # avatares de canal
├── audio/ # MP3 descargados (webapp: <video_id>.mp3; CLI: por título)
├── videos/<id>/ # descargas de vídeo completas (.webm)
├── exports/ # export json/csv/srt/html
└── analysis/ # wordcloud, top_words, timeline
cookies/ # vault de cookies Netscape (gitignored, SENSIBLE)
.run/ # estado del servidor (puerto, PID)
Estados de un vídeo en la BD: pending (descubierto, sin procesar), done (transcripción + .md), no_subtitles (sin pista válida según la política de idiomas), error (fallo: privado, bloqueo, ...).
data/, cookies/, config.yaml y .run/ están gitignored: contienen tu biblioteca y tu sesión. No los commitees.
Tests
uv run python -m pytest -q # o: python -m pytest -q (dentro del venv)
Suite hermética (~240 tests en 24 archivos, sin red): todo con tmp_path + monkeypatch; yt_dlp.YoutubeDL se sustituye por un fake.
Migrar el repo a otra máquina
- Las rutas se resuelven relativas al CWD: sigue ejecutando todo desde la raíz del repo.
- Si mueves o clonas el repo, la instalación editable apunta a la ruta vieja: re-ejecuta
uv sync --all-extras(opip install -e ".[dev,web,analysis]") y recrea el venv si hace falta. config.yamlycookies/no viajan en el clon: recréalos desdeconfig.example.yamly re-exporta las cookies.
Licencia
TBD.