Saltar al contenido
Inicio 100 Días de Código en Python — Día 52: El bot que sigue cuentas (y la ética de "crecer" en redes)

100 Días de Código en Python — Día 52: El bot que sigue cuentas (y la ética de "crecer" en redes)

Nivel: Grado décimo Área: Tecnología e Informática Modalidad: Tutorial práctico Fuente: Curso "100 Days of Code: The Complete Python Pro Bootcamp" (Angela Yu, App Brewery). Contenido del Día 52 (nivel intermedio+).

Este tutorial adapta pedagógicamente el Día 52. Las secciones 📝 Ejercicio original del curso provienen de los archivos del curso (lecciones 379 a 384). Las soluciones oficiales (para Instagram y para el clon Share-a-Naan) están en los gists enlazados en cada paso. Los retos 🟢🟡🟠🔴, la sección sobre ética y datos personales, el proyecto extra y la etapa del proyecto integrador son creaciones de esta adaptación. Las correcciones al material original aparecen como 🛠️ Nota técnica.

Nota sobre las verificaciones: el 27 de septiembre de 2026, con Python 3.14, Selenium 4.49 y requests 2.34, se comprobó que la página de inicio de sesión de Share-a-Naan (app.100daysofpython.dev/services/share-a-naan/login) responde y tiene los campos id="username", id="password" y un botón type="submit" con el texto Log in. No se inició sesión en Instagram ni en Share-a-Naan, ni se siguió ninguna cuenta.

🏫 Para el aula: este proyecto se hace solo con Share-a-Naan, el clon de práctica del curso, usando la cuenta del docente. Seguir cuentas de forma automática en Instagram viola sus condiciones de uso, puede provocar el bloqueo de la cuenta y exige cumplir la edad mínima de la plataforma. Lo que se aprende hoy (clases, inicio de sesión, desplazamiento dentro de ventanas, manejo de excepciones) sirve para muchos otros bots legítimos.


Tabla de contenidos

  1. Antes de empezar: el truco del consultor y sus preguntas incómodas
  2. Paso 1: credenciales, ventanas emergentes y constantes
  3. Paso 2: organizar el bot en una clase
  4. Paso 3: iniciar sesión con selectores que no cambian
  5. Paso 4: desplazarse dentro de la ventana de seguidores
  6. Paso 5: seguir cuentas y manejar la excepción del clic tapado
  7. 🚀 Proyecto del día: el bot seguidor en Share-a-Naan
  8. 🌟 Proyecto extra: el auditor de la comunidad del club
  9. 🧩 Comprueba lo aprendido
  10. 🤖 Programar con apoyo de IA
  11. 🛠️ Correcciones técnicas al material original
  12. 📋 Matriz de trazabilidad
  13. 📦 Evidencias de aprendizaje del Día 52

0. Antes de empezar: el truco del consultor y sus preguntas incómodas

(Fuente: 379 - Day 52 Goals- what you will make by the end of the day.md)

📝 Contenido original del curso

La profesora cuenta que habló con un consultor de Instagram que asesora empresas. Uno de sus "secretos": si quieres construir una marca (por ejemplo, de cocina), busca una cuenta grande con un público parecido al tuyo (ella menciona ChefSteps), abre la lista de sus seguidores y síguelos uno por uno. Muchos son personas interesadas en el tema; al verse seguidas, algunas te devolverán el seguimiento.

Como nadie quiere hacer clic en 247 000 botones, el plan es automatizarlo con Selenium y Python: el bot inicia sesión, va a la cuenta objetivo, abre la lista de seguidores y empieza a seguir cuentas una por una.

Lo nuevo hoy: desplazarse dentro de una ventana emergente (no de la página), manejar una excepción específica de Selenium y organizar un bot de varios pasos en una clase.

Prerequisitos: clases y objetos (Días 16 a 20), manejo de excepciones con try/except (Día 30), Selenium con esperas y XPath (Días 48 a 51).

📖 Preguntas incómodas (adaptación)

Antes de programar, un buen ingeniero pregunta si debe construir algo, no solo si puede. Discute con tu grupo:

Pregunta Para pensar
¿Qué obtienes? Seguidores que te siguieron "de vuelta" por cortesía, no por interés en tu contenido. ¿Eso es una comunidad?
¿Qué recibe la otra persona? Una notificación de alguien que no la conoce y que probablemente la dejará de seguir pronto.
¿Qué dice la plataforma? Las condiciones de Instagram prohíben acceder o actuar mediante medios automatizados no autorizados. Las cuentas que lo hacen se limitan o se cierran.
¿Qué datos tocas? Nombres de usuario de personas reales. En Colombia, la Ley 1581 de 2012 (protección de datos personales) exige una finalidad legítima para recolectarlos y tratarlos.

💡 El mismo esqueleto técnico (iniciar sesión, recorrer una lista, hacer una acción por elemento, manejar ventanas que estorban) sirve para bots útiles: revisar una lista de tareas en la plataforma del colegio, descargar tus propios certificados, probar el sitio web del grupo. Por eso lo estudiamos en un clon.


1. Paso 1: credenciales, ventanas emergentes y constantes

(Fuente: 380-step-1-get-your-instagram-or-share-a-naan-credentials.md)

📝 Ejercicio original del curso: paso 1

  1. Cuenta: en Instagram, o en Share-a-Naan, el servicio de práctica del curso (app.100daysofpython.dev). Share-a-Naan permite "concentrarse en la lógica del bot sin preocuparse de que Facebook cambie los XPath o deshabilite la cuenta por comportamiento de bot".
    • 1a. Con Share-a-Naan: anota las credenciales que aparecen en el panel del servicio (/services/share-a-naan/admin/profile). No hace falta registrarse aparte.
    • 1b. Con Instagram: crea una cuenta nueva (no la personal), verifica el correo, publica una o dos fotos, sigue algunas cuentas a mano y pon una foto de perfil.
  2. Anota las ventanas emergentes o avisos de cookies que aparezcan y copia el XPath de sus botones.
  3. Elige la cuenta objetivo: una cuenta grande con muchos seguidores.
  4. Define estas constantes:
Python
SIMILAR_ACCOUNT = "chefsteps"   # la cuenta cuyos seguidores vas a seguir
USERNAME = "your_email"         # usuario de Share-a-Naan (o Instagram): tu correo
PASSWORD = "your_pw"
BASE_URL = "https://app.100daysofpython.dev/services/share-a-naan"   # si usas el clon
LOGIN_URL = f"{BASE_URL}/login"

🛠️ Nota técnica: como en los Días 35, 47 y 51, el usuario y la contraseña no se escriben en el código: van en un archivo .env que se lee con os.environ (o python-dotenv) y que nunca se sube a GitHub. SIMILAR_ACCOUNT, BASE_URL y LOGIN_URL no son secretos y pueden quedarse como constantes.

Python
import os
from dotenv import load_dotenv

load_dotenv()
USERNAME = os.environ["NAAN_USERNAME"]
PASSWORD = os.environ["NAAN_PASSWORD"]

🔍 Antes de programar

  • ¿Por qué el curso recomienda una cuenta nueva y no la personal? ¿Qué podrías perder?
  • Abre Share-a-Naan a mano. Cuenta cuántas ventanas o avisos aparecen antes de ver el contenido. Cada uno es un paso que el bot deberá resolver.
  • ¿Qué constante tendrías que cambiar para usar el bot con otra cuenta objetivo? Si la respuesta es "solo una", el diseño va bien.

2. Paso 2: organizar el bot en una clase

(Fuente: 381-step-2-create-a-class.md)

🎯 Objetivo de aprendizaje

Estructurar un bot de varios pasos como una clase con un atributo compartido (driver) y un método por paso.

📝 Ejercicio original del curso: paso 2

  1. Crear la clase InstaFollower.
  2. En __init__, crear el driver de Selenium.
  3. Crear tres métodos: login(), find_followers() y follow().
  4. Fuera de la clase, crear el objeto y llamar los tres métodos en orden.

📖 Explicación conceptual

Python
from selenium import webdriver


class InstaFollower:
    def __init__(self):
        opciones = webdriver.ChromeOptions()
        opciones.add_experimental_option("detach", True)   # el navegador no se cierra al terminar (Día 48)
        self.driver = webdriver.Chrome(options=opciones)

    def login(self):
        ...

    def find_followers(self):
        ...

    def follow(self):
        ...


bot = InstaFollower()
bot.login()
bot.find_followers()
bot.follow()

Es el mismo patrón del Día 51: el driver es el estado que comparten los métodos, y cada método es un paso del recorrido. Leer las últimas cuatro líneas es como leer el plan del bot en español: "inicia sesión, busca los seguidores, síguelos".

⚠️ Errores frecuentes

Error Qué ocurre Solución
driver = webdriver.Chrome() sin self. en __init__ Los demás métodos lanzan AttributeError: 'InstaFollower' object has no attribute 'driver' Guarda el navegador en self.driver
Escribir bot = InstaFollower (sin paréntesis) bot es la clase, no un objeto; bot.login() pide el argumento self Crea el objeto: InstaFollower()
Llamar follow() antes de find_followers() No hay ventana de seguidores abierta; no encuentra botones Respeta el orden o haz que follow() compruebe que la ventana existe

Retos adicionales (creados para esta adaptación)

  • 🟢 Reto 1 — Aplicación: agrega un método cerrar() que haga self.driver.quit().
  • 🟡 Reto 2 — Ampliación: recibe cuenta_objetivo y limite (máximo de cuentas a seguir) como parámetros de __init__.
  • 🟠 Reto 3 — Desafío: agrega un atributo self.seguidas = [] y un método resumen() que imprima cuántas cuentas se siguieron, cuántas ya estaban seguidas y cuántos errores hubo.
  • 🔴 Reto 4 — Pensamiento computacional: dibuja un diagrama de clase (nombre, atributos, métodos) para un bot que, en lugar de seguir, solo cuente cuántos seguidores en común tienen dos cuentas. ¿Qué métodos del curso reutilizas y cuáles cambian?

3. Paso 3: iniciar sesión con selectores que no cambian

(Fuente: 382-step-3-login-to-share-a-naan-or-instagram.md)

🎯 Objetivo de aprendizaje

Iniciar sesión con Selenium y cerrar ventanas emergentes usando XPath con contains() y text(), que no dependen de clases generadas automáticamente.

📝 Ejercicio original del curso: paso 3

  1. Habrá ventanas emergentes y avisos antes de ver el contenido. Si aparece un aviso de cookies, ciérralo.
  2. Inicia sesión automáticamente en el método login(). Con Share-a-Naan usa BASE_URL y LOGIN_URL; con Instagram, https://www.instagram.com/accounts/login/.
  3. Cierra las ventanas que aparezcan (guardar la información de inicio de sesión, activar notificaciones).

El curso advierte que en Instagram los XPath y los selectores cambian cada vez que se carga la página, porque se generan dinámicamente. La pista: usar contains() y text() en el XPath:

Python
button = driver.find_element(by=By.XPATH, value="//button[contains(text(), 'Click me')]")

Advertencia del curso: ejecuta el programa pocas veces, cierra sesión a mano mientras pruebas y no lo ejecutes muchas veces seguidas (por ejemplo, cinco), porque puedes activar la detección de bots y perder la cuenta.

📖 Explicación conceptual

Por qué contains(text(), ...) es más estable. Una clase como x1lliihq x193iq5w la inventa una herramienta al compilar el sitio y puede cambiar mañana. El texto "Not now" o "Log in" lo escribió una persona para otra persona y cambia mucho menos. Es la misma lección del Día 51 con speedtest.net.

Selector Estabilidad Cuándo usarlo
By.ID, "username" Alta, si el id es legible Siempre que exista un id con sentido
By.NAME, "password" Alta Campos de formulario
By.XPATH, "//button[contains(text(), 'Log in')]" Media-alta Botones sin id, identificados por su texto
By.CLASS_NAME, "x1lliihq" Muy baja Casi nunca: parece aleatoria

En Share-a-Naan la página de inicio de sesión (verificada) tiene id="username" e id="password":

Python
import time
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

    def login(self):
        self.driver.get(LOGIN_URL)
        espera = WebDriverWait(self.driver, 10)

        campo_usuario = espera.until(EC.presence_of_element_located((By.ID, "username")))
        campo_usuario.send_keys(USERNAME)
        campo_clave = self.driver.find_element(By.ID, "password")
        campo_clave.send_keys(PASSWORD, Keys.ENTER)

Ventanas opcionales. Una ventana "¿Guardar tu información?" a veces aparece y a veces no. Si la buscas con find_element y no está, el programa se detiene. Solución: intentar y, si no aparece, seguir:

Python
from selenium.common.exceptions import TimeoutException

    def cerrar_si_aparece(self, texto_boton, segundos=3):
        """Hace clic en un botón con ese texto si aparece; si no, no pasa nada."""
        try:
            boton = WebDriverWait(self.driver, segundos).until(
                EC.element_to_be_clickable((By.XPATH, f"//button[contains(text(), '{texto_boton}')]"))
            )
            boton.click()
        except TimeoutException:
            pass

Así, login() termina con self.cerrar_si_aparece("Not now") sin romperse cuando la ventana no sale.

⚠️ Errores frecuentes

Error Causa Solución
NoSuchElementException al buscar el campo de usuario La página aún no ha cargado WebDriverWait con presence_of_element_located
NoSuchElementException con un XPath copiado del navegador El XPath "completo" (/html/body/div[2]/...) depende de la posición exacta Escribe un XPath por texto o por atributo
contains(text(), 'Not Now') no encuentra nada Mayúsculas diferentes ("Not now") o el texto está en un <span> dentro del botón Revisa el HTML real; prueba //button[.//*[contains(text(), 'Not now')]]
La cuenta de Instagram pide verificación Muchos inicios de sesión seguidos Usa Share-a-Naan; en Instagram, pocas ejecuciones y espaciadas

Retos adicionales (creados para esta adaptación)

  • 🟢 Reto 1: escribe el XPath para un botón cuyo texto es exactamente "Aceptar cookies" y otro para cualquier botón que contenga "cookies".
  • 🟡 Reto 2: después de iniciar sesión, verifica que el inicio fue exitoso (por ejemplo, que la URL ya no termine en /login) e imprime un mensaje claro si falló.
  • 🟠 Reto 3: haz que cerrar_si_aparece reciba una lista de textos posibles ("Not now", "Ahora no", "Cancel") y pulse el primero que encuentre.
  • 🔴 Reto 4: en una tabla, compara tres estrategias para no iniciar sesión cada vez: guardar el perfil de Chrome (Día 49), guardar cookies en un archivo, o no cerrar el navegador. Para cada una: ventaja, riesgo de seguridad y cuándo la usarías.

4. Paso 4: desplazarse dentro de la ventana de seguidores

(Fuente: 383-step-4-find-the-followers-of-the-target-account.md)

🎯 Objetivo de aprendizaje

Ejecutar JavaScript desde Selenium para desplazar un elemento con barra de desplazamiento propia.

📝 Ejercicio original del curso: paso 4

La URL de una cuenta es el nombre de la cuenta al final del dominio (por ejemplo, https://www.instagram.com/chefsteps/). Al hacer clic en el número de seguidores se abre una ventana emergente que carga unos 15 seguidores; para ver más hay que desplazarse dentro de esa ventana, no en la página principal.

  1. Investiga en Google o Stack Overflow cómo desplazarte en la ventana de seguidores y escribe el código en find_followers().

El curso insiste: "Como desarrollador, una de las cosas más comunes es averiguar cómo hacer algo que nunca has hecho. Es una habilidad clave". Las pistas enlazan una pregunta de Stack Overflow ("selenium scroll inside of popup div") y la documentación de MDN de scrollTop y scrollHeight.

📖 Explicación conceptual

Un elemento con desplazamiento propio tiene dos propiedades (documentadas en MDN):

Propiedad Significado
scrollHeight Altura total del contenido, incluida la parte que no se ve
scrollTop Cuántos píxeles se ha desplazado ya hacia abajo

Si pones scrollTop = scrollHeight, el elemento baja hasta el final. Selenium no tiene un método para eso, pero puede ejecutar JavaScript en la página con execute_script. Lo que pasas después del código llega a JavaScript como arguments[0], arguments[1], etc.:

Python
    def find_followers(self):
        self.driver.get(f"{BASE_URL}/{SIMILAR_ACCOUNT}")   # la ruta exacta depende del sitio: compruébala a mano
        # ... hacer clic en el enlace de seguidores ...

        ventana = WebDriverWait(self.driver, 10).until(
            EC.presence_of_element_located((By.XPATH, "SELECTOR_DE_LA_VENTANA"))   # búscalo con Inspeccionar
        )
        for _ in range(5):
            self.driver.execute_script("arguments[0].scrollTop = arguments[0].scrollHeight", ventana)
            time.sleep(2)   # dar tiempo a que carguen los siguientes seguidores

¿Por qué un ciclo? Cada vez que llegas al final, el sitio carga más seguidores y scrollHeight crece. Repetir el desplazamiento carga un "lote" por vuelta.

🛠️ Nota técnica: un for con número fijo de vueltas funciona, pero no sabe cuándo ya no hay más. Una versión mejor compara scrollHeight antes y después: si no cambió, se llegó al final y el ciclo se detiene (Reto 2).

⚠️ Errores frecuentes

Error Causa Solución
Se desplaza la página, no la ventana Se usó window.scrollTo o Keys.END sobre el body Desplaza el elemento que tiene la barra
No pasa nada al desplazar El elemento elegido no es el que tiene overflow: auto (suele ser un div interior) En Inspeccionar, busca el div cuyo scrollHeight sea mayor que su clientHeight
JavascriptException: Cannot set properties of null Se pasó None o el elemento aún no existía Espera al elemento antes de ejecutar el script

Retos adicionales (creados para esta adaptación)

  • 🟢 Reto 1: imprime scrollTop y scrollHeight antes y después de cada desplazamiento (execute_script("return arguments[0].scrollHeight", ventana)).
  • 🟡 Reto 2: reemplaza el for por un while que se detenga cuando scrollHeight deje de crecer.
  • 🟠 Reto 3: detente también cuando ya haya al menos limite botones cargados en la ventana.
  • 🔴 Reto 4: crea una página HTML propia con un div de 300 px de alto que contenga 200 elementos, ábrela con Selenium y prueba tu función de desplazamiento sin depender de ningún sitio externo. ¿Cómo sabes que funcionó?

5. Paso 5: seguir cuentas y manejar la excepción del clic tapado

(Fuente: 384-step-5-follow-all-the-followers.md)

🎯 Objetivo de aprendizaje

Recorrer una lista de elementos, actuar sobre cada uno con pausas y recuperarse de una excepción concreta sin detener el programa.

📝 Ejercicio original del curso: paso 5

  1. En follow(), encuentra todos los botones "Seguir" de la ventana y haz clic en cada uno, con 1 segundo de pausa entre clics "para parecer más humano".
  2. Si ya seguías a una cuenta, al hacer clic aparece una ventana que pregunta si quieres dejar de seguirla. Esa ventana tapa los demás botones, y el siguiente clic lanza ElementClickInterceptedException. Maneja la excepción: cuando ocurra, pulsa "Cancel" para cerrar la ventana y continúa.

El curso también avisa: "Instagram actualizará su sitio, así que los selectores CSS y los XPath pueden cambiar".

📖 Explicación conceptual

Python
from selenium.common.exceptions import ElementClickInterceptedException

    def follow(self):
        botones = self.driver.find_elements(By.XPATH, "//button[contains(text(), 'Follow')]")
        for boton in botones:
            try:
                boton.click()
                time.sleep(1.1)
            except ElementClickInterceptedException:
                cancelar = self.driver.find_element(By.XPATH, "//button[contains(text(), 'Cancel')]")
                cancelar.click()

Qué hace cada parte:

  • find_elements (en plural) devuelve una lista; si no hay ninguno, la lista está vacía (no lanza error).
  • El try/except atrapa solo ElementClickInterceptedException: cualquier otro error sigue siendo visible, lo cual es bueno para depurar.
  • Nombrar la excepción exacta es mejor que un except: vacío, que escondería errores que no esperabas.

Una sutileza: si el clic falla porque la ventana de "dejar de seguir" tapaba el botón, ese botón no se pulsó. El código del curso simplemente pasa al siguiente. ¿Es correcto? Depende: si la ventana apareció porque el anterior ya estaba seguido, el actual se queda sin seguir. En el Reto 3 lo resuelves.

🛠️ Nota técnica: contains(text(), 'Follow') también encuentra botones que digan "Following" (ya seguido) porque "Following" contiene "Follow". Pulsar "Following" abre justamente la ventana de dejar de seguir. Un selector más preciso es //button[normalize-space(text())='Follow'] (texto exacto).

⚠️ Errores frecuentes

Error Causa Solución
ElementClickInterceptedException sin manejar Una ventana tapa el botón try/except que la cierre
StaleElementReferenceException La lista de botones se volvió a dibujar tras un clic Vuelve a buscar los botones, o búscalos uno por uno
Sigue y deja de seguir sin querer El selector incluye "Following" Texto exacto con normalize-space()
except: vacío Oculta errores reales (por ejemplo, un NameError) Nombra la excepción que esperas

Retos adicionales (creados para esta adaptación)

  • 🟢 Reto 1: cuenta en variables cuántos clics fueron exitosos y cuántas veces se atrapó la excepción; imprímelo al final.
  • 🟡 Reto 2: respeta el limite del Reto 2 de la sección 2: detente al llegar a ese número de cuentas seguidas.
  • 🟠 Reto 3: cuando atrapes la excepción, después de pulsar "Cancel", reintenta el clic en el botón actual una vez.
  • 🔴 Reto 4: agrega un modo simulación (Día 50): con MODO_PRUEBA = True, el bot recorre los botones y escribe en un archivo lo que haría, sin pulsar nada. ¿Por qué es la primera versión que deberías ejecutar siempre?

6. 🚀 Proyecto del día: el bot seguidor en Share-a-Naan

(Integra los pasos 1 a 5 del curso: 380 a 384.)

Problema

Quieres automatizar una tarea repetitiva en una red social (iniciar sesión, abrir una lista larga y hacer una acción por elemento) sin perder el control de lo que hace el bot.

Contexto

Share-a-Naan es un clon de práctica creado por el curso: puedes cometer errores sin afectar a personas reales ni arriesgar una cuenta.

Objetivo

Construir la clase InstaFollower que, en Share-a-Naan, inicie sesión, abra la lista de seguidores de SIMILAR_ACCOUNT, cargue más seguidores desplazándose y siga hasta limite cuentas.

Requisitos funcionales

  1. Clase con __init__, login(), find_followers() y follow() (estructura del curso).
  2. Inicio de sesión con credenciales desde .env.
  3. Cierre de ventanas opcionales sin romper el programa.
  4. Desplazamiento dentro de la ventana de seguidores hasta tener suficientes botones.
  5. Clics con pausa de al menos 1 segundo y manejo de ElementClickInterceptedException.
  6. Resumen final: seguidas, ya seguidas, errores.

Restricciones técnicas

  • Solo Share-a-Naan.
  • Nada de except: vacíos.
  • Esperas con WebDriverWait para cargar elementos; sleep solo para las pausas entre acciones.
  • Un limite pequeño (por ejemplo, 10) mientras pruebas.

Planificación

  1. Comprender el recorrido haciéndolo a mano y anotando cada clic.
  2. Entradas: credenciales, cuenta objetivo, límite.
  3. Procesos: iniciar sesión → cerrar ventanas → abrir perfil → abrir seguidores → desplazar → seguir.
  4. Salidas: cuentas seguidas y un resumen.
  5. Dividir en métodos (uno por proceso).
  6. Diseñar: escribe los XPath que usarás antes de programar, y pruébalos en la consola de Chrome con $x("tu_xpath").
  7. Implementar. 8. Probar. 9. Corregir. 10. Mejorar.

Construcción paso a paso

  • Versión 1: la clase con los métodos vacíos que solo imprimen "iniciando sesión…", "buscando seguidores…", "siguiendo…".
  • Versión 2: login() real y verificación de que funcionó.
  • Versión 3: find_followers() que abre la ventana y se desplaza.
  • Versión 4: follow() en modo simulación (solo registra).
  • Versión final: follow() real con límite, excepción manejada y resumen.

Pruebas

Caso Situación Resultado esperado
Contraseña incorrecta en .env El inicio de sesión falla Mensaje claro y el bot se detiene sin intentar seguir
Falta una variable en .env NAAN_PASSWORD no existe Mensaje claro, no un KeyError sin explicación
Ventana "Not now" no aparece Carga distinta El bot continúa
Una cuenta ya estaba seguida Aparece "dejar de seguir" Pulsa "Cancel" y sigue con las demás
limite = 3 — Exactamente 3 cuentas nuevas seguidas
Modo simulación — Ningún clic real; archivo con lo que haría

🧪 Prueba y depuración

Python
class InstaFollower:
    def __init__(self):
        self.driver = webdriver.Chrome()

    def follow(self):
        botones = self.driver.find_element(By.XPATH, "//button[contains(text(), 'Follow')]")
        for boton in botones:
            try:
                boton.click()
            except:
                cancelar = self.driver.find_element(By.XPATH, "//button[contains(text(), 'Cancel')]")
                cancelar.click()

Encuentra cuatro problemas: find_element en singular (devuelve un elemento, no una lista, y el for falla), except: vacío, falta la pausa entre clics y el selector también atrapa botones "Following".

⭐ Extensión del proyecto

  • Guardar en un CSV cada cuenta seguida con la fecha, para no volver a intentarla en la siguiente ejecución.
  • Un método dejar_de_seguir_antiguas(dias) que revierta lo hecho (útil para "limpiar" la cuenta de práctica).
  • Medir cuánto tarda cada fase con un decorador (lo aprenderás el Día 54).

7. 🌟 Proyecto extra: el auditor de la comunidad del club

(Proyecto creado para esta adaptación; no existe en el curso original.)

Situación

El club de robótica del colegio tiene una cuenta de práctica en Share-a-Naan. Antes de pensar en "crecer", la coordinadora quiere entender su comunidad: ¿cuántos seguidores tiene?, ¿a cuántas cuentas sigue?, ¿cuántas de ellas no lo siguen de vuelta?

Requisitos

  1. Un bot que no sigue a nadie: solo lee. Inicia sesión, abre las listas de seguidores y de seguidos, se desplaza hasta el final y guarda los nombres de usuario.
  2. Con conjuntos de Python (Día 49), calcula: seguidores, seguidos, mutuos (intersección), "no me siguen de vuelta" (diferencia) y "no sigo de vuelta".
  3. Un informe en texto con los números y, como máximo, los 10 primeros nombres de cada grupo.
  4. Las listas se guardan solo en el computador y se borran al terminar la actividad.

Decisiones que debes tomar (y justificar)

  • ¿Qué harías con la lista de "no me siguen de vuelta"? ¿Es ético publicarla?
  • ¿Cómo detectas que la lista terminó de cargar (Reto 2 de la sección 4)?
  • ¿Qué harías si el sitio limita cuántos seguidores muestra?

Casos de prueba

  • Seguidores {"ana", "beto", "caro"} y seguidos {"beto", "caro", "dani"}: mutuos {"beto", "caro"}, no me siguen {"dani"}, no sigo {"ana"}.
  • Lista vacía de seguidos: el informe lo dice sin errores de división.

8. 🧩 Comprueba lo aprendido

Conceptuales

  1. ¿Por qué contains(text(), 'Log in') suele ser más estable que una clase como x1lliihq?
  2. ¿Qué diferencia hay entre desplazar la página y desplazar un elemento?
  3. ¿Qué significan scrollTop y scrollHeight?
  4. ¿Por qué es mejor except ElementClickInterceptedException: que except:?
  5. Nombra dos razones (una técnica y una ética) para usar Share-a-Naan en lugar de Instagram.

Análisis de código

Python
seguidores = {"ana", "beto", "caro", "dani"}
seguidos = {"caro", "dani", "eva"}
print(len(seguidores & seguidos), seguidos - seguidores, sorted(seguidores - seguidos))

¿Qué imprime? Explica cada operación.

Predicción

Python
botones = driver.find_elements(By.XPATH, "//button[contains(text(), 'Follow')]")

Si la ventana tiene 4 botones "Follow" y 2 botones "Following", ¿cuántos elementos tiene botones? ¿Y con normalize-space(text())='Follow'?

Depuración

Python
def find_followers(self):
    ventana = self.driver.find_element(By.XPATH, "//div[@role='dialog']")
    self.driver.execute_script("arguments[0].scrollTop = arguments[0].scrollHeight")

¿Por qué no se desplaza nada? (Pista: ¿a quién se refiere arguments[0]?)

Modificación

Cambia follow() para que la pausa entre clics sea aleatoria entre 1 y 3 segundos. ¿Qué módulo usas?

Creación

Escribe una función esperar_boton(driver, texto, segundos) que devuelva el botón si aparece y None si no, sin lanzar excepciones.


9. 🤖 Programar con apoyo de IA

Ciclo de trabajo: formula el problema → inténtalo tú → consulta a la IA → lee y entiende → prueba → compara con la documentación oficial → modifica → explica con tus palabras.

Actividad 1 — El XPath de la IA. Pide a una IA "el XPath del botón Seguir de Instagram". Compáralo con el HTML real de Share-a-Naan (Inspeccionar). ¿Usa clases aleatorias? ¿Incluiría "Following"? Corrígelo.

Actividad 2 — Verificar una explicación. Pregunta a una IA qué hacen scrollTop y scrollHeight. Compara su respuesta con las páginas de MDN que enlaza el curso. Anota una diferencia o un detalle que la IA omitió.

Actividad 3 — La pregunta ética. Pide a una IA un bot para "ganar 10 000 seguidores reales en una semana". Analiza la respuesta: ¿advierte sobre las condiciones de uso?, ¿propone algo que viole la ley de datos personales? Escribe tu propia conclusión en un párrafo: tú eres responsable de lo que ejecutas.


10. 🛠️ Correcciones técnicas al material original

Archivo Afirmación o comportamiento del curso Observación Tratamiento
380 USERNAME y PASSWORD como constantes en el código Deben ir en .env (Días 35, 47 y 51) Nota técnica de la sección 1
381 "En el método init()" El constructor de Python es __init__ Sección 2
384 Seleccionar botones con contains(text(), 'Follow') (sugerido por la pista del paso 3) También coincide con "Following", lo que provoca la ventana de dejar de seguir Nota técnica de la sección 5
384 Al atrapar la excepción, se cancela y se pasa al siguiente botón El botón actual queda sin pulsar Explicación y Reto 3 de la sección 5
383 Desplazar un número fijo de veces No detecta el final de la lista Nota técnica y Reto 2 de la sección 4
379 Seguir masivamente a los seguidores de otra cuenta como estrategia de crecimiento Viola las condiciones de uso de Instagram y trata datos personales (Ley 1581 de 2012 en Colombia) Sección 0 y aviso para el aula; proyecto solo en Share-a-Naan

11. 📋 Matriz de trazabilidad

Contenido original Archivo de origen Concepto desarrollado Ejercicio original Reto adicional Proyecto relacionado
Consultor de Instagram, estrategia de seguir a los seguidores de ChefSteps, automatización con Selenium 379 Sección 0 — Preguntas incómodas (ética, datos personales) Secciones 6 y 7
Instagram o Share-a-Naan, credenciales del panel, cuenta nueva, ventanas emergentes, cuenta objetivo, constantes 380 Sección 1 Paso 1 .env Sección 6
Clase InstaFollower, driver en el constructor, métodos login(), find_followers(), follow() 381 Sección 2 Paso 2 Retos 1–4 de la sección 2 Secciones 6 y 7
Inicio de sesión, ventanas de cookies y notificaciones, selectores dinámicos, contains() y text(), ejecutar pocas veces 382 Sección 3 Paso 3 Retos 1–4 de la sección 3 Sección 6
URL de la cuenta, ventana con ~15 seguidores, desplazamiento dentro de la ventana, investigar en Stack Overflow y MDN (scrollTop, scrollHeight) 383 Sección 4 Paso 4 Retos 1–4 de la sección 4 Secciones 6 y 7
Seguir con pausa de 1 s, ventana de dejar de seguir, ElementClickInterceptedException, botón Cancel, cambios futuros en los selectores 384 Sección 5 Paso 5 Retos 1–4 de la sección 5 Sección 6
(No existe en el curso original) — — — Auditor de la comunidad del club Sección 7

12. 📦 Evidencias de aprendizaje del Día 52

  • La clase InstaFollower funcionando en Share-a-Naan (o, como mínimo, en modo simulación) con sus cuatro métodos.
  • Una tabla con los XPath usados, por qué se eligieron y la prueba de cada uno con $x() en la consola de Chrome.
  • La captura del resumen final (seguidas, ya seguidas, errores).
  • El informe del auditor de la comunidad (proyecto extra) con las operaciones de conjuntos.
  • Un párrafo de reflexión: ¿construirías este bot para una cuenta real? ¿Por qué?
  • Cuaderno de errores: driver sin self., clase sin instanciar, find_element contra find_elements, except: vacío, "Follow" contra "Following", desplazar la página en vez de la ventana, arguments[0] sin pasar el elemento.