Extraccion autenticada: - import_from_browser (Brave) con fallback CDP headless para cookies app-bound v20 - extract_via_watch_page: GET plano + ytInitialPlayerResponse cuando yt-dlp falla con sesion logueada (members-only); regex y opener cacheados - js_runtimes (node/deno/bun/quickjs) propagado a todos los ydl_opts - rutas de Brave multiplataforma (Windows/macOS/Linux) Webapp UX: chips de filtros removibles, skeleton loaders, estado de vista en URL, memoria de scroll, copyMd/openMd, import de cookies desde navegador, no-cache de statics Rendimiento: - entorno Jinja2 cacheado por directorio de plantilla (antes 1 por nota) - _rank_unranked con guarda (antes full-scan en cada arranque/import) - upsert_videos con executemany; dashboard sin N+1 (GROUP BY + conteo de tags en SQL) - thumbnails en paralelo (6 hilos, CDN ytimg); handlers bloqueantes -> def (threadpool) - reconcile de arranque en hilo daemon: uvicorn arriba al instante (0.95s con 1503 md), healthz expone reconcile_done - Store.transaction(): escrituras por video agrupadas (~6 commits -> 3) Refactor: helpers unicos (extract_handle->discover, safe_dirname/filename->render, order_pending->store, keep_ref->config, seconds_to_ts solo en segments); re-render del CLI delega en pipeline.re_render_videos (retira huerfanos y marca done); fuera wrappers muertos de segments.py
yt-channel-scraper
Scraper de canales de YouTube que extrae transcripciones, capítulos y metadatos completos, generando notas Markdown listas para Obsidian.
Basado en la ingeniería inversa de Obsidian Web Clipper — usa yt-dlp internamente, que implementa el mismo mecanismo InnerTube (youtubei/v1/player con clientes ANDROID/IOS/WEB) que la extensión audita.
Instalación
cd yt-channel-scraper
pip install -e ".[dev]"
Requiere Python ≥ 3.10.
Uso
Scrape completo de un canal
yt-scraper --channel "https://www.youtube.com/@Nostal-Vlad/videos"
Dry run (ver qué descubriría sin descargar)
yt-scraper --channel "https://www.youtube.com/@Nostal-Vlad/videos" --dry-run --limit 10
Solo vídeos recientes
yt-scraper --channel "https://www.youtube.com/@Nostal-Vlad/videos" --since 2025-01-01
Reanudar tras interrupción
yt-scraper --channel "https://www.youtube.com/@Nostal-Vlad/videos"
El estado se guarda en data/state.db (SQLite). Los vídeos ya procesados se saltan automáticamente.
Reintentar vídeos con error
yt-scraper --channel "https://www.youtube.com/@Nostal-Vlad/videos" --reset-errors
Opciones
| Flag | Descripción | Default |
|---|---|---|
--channel, -c |
URL del canal | de config.yaml |
--config |
Ruta al YAML de configuración | config.yaml |
--limit N |
Procesar solo N vídeos | sin límite |
--since DATE |
Solo vídeos desde YYYY-MM-DD | sin filtro |
--languages, -l |
Idiomas preferidos (coma-sep) | es,en |
--no-auto |
Ignorar subtítulos auto-generados | false |
--no-shorts |
Excluir Shorts | de config |
--include-shorts |
Incluir Shorts | de config |
--resume/--no-resume |
Saltar procesados | --resume |
--dry-run |
Solo discovery, no descargar | false |
--reset-errors |
Reintentar vídeos con error | false |
--verbose, -v |
Logging DEBUG | false |
Configuración
Copia config.example.yaml a config.yaml y edita:
channel_url: "https://www.youtube.com/@Nostal-Vlad/videos"
languages: ["es", "es-419", "en"]
prefer_manual: true
include_shorts: false
min_duration_sec: 30
delay:
min_seconds: 1.5
max_seconds: 3.5
Output
Cada vídeo genera un archivo Markdown en data/markdown/@canal/:
data/markdown/Nostal Vlad/
├── 2026-07-26_asi-era-ser-una-adolescente-edgy-en-los-2000.md
├── 2026-07-12_la-estetica-que-romantiza-ser-un-perdedor-losercore.md
└── ...
Formato de cada nota:
---
video_id: gOUyxFwWQqA
title: "La Estética Que ROMANTIZA ser un \"PERDEDOR\" | Losercore"
channel: Nostal Vlad
upload_date: 2026-07-12
duration: 1069
url: https://www.youtube.com/watch?v=gOUyxFwWQqA
transcript_lang: es-orig
transcript_src: auto
views: 100523
likes: 8196
---
# La Estética Que ROMANTIZA ser un "PERDEDOR" | Losercore
> [Ver en YouTube](https://www.youtube.com/watch?v=gOUyxFwWQqA)
## Transcripcion
### Intro (00:15)
**00:15** · Una de las estéticas que ha cobrado más relevancia últimamente...
**00:28** · que hacer esto. Y es que el loser core como tal es muy difuso...
### Losercore (01:38)
**01:38** · ...
Estados en SQLite
| Status | Significado |
|---|---|
pending |
Descubierto, sin procesar |
done |
Transcripción extraída y Markdown generado |
no_subtitles |
El vídeo no tiene subtítulos (ni manuales ni auto) |
error |
Error al procesar (miembros-only, privado, bloqueo, etc.) |
Arquitectura
cli.py Orquestador (Click + Rich progress)
├── discover.py yt-dlp --flat-playlist → lista de videoIds
├── store.py SQLite: estado, resume, dedup
├── extract.py yt-dlp.extract_info → metadata + subtítulos
├── parse.py JSON3/VTT → segmentos {start, end, text}
├── chapters.py align_chapters: capítulos ↔ segmentos
├── render.py Jinja2 → Markdown con frontmatter YAML
└── ratelimit.py delays aleatorios + backoff exponencial
Tests
python -m pytest tests/ -v
Mantenimiento
Si la extracción falla tras una actualización de YouTube:
yt-dlp -U # actualizar yt-dlp
pip install -U yt-dlp
Las versiones de cliente InnerTube (ANDROID 20.10.x, IOS 20.10.x, WEB 2.2024xxxx) rotan mensualmente. yt-dlp las mantiene actualizadas.