Files
yt-channel-scraper/README.md
T
urieljareth 450aee216d docs: documentacion completa para humanos y agentes IA
- 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
2026-09-10 00:32:32 -06:00

213 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
```
- `discover` lista el canal (1 petición por página); `extract` baja metadatos + subtítulos (~2 peticiones por vídeo); `parse` convierte JSON3/VTT en segmentos; `store` persiste en SQLite con FTS5; `render` escribe el `.md` con frontmatter YAML.
- CLI, webapp y `watch` son 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 audio` y 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.
```bash
# 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](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.ps1` busca un puerto libre (8000–8100), arranca uvicorn, registra el proceso en `.run/server.info` (reutilizable), comprueba `/healthz` y abre el navegador.
- Para parar: `stop-server.bat` (mata el proceso registrado y libera el puerto).
**macOS / Linux**:
```bash
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)**:
```bash
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:
```bash
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):
```bash
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):
```bash
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):
```bash
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**:
```bash
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:
```bash
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](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 scrape` o `--cookies-from-browser brave` (chrome|firefox|edge|brave).
Cómo exportarlas desde tu navegador y cómo rotarlas: [docs/COOKIES.md](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
```bash
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` (o `pip install -e ".[dev,web,analysis]"`) y recrea el venv si hace falta.
- `config.yaml` y `cookies/` no viajan en el clon: recréalos desde `config.example.yaml` y re-exporta las cookies.
## Licencia
TBD.