Selenium, Auth y Scraping Concurrente
Control real de navegadores, páginas protegidas y descargas paralelas con futures
En esta tercera y última parte del capítulo dominamos las técnicas más avanzadas: Selenium para controlar un navegador Chrome real, autenticación HTTP básica para páginas protegidas con contraseña, y concurrent.futures para acelerar las descargas ejecutándolas en paralelo.
- Descargar páginas web
- Analizar HTML
- Rastrear la web (crawling)
- Suscribirse a feeds RSS
- Acceder a APIs web
- Interactuar con formularios
- Selenium para interacción avanzada
- Páginas protegidas por contraseña
- Acelerar el web scraping
Selenium: Automatización de Navegador
A veces, nada menos que la herramienta real funcionará. Selenium permite controlar un navegador web completo programáticamente. Está concebido para pruebas automáticas pero también es perfecto para automatizar interacciones con sitios que dependen de JavaScript o tienen comportamientos complejos.
Preparación
Instala chromedriver para tu versión de Chrome y el módulo Python:
# chromedriver en: https://sites.google.com/a/chromium.org/chromedriver/ $ echo "selenium==3.141.0" >> requirements.txt $ pip install -r requirements.txt
Cómo hacerlo
from selenium import webdriver # Paso 1: Iniciar Chrome y cargar la página browser = webdriver.Chrome() browser.get('https://httpbin.org/forms/post') # Se abre una ventana de Chrome con un banner indicando control automático # Paso 2: Ingresar texto en un campo custname = browser.find_element_by_name("custname") custname.clear() custname.send_keys("Sean O'Connell") # Paso 3: Seleccionar radio button (tamaño mediano) for size_el in browser.find_elements_by_name("size"): if size_el.get_attribute('value') == 'medium': size_el.click() # Paso 4: Marcar checkboxes múltiples for topping in browser.find_elements_by_name('topping'): if topping.get_attribute('value') in ['bacon', 'cheese']: topping.click() # Paso 5: Enviar el formulario browser.find_element_by_tag_name('form').submit() # La página se actualiza mostrando el JSON de respuesta # Paso 6: Cerrar el navegador browser.quit()
Selectores disponibles en Selenium
# Elemento único (lanza error si no existe) browser.find_element_by_id('mi-id') browser.find_element_by_name('mi-nombre') browser.find_element_by_class_name('mi-clase') browser.find_element_by_tag_name('input') browser.find_element_by_css_selector('div.mi-clase > input') # Lista de elementos (devuelve [] si no existen) browser.find_elements_by_name('topping') # Interacción con elementos elemento.send_keys('texto a ingresar') # Escribir elemento.click() # Clic / toggle checkbox elemento.submit() # Enviar formulario elemento.clear() # Limpiar campo elemento.get_attribute('value') # Leer atributo HTML elemento.text # Texto visible del elemento # Captura de pantalla browser.save_screenshot('captura.png')
requests: Selenium ejecuta JavaScript, maneja cookies de sesión reales, espera a que carguen elementos dinámicos y simula interacciones humanas reales. Úsalo cuando la página requiere JS para funcionar.
Páginas Protegidas por Contraseña
La autenticación básica HTTP es un método simple donde el nombre de usuario y la contraseña se envían con cada solicitud. Es compatible con prácticamente todos los servidores web y requests lo soporta de forma nativa con el parámetro auth.
Cómo hacerlo
import requests URL = 'https://httpbin.org/basic-auth/user/psswd' # Método 1: Parámetro auth=(usuario, contraseña) response = requests.get(URL, auth=('user', 'psswd')) print(response.status_code) # 200 # Credenciales incorrectas → 401 Unauthorized response = requests.get(URL, auth=('user', 'wrong')) print(response.status_code) # 401 # Método 2: Credenciales embebidas en la URL response = requests.get('https://user:psswd@httpbin.org/basic-auth/user/psswd') print(response.status_code) # 200 # Método 3: Sesión con autenticación persistente # (Ideal para múltiples páginas del mismo sitio) session = requests.Session() session.auth = ('user', 'psswd') response = session.get(URL) print(response.status_code) # 200 # Todas las siguientes llamadas a session.get() usarán las mismas credenciales
401
200
200
HTTPS al enviar credenciales. Con HTTP, la contraseña viaja en texto claro por internet y puede ser capturada por cualquier elemento que esté escuchando el tráfico de red.
Session de requests mantiene cookies, cabeceras y autenticación entre múltiples solicitudes. Es la forma más eficiente cuando necesitas acceder a varias páginas del mismo sitio autenticado.
Acelerar el Web Scraping
La mayor parte del tiempo en web scraping se pasa esperando respuestas del servidor. Las computadoras pueden hacer otras cosas mientras esperan, incluyendo hacer más solicitudes simultáneas. Usaremos concurrent.futures para descargar páginas en paralelo.
Concepto: Futures y Executors
Un future es un objeto que representa la promesa de un valor. Recibes el objeto inmediatamente mientras el código se ejecuta en segundo plano. Solo cuando pides .result() el código espera a que esté listo.
Benchmark: Workers vs Tiempo
Cómo hacerlo
import concurrent.futures, argparse def process_link(source_link, text): # Igual que antes pero ahora devuelve (link, nuevos_links) ... return source_link, get_links(parsed_source, page) def main(base_url, to_search, workers): checked_links = set() to_check = [base_url] max_checks = 10 with concurrent.futures.ThreadPoolExecutor(max_workers=workers) as executor: while to_check: # Crear futures para todas las URLs pendientes en paralelo futures = [executor.submit(process_link, url, to_search) for url in to_check] to_check = [] # Procesar resultados conforme van terminando for data in concurrent.futures.as_completed(futures): link, new_links = data.result() checked_links.add(link) for link in new_links: if link not in checked_links and link not in to_check: to_check.append(link) max_checks -= 1 if not max_checks: return if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('-w', type=int, default=4, help='Número de workers') args = parser.parse_args() main(args.u, args.p, args.w)
real 0m6.234s
user 0m0.171s
sys 0m0.040s
Alternativas de concurrencia
# Opción 1: requests-futures (integrado con la API de requests) # https://github.com/ross/requests-futures from requests_futures.sessions import FuturesSession session = FuturesSession() future = session.get('https://ejemplo.com') # No bloquea response = future.result() # Espera aquí # Opción 2: aiohttp + asyncio (programación asíncrona) # https://aiohttp.readthedocs.io/ import aiohttp, asyncio async def fetch(session, url): async with session.get(url) as response: return await response.text() async def main(): async with aiohttp.ClientSession() as session: html = await fetch(session, 'https://ejemplo.com') print(html)
time.
Resumen Completo // Capítulo 3
Descargar páginas
requests.get() · status_code · response.text · cabeceras HTTP
Analizar HTML
BeautifulSoup · .find() · .find_all() · next_elements · parsers
Web Crawling
urlparse · seguir enlaces · filtrar dominio · límites de seguridad
Feeds RSS
feedparser · rss.entries · título · link · summary · published
APIs REST
GET/POST/PATCH/PUT/DELETE · .json() · json= argumento · 201 Created
Formularios
POST data · URL-encoded · inspeccionar campos · tokens CSRF
Selenium
Chrome real · find_element · send_keys · click · submit · quit
Autenticación
auth=() · credenciales en URL · requests.Session() · HTTPS
Concurrencia
ThreadPoolExecutor · futures · as_completed · workers paralelos