- 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
213 lines
10 KiB
Markdown
213 lines
10 KiB
Markdown
# 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.
|