Exportar una lista de reproducción de YouTube a Excel (títulos y enlaces)

Exporta todos los vídeos de cualquier lista de YouTube (tuya o pública) a CSV o texto: título, canal, duración y enlace. Gratis, sin clave de API.

Actualizado para el diseño de YouTube de 2026 (los snippets antiguos que buscan ytd-playlist-video-renderer ya no encuentran la mayoría de listas).

export-playlist.js · 122 líneaspégalo en la consola (F12) de www.youtube.com
// Responsive Muse · YouTube Playlist Exporter · v1.1 · 2026-10-22
// Exports any playlist you can open (your own, Watch Later, Liked videos, public lists,
// or a channel's uploads) to CSV or TXT. Runs only on youtube.com/playlist.
// The script makes no network requests of its own: YouTube loads the rest of the list
// while the script scrolls. It never reads cookies, passwords or tokens.
(async () => {
  const TOOL = 'Exportar lista de reproducción';
  const FORMAT = 'csv';        // 'csv' (Excel, Google Sheets) or 'txt' (one URL per line)

  if (location.hostname !== 'www.youtube.com' || location.pathname !== '/playlist') {
    alert('Abre primero una lista de reproducción, por ejemplo:\nhttps://www.youtube.com/playlist?list=WL');
    return;
  }
  // A small box on the page shows progress and asks before changing anything,
  // so you never need to read or edit the console.
  const many = (n, word) => `${n} ${n === 1 ? word : ({ canal: 'canales', 'canción': 'canciones', 'publicación': 'publicaciones', 'elemento guardado': 'elementos guardados' })[word] || word + 's'}`;   // 1 vídeo, 2 vídeos
  const panel = (() => {
    const box = document.createElement('div');
    box.style.cssText = 'position:fixed;right:16px;bottom:16px;z-index:2147483647;width:340px;max-width:calc(100vw - 32px);' +
      'background:#111318;color:#e6e8ee;font:14px/1.5 system-ui,sans-serif;border-radius:12px;padding:16px;' +
      'box-shadow:0 8px 30px rgba(0,0,0,.4);text-align:left';
    const head = document.createElement('strong');
    head.textContent = TOOL;
    const msg = document.createElement('p');
    msg.style.cssText = 'margin:8px 0 12px;white-space:pre-line';
    const row = document.createElement('div');
    row.style.cssText = 'display:flex;flex-wrap:wrap;gap:8px;justify-content:flex-end';
    box.append(head, msg, row);
    document.body.append(box);
    const button = (label, primary, onClick) => {
      const b = document.createElement('button');
      b.textContent = label;
      b.style.cssText = 'font:600 14px system-ui,sans-serif;padding:8px 14px;border-radius:8px;cursor:pointer;' +
        (primary ? 'background:#0f766e;color:#fff;border:1px solid #0f766e' : 'background:none;color:#e6e8ee;border:1px solid #3a3f4b');
      b.onclick = onClick;
      row.append(b);
    };
    return {
      text(t) { msg.textContent = t; },
      ask(t, yes) {
        msg.textContent = t;
        row.replaceChildren();
        return new Promise(r => {
          button('Cancelar', false, () => { row.replaceChildren(); r(false); });
          button(yes, true, () => { row.replaceChildren(); r(true); });
        });
      },
      stoppable(onStop) { row.replaceChildren(); button('Parar', false, onStop); },
      actions(t, list) {
        msg.textContent = t;
        row.replaceChildren();
        list.forEach(([label, primary, run]) => button(label, primary, run));
      },
      finish(t) { this.actions(t, [['Cerrar', true, () => box.remove()]]); },
      close() { box.remove(); },
    };
  })();
  // Offer the result as a downloaded file or as clipboard text (a real click, so it always works)
  const deliver = (summary, body, file) => {
    const save = () => {
      const a = document.createElement('a');
      a.href = URL.createObjectURL(new Blob([/\.csv$/.test(file) ? '\ufeff' + body : body], { type: 'text/plain;charset=utf-8' }));
      a.download = file;
      a.click();
      setTimeout(() => URL.revokeObjectURL(a.href), 5000);
      panel.text(`${summary}\nGuardado como ${file} (mira tu carpeta de Descargas).`);
    };
    const copy = () => navigator.clipboard.writeText(body)
      .then(() => panel.text(`${summary}\nCopiado. Pégalo en una hoja de cálculo o en una nota.`))
      .catch(() => panel.text(`${summary}\nEl navegador ha bloqueado el portapapeles: usa Descargar.`));
    panel.actions(summary, [['Cerrar', false, () => panel.close()], ['Copiar', false, copy], ['Descargar', true, save]]);
  };
  const sleep = ms => new Promise(r => setTimeout(r, ms));
  // Playlist rows: new layout (yt-lockup-view-model) and old layout (ytd-playlist-video-renderer)
  const rows = () => [...document.querySelectorAll('yt-lockup-view-model, ytd-playlist-video-renderer')]
    .filter(el => el.querySelector('a[href*="/watch?v="][href*="index="]'));

  // 1. Scroll to the end until no new videos appear three times in a row
  let last = -1, idle = 0;
  while (idle < 3) {
    window.scrollTo(0, document.documentElement.scrollHeight);
    await sleep(1200);
    const n = rows().length;
    idle = n === last ? idle + 1 : 0;
    last = n;
    panel.text(`Cargando la lista… ${n} vídeos`);
  }

  // 2. Read every row
  const seen = new Set();
  const videos = [];
  for (const el of rows()) {
    const link = new URL(el.querySelector('a[href*="/watch?v="]').href);
    const id = link.searchParams.get('v');
    const index = link.searchParams.get('index') || '';
    if (seen.has(id + index)) continue;
    seen.add(id + index);
    const text = sel => (el.querySelector(sel)?.textContent || '').trim();
    videos.push({
      index,
      title: el.querySelector('h3')?.getAttribute('title') || text('#video-title'),
      channel: text('a[href^="/@"], a[href^="/channel/"], ytd-channel-name a'),
      duration: text('.ytBadgeShapeText, ytd-thumbnail-overlay-time-status-renderer #text'),
      url: 'https://www.youtube.com/watch?v=' + id,
    });
  }
  if (!videos.length) {
    panel.finish('No se han encontrado vídeos. Puede que YouTube haya cambiado su diseño: avísanos en responsive-muse.com.');
    return 0;
  }

  // 3. Build the file and offer it
  const cell = v => '"' + String(v).replace(/"/g, '""') + '"';
  const body = FORMAT === 'txt'
    ? videos.map(v => v.url).join('\n')
    : ['Posición,Título,Canal,Duración,URL', ...videos.map(v =>
        [v.index, v.title, v.channel, v.duration, v.url].map(cell).join(','))].join('\n');
  const name = (document.title.replace(/ - YouTube$/, '') || 'playlist').replace(/[\\/:*?"<>|]+/g, '_');
  deliver(`Listo: ${many(videos.length, 'vídeo')}.`, body, `${name}.${FORMAT}`);
  return videos.length;
})();

Marcador: arrastra el botón del medio a tu barra de marcadores, abre www.youtube.com y pulsa el marcador. Ejecuta el mismo script con las opciones de arriba.

Por qué es seguro ejecutarloComprobación automática superada 23 oct 2026 · probado en una cuenta real el 22 oct 2026
  • No envía nada a ningún sitio. Sin fetch, XHR ni scripts externos. La única actividad de red es la de www.youtube.com cargando tu lista.
  • No toca tu sesión. No lee cookies, contraseñas, tokens ni el almacenamiento del navegador.
  • Solo funciona en www.youtube.com. En cualquier otra web se detiene antes de hacer nada.
  • Corto y legible. 122 líneas comentadas, sin minificar ni ofuscar.
  • Muestra su progreso en la página. Un pequeño recuadro te dice qué está haciendo y al final te ofrece Descargar o Copiar.
  • El mismo código para todos. SHA-256 de la versión por defecto: 1e5e2a5a3ea93edf…
Compruébalo tú mismo en 30 segundos
  1. Léelo: todas las líneas están arriba, con comentarios.
  2. Busca en el código fetch, XMLHttpRequest, cookie, localStorage o eval. No aparece ninguna.
  3. Abre la pestaña Red (Network) antes de ejecutarlo: solo verás peticiones a www.youtube.com.
¿Te ha funcionado?

Cómo usarlo

  1. Abre la lista en YouTube, por ejemplo desde tus listas.
  2. Abre la consola: F12 o Ctrl+Mayús+J (Cmd+Opción+J en Mac). Si Chrome te lo pide, escribe permitir pegar (o allow pasting si lo tienes en inglés) y pulsa Enter.
  3. Elige las opciones, copia el script, pégalo en la consola y pulsa Enter. Al terminar aparece un recuadro: pulsa Descargar (o Copiar).

Listas largas

YouTube carga las listas por partes. El script se desplaza hasta el final antes de leer, así que exporta listas enteras: lo probamos con una de 2.785 vídeos.

Preguntas frecuentes

¿Funciona con listas largas?

Sí. Se desplaza hasta cargar la lista entera. Lo probamos con una lista de 2.785 vídeos.

¿Por qué faltan algunos vídeos?

YouTube oculta los vídeos borrados y privados («los vídeos no disponibles están ocultos»), así que no se pueden exportar.

Más herramientas gratis

Limpiar

Exportar y hacer copia