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 camposid="username",id="password"y un botóntype="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
- Antes de empezar: el truco del consultor y sus preguntas incómodas
- Paso 1: credenciales, ventanas emergentes y constantes
- Paso 2: organizar el bot en una clase
- Paso 3: iniciar sesión con selectores que no cambian
- Paso 4: desplazarse dentro de la ventana de seguidores
- Paso 5: seguir cuentas y manejar la excepción del clic tapado
- 🚀 Proyecto del día: el bot seguidor en Share-a-Naan
- 🌟 Proyecto extra: el auditor de la comunidad del club
- 🧩 Comprueba lo aprendido
- 🤖 Programar con apoyo de IA
- 🛠️ Correcciones técnicas al material original
- 📋 Matriz de trazabilidad
- 📦 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
- 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.
- 1a. Con Share-a-Naan: anota las credenciales que aparecen en el panel del servicio (
- Anota las ventanas emergentes o avisos de cookies que aparezcan y copia el XPath de sus botones.
- Elige la cuenta objetivo: una cuenta grande con muchos seguidores.
- Define estas constantes:
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
.envque se lee conos.environ(opython-dotenv) y que nunca se sube a GitHub.SIMILAR_ACCOUNT,BASE_URLyLOGIN_URLno son secretos y pueden quedarse como constantes.
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
- Crear la clase
InstaFollower. - En
__init__, crear eldriverde Selenium. - Crear tres métodos:
login(),find_followers()yfollow(). - Fuera de la clase, crear el objeto y llamar los tres métodos en orden.
📖 Explicación conceptual
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 hagaself.driver.quit(). - 🟡 Reto 2 — Ampliación: recibe
cuenta_objetivoylimite(máximo de cuentas a seguir) como parámetros de__init__. - 🟠 Reto 3 — Desafío: agrega un atributo
self.seguidas = []y un métodoresumen()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
- Habrá ventanas emergentes y avisos antes de ver el contenido. Si aparece un aviso de cookies, ciérralo.
- Inicia sesión automáticamente en el método
login(). Con Share-a-Naan usaBASE_URLyLOGIN_URL; con Instagram,https://www.instagram.com/accounts/login/. - 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:
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":
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:
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:
passAsí, 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_aparecereciba 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.
- 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.:
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
forcon número fijo de vueltas funciona, pero no sabe cuándo ya no hay más. Una versión mejor comparascrollHeightantes 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
scrollTopyscrollHeightantes y después de cada desplazamiento (execute_script("return arguments[0].scrollHeight", ventana)). - 🟡 Reto 2: reemplaza el
forpor unwhileque se detenga cuandoscrollHeightdeje de crecer. - 🟠 Reto 3: detente también cuando ya haya al menos
limitebotones cargados en la ventana. - 🔴 Reto 4: crea una página HTML propia con un
divde 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
- 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". - 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
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/exceptatrapa soloElementClickInterceptedException: 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
limitedel 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
- Clase con
__init__,login(),find_followers()yfollow()(estructura del curso). - Inicio de sesión con credenciales desde
.env. - Cierre de ventanas opcionales sin romper el programa.
- Desplazamiento dentro de la ventana de seguidores hasta tener suficientes botones.
- Clics con pausa de al menos 1 segundo y manejo de
ElementClickInterceptedException. - Resumen final: seguidas, ya seguidas, errores.
Restricciones técnicas
- Solo Share-a-Naan.
- Nada de
except:vacíos. - Esperas con
WebDriverWaitpara cargar elementos;sleepsolo para las pausas entre acciones. - Un
limitepequeño (por ejemplo, 10) mientras pruebas.
Planificación
- Comprender el recorrido haciéndolo a mano y anotando cada clic.
- Entradas: credenciales, cuenta objetivo, límite.
- Procesos: iniciar sesión → cerrar ventanas → abrir perfil → abrir seguidores → desplazar → seguir.
- Salidas: cuentas seguidas y un resumen.
- Dividir en métodos (uno por proceso).
- Diseñar: escribe los XPath que usarás antes de programar, y pruébalos en la consola de Chrome con
$x("tu_xpath"). - 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
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
- 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.
- Con conjuntos de Python (Día 49), calcula: seguidores, seguidos, mutuos (intersección), "no me siguen de vuelta" (diferencia) y "no sigo de vuelta".
- Un informe en texto con los números y, como máximo, los 10 primeros nombres de cada grupo.
- 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
- ¿Por qué
contains(text(), 'Log in')suele ser más estable que una clase comox1lliihq? - ¿Qué diferencia hay entre desplazar la página y desplazar un elemento?
- ¿Qué significan
scrollTopyscrollHeight? - ¿Por qué es mejor
except ElementClickInterceptedException:queexcept:? - Nombra dos razones (una técnica y una ética) para usar Share-a-Naan en lugar de Instagram.
Análisis de código
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
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
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
InstaFollowerfuncionando 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:
driversinself., clase sin instanciar,find_elementcontrafind_elements,except:vacío, "Follow" contra "Following", desplazar la página en vez de la ventana,arguments[0]sin pasar el elemento.