Ver quién no te sigue en X (Twitter), gratis

Consigue la lista de cuentas que sigues en X y no te siguen, en una hoja de cálculo para revisarla. Gratis, sin registro, sin conectar ninguna app y sin dejar de seguir a nadie.

Inicia sesión en x.com, abre tu perfil, pulsa Siguiendo y ejecuta el script. Solo lee la lista: no deja de seguir a nadie.

export-followers.js · 145 líneaspégalo en la consola (F12) de x.com
// Responsive Muse · X (Twitter) Followers & Following Exporter · v1.0 · 2026-10-31
// Saves the followers or the following list of any X account you can open (yours or a public
// one) to CSV or TXT: name, @handle, user ID, bio, verified, whether they follow you and the
// profile link. On your own Following list it can keep only the accounts that don't follow
// you back. X only keeps on screen the people you can see, so the script collects them while
// it scrolls. It makes no network requests of its own and never reads cookies, passwords or tokens.
(async () => {
  const TOOL = 'Exportar seguidores de X';
  const FORMAT = 'csv';        // 'csv' (Excel, Sheets) or 'txt' (one @handle per line)
  const ONLY = 'all';          // 'all' or 'not-following-back' (only accounts that don't follow you)
  const LIMIT = 2000;          // stop after this many accounts (0 = no limit; big accounts take a while)

  const page = location.pathname.match(/^\/([A-Za-z0-9_]{1,15})\/(followers|following|verified_followers)\/?$/);
  if (!/^(x|twitter)\.com$/.test(location.hostname) || !page) {
    alert('Abre primero una lista de seguidores o de seguidos en X (con sesión iniciada), por ejemplo:\nhttps://x.com/YourName/following');
    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 = 'display:block!important;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));
  const people = new Map();
  let total = 0;               // accounts seen, before the "not following back" filter

  // Read the accounts currently on screen
  const collect = () => {
    for (const cell of document.querySelectorAll('[data-testid="UserCell"]')) {
      // Skip the "Who to follow" suggestions in the sidebar
      if (cell.closest('aside') || !cell.closest('[data-testid="primaryColumn"]')) continue;
      // Profile links are plain "/handle" (links in the bio carry ?src= or point to /status)
      const prof = [...cell.querySelectorAll('a[href^="/"]')].find(a => /^\/[A-Za-z0-9_]{1,15}$/.test(a.getAttribute('href')) && a.textContent.trim());
      if (!prof) continue;
      const handle = prof.getAttribute('href').slice(1);
      if (people.has(handle)) continue;
      // The Follow / Following button carries the numeric user ID: "<id>-follow" or "<id>-unfollow"
      const btn = cell.querySelector('[data-testid$="-follow"], [data-testid$="-unfollow"]');
      // Bio: the visible text block outside the links (hidden ones with an id are screen-reader labels)
      const bio = [...cell.querySelectorAll('div[dir="auto"]')].find(d => !d.closest('a') && !d.id);
      const followsYou = !!cell.querySelector('[data-testid="userFollowIndicator"]');
      total++;
      people.set(handle, {
        name: prof.textContent.trim(),
        handle: '@' + handle,
        id: (btn?.dataset.testid.match(/^(\d+)-/) || [])[1] || '',
        bio: (bio?.innerText || '').replace(/\s+/g, ' ').trim(),
        verified: cell.querySelector('[data-testid="icon-verified"]') ? 'sí' : '',
        followsYou: followsYou ? 'sí' : '',
        youFollow: btn && /-unfollow$/.test(btn.dataset.testid) ? 'sí' : '',
        url: 'https://x.com/' + handle,
      });
    }
  };

  // 1. Scroll down step by step, collecting as we go. X loads the next accounts only when you reach
  //    the bottom and can take a few seconds, so we stop after ~10 s at the bottom with nothing new.
  let stop = false;
  panel.stoppable(() => { stop = true; });
  window.scrollTo(0, 0);
  await sleep(1000);
  let idle = 0;
  while (idle < 8 && !stop && !(LIMIT && people.size >= LIMIT)) {
    const before = people.size;
    collect();
    const atBottom = scrollY + innerHeight >= document.scrollingElement.scrollHeight - 50;
    window.scrollBy(0, innerHeight * 0.8);
    await sleep(1200);
    idle = people.size !== before ? 0 : atBottom ? idle + 1 : idle;
    panel.text(`Recogiendo cuentas… ${people.size}${LIMIT ? ' de un máximo de ' + LIMIT : ''}`);
  }
  let list = [...people.values()];
  if (LIMIT) list = list.slice(0, LIMIT);
  if (!list.length) {
    panel.finish('No se han encontrado cuentas. ¿La lista está vacía o es privada? Si no, puede que X haya cambiado su diseño: avísanos en responsive-muse.com.');
    return 0;
  }
  if (ONLY === 'not-following-back') list = list.filter(p => !p.followsYou);

  // 2. Build the file and offer it
  const cell = v => '"' + String(v).replace(/"/g, '""') + '"';
  const body = FORMAT === 'txt'
    ? list.map(p => p.handle).join('\n')
    : ['#,Nombre,Usuario,ID de usuario,Bio,Verificado,Te sigue,Le sigues,URL', ...list.map((p, i) =>
        [i + 1, p.name, p.handle, p.id, p.bio, p.verified, p.followsYou, p.youFollow, p.url].map(cell).join(','))].join('\n');
  const what = ONLY === 'not-following-back' ? `${many(list.length, 'cuenta')} que no te siguen (de ${total})` : many(list.length, 'cuenta');
  const file = `x-${page[1]}-${ONLY === 'not-following-back' ? 'not-following-back' : page[2]}.${FORMAT}`;
  deliver(`${stop ? 'Parado antes de terminar. ' : ''}Listo: ${what}.`, body, file);
  return list.length;
})();

🛡️ ¿Chrome te dice «No pegues código…»? Es normal. Escribe allow pasting en la consola (o permitir pegar si tu Chrome está en español), pulsa Intro y vuelve a pegar. Solo hace falta una vez.

¿Por qué avisa Chrome?

Chrome lo muestra la primera vez que alguien pega algo en la consola, para proteger de estafas en las que te piden pegar código desconocido. Hace bien en avisar, y por eso este script está entero a la vista: 145 líneas comentadas que no envían nada a ningún sitio ni tocan tus contraseñas o cookies. Por qué es seguro.

¿Prefieres no usar la consola? Arrastra el botón Quién no te sigue de vuelta a tu barra de marcadores y haz clic en él estando en x.com. Así no sale el aviso.

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

Por qué es seguro ejecutarloComprobación automática superada 26 oct 2026 · probado en una cuenta real el 31 oct 2026
  • No envía nada a ningún sitio. Sin fetch, XHR ni scripts externos. La única actividad de red es la de x.com cargando tu lista.
  • No toca tu sesión. No lee cookies, contraseñas, tokens ni el almacenamiento del navegador.
  • Solo funciona en x.com. En cualquier otra web se detiene antes de hacer nada.
  • Corto y legible. 145 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: 82279e9dde3023de…
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 x.com.
¿Te ha funcionado?

Cómo usarlo

  1. Inicia sesión en x.com, abre tu perfil y pulsa Siguiendo.
  2. Abre la consola: F12 o Ctrl+Mayús+J (Cmd+Opción+J en Mac). Si Chrome te lo pide, escribe permitir pegar y pulsa Enter.
  3. Copia el script, pégalo en la consola y pulsa Enter. Recorre todas las cuentas que sigues y se queda con las que no tienen la etiqueta «Te sigue».

Qué obtienes

Una lista con nombre, @usuario, ID, bio y enlace al perfil de cada cuenta que no te sigue, y cuántas cuentas ha revisado. Abre los enlaces y decide tú a quién dejar de seguir: tu cuenta no cambia si no lo haces tú.

Preguntas frecuentes

¿Cómo sabe quién me sigue?

En tu propia lista de Siguiendo, X muestra una pequeña etiqueta «Te sigue» junto a cada cuenta que te sigue. El script se queda con las que no la tienen.

¿Deja de seguir a alguien?

No. Solo hace la lista, para que decidas tú. No cambia nada de tu cuenta.

¿Es seguro para mi cuenta?

Solo desplaza la página que tienes abierta, como harías tú, y no envía nada a ningún sitio. No das tu contraseña ni conectas ninguna app.

Más herramientas para X

Limpiar

Exportar y hacer copia