Requiero una introducción a Playwright con Python.
Playwright es una biblioteca de automatización para navegadores moderna y potente, creada por Microsoft. Si tu proyecto de scraping implica lidiar con sitios web complejos y dinámicos (como el que describes para tu controlador de leads), Playwright puede ser tu mejor aliado. A continuación, te presento una introducción práctica para empezar a usarlo con Python.
🤔 ¿Por qué Playwright y no Selenium?
Ambas herramientas pueden controlar navegadores de forma automática, pero Playwright fue diseñado para resolver muchos de los dolores de cabeza de Selenium. Comparado con Selenium, Playwright destaca por su velocidad, ya que es notablemente más rápido en la ejecución de tareas, y por su sistema de espera automática, que evita que tu script falle porque un elemento aún no ha cargado, ahorrándote tener que escribir time.sleep() constantemente. Es una alternativa moderna que simplifica la automatización y el scraping complejo.
🛠️ Paso a Paso para tu Primer Proyecto con Playwright
1. Verifica tu entorno
Antes de empezar, asegúrate de tener Python instalado. Abre tu terminal y escribe:
python --version # o python3 --version
Deberías ver una versión de Python 3.7 o superior. Si no es así, descárgalo desde la página oficial de Python.
2. Instalación (Solo dos comandos)
La instalación es muy sencilla. Primero, instala la librería de Playwright y luego descarga los navegadores que usarás para la automatización.
# 1. Instalar la librería Playwright pip install playwright # 2. Instalar los navegadores (Chromium, Firefox, WebKit) playwright install
Consejo: Si estás en China y la descarga de navegadores es lenta, puedes usar un espejo (mirror) para acelerar el proceso.
3. Escribe tu Primer Script (el "Hola Mundo")
Crea un archivo, por ejemplo, primer_scraping.py, y escribe el siguiente código que automatiza una búsqueda en Google:
from playwright.sync_api import sync_playwright with sync_playwright() as p: # Lanza el navegador Chromium en modo visible para que veas la acción browser = p.chromium.launch(headless=False) page = browser.new_page() # Ve a la URL de Google page.goto("https://www.google.com") # Encuentra la caja de búsqueda, escribe "Playwright" y presiona Enter page.locator('textarea[name="q"]').fill("Playwright Python") page.locator('textarea[name="q"]').press("Enter") # Espera a que carguen los resultados y toma una captura de pantalla page.wait_for_load_state("networkidle") page.screenshot(path="resultado_google.png") # Cierra el navegador browser.close()
Este script lanza un navegador, realiza una búsqueda en Google, espera a que la página cargue completamente y guarda una captura de pantalla llamada resultado_google.png.
🧠 Entendiendo los Conceptos Clave
API Síncrona vs. Asíncrona: Playwright te ofrece dos formas de escribir tu código.
Síncrona (
sync_api): Es la más sencilla y directa. El código se ejecuta paso a paso y es ideal para la mayoría de los proyectos de scraping. Los ejemplos anteriores usan este enfoque.Asíncrona (
async_api): Es más avanzada y está pensada para proyectos que requieren manejar muchas tareas al mismo tiempo (concurrencia) para ser más eficientes.
Localizadores (Locators): Para interactuar con elementos de una página (botones, cajas de texto), necesitas "localizarlos". Playwright ofrece múltiples estrategias robustas y legibles.
page.locator('css_selector'): Usa selectores CSS.page.get_by_text("Texto del elemento"): Busca por el texto visible.page.get_by_role("button", name="Enviar"): Busca por su rol (recomendado por su estabilidad).page.get_by_label("Usuario"): Busca por la etiqueta de un formulario.
Esperas Automáticas (Auto-Waiting): Esta es una de las características estrella de Playwright. Cuando usas un localizador para hacer
click()ofill(), Playwright espera automáticamente a que el elemento exista y esté listo para la interacción, sin que tengas que añadir pausas manuales en tu código.
🚀 Herramientas que te Facilitarán la Vida
Playwright Inspector y Grabador (
codegen): Esta herramienta es increíble para empezar. Al ejecutarplaywright codegenen tu terminal, se abrirá un navegador y, mientras tú haces clics y escribes, Playwright irá generando el código Python correspondiente. Es una forma muy práctica de aprender la sintaxis y crear scripts rápidamente.
⚙️ Modo de Depuración
Cuando estés desarrollando, usa el parámetro slow_mo en launch() para ralentizar las acciones y ver qué está pasando. Esto es muy útil para depurar errores.
# En el script de ejemplo browser = p.chromium.launch(headless=False, slow_mo=500) # 500ms de pausa entre acciones
En resumen, Playwright con Python te da un control total sobre navegadores modernos con una API limpia y potente. Es una herramienta que, sin duda, te ayudará a superar los desafíos de scraping de tu proyecto.
¿Te gustaría que profundice en algún aspecto concreto, como usar localizadores más complejos o manejar el escenario de los CAPTCHA con Playwright?
Tu intuición es correcta. Aunque la documentación oficial de Playwright está muy orientada a pruebas, no estás obligado a usarlo de esa manera. La belleza de Playwright es que es una herramienta de automatización de navegadores muy versátil, y para tu proyecto de scraping y leads, puedes usarla de la forma que mejor se adapte a tus necesidades.
Vamos a verlo en detalle para que tomes la mejor decisión.
🤔 ¿Por qué la documentación enfatiza las pruebas (E2E)?
La razón principal es ecosistema y herencia, no una limitación técnica.
El caso de uso principal: Playwright fue creado por Microsoft para ser una herramienta de pruebas end-to-end (E2E) confiable y moderna, y en eso es excelente . Su objetivo es automatizar navegadores para simular usuarios reales en entornos de pruebas.
La elección de Pytest en Python: La comunidad de Python ya tenía un estándar de facto para pruebas: Pytest. En lugar de crear su propio sistema desde cero para Python, el equipo de Playwright creó el plugin
pytest-playwright, que integra de forma nativa las potentes funcionalidades de Playwright con la estructura y los fixtures de Pytest . Por eso, la documentación te guía hacia este camino: es la forma más "oficial" y estructurada de usar Playwright en Python, especialmente para quienes vienen del mundo del testing.
🛠️ Dos formas de usar Playwright para tu proyecto
Tienes dos caminos perfectamente válidos:
1. Scripts Independientes (El camino "libre")
Este es el que más se ajusta a lo que tienes en mente. Consiste en escribir scripts de Python que usen directamente la API de Playwright (sync_playwright o async_playwright) para controlar el navegador y realizar tareas de automatización.
Cómo se hace: Creas un archivo
.py, importasplaywrighty controlas el ciclo de vida del navegador (abrirlo, navegar, interactuar, cerrar) dentro de un bloquewith sync_playwright() as p:.Ventajas:
Máxima flexibilidad: Tienes control total sobre el flujo de tu script, sin la estructura "impuesta" por un test runner.
Menor curva de aprendizaje inicial: Te centras en la lógica de tu scraper, no en cómo estructurar pruebas.
Para tu caso: Es perfecto para un script que:
Recibe un número de documento.
Abre el navegador con Playwright.
Llena el formulario en la web.
Resuelve el desafío matemático (usando la lógica que ya hemos diseñado).
Extrae el resultado (activo/inactivo).
Guarda el resultado y cierra el navegador.
Este es, probablemente, el enfoque más directo para la fase de prototipado y para un proyecto unipersonal.
2. Pytest + Plugin pytest-playwright (El camino "estructurado")
Este enfoque integra Playwright con Pytest. Cada "prueba" que escribes es, en realidad, una automatización que se ejecuta en un contexto aislado.
Cómo se hace: Usas los fixtures que el plugin te proporciona, como
pageobrowser, directamente en tus funciones de prueba . Playwright se encarga de abrir y cerrar el navegador por ti.Ventajas:
Fixtures y aislamiento: El plugin se encarga de la gestión del navegador, y cada prueba tiene su propio contexto de navegador, evitando que el estado de una afecte a otra .
Integración con herramientas: Puedes usar fácilmente plugins de Pytest para ejecución en paralelo (
pytest-xdist), generar reportes, etc. .
Para tu caso: Podrías escribir "pruebas" que, en realidad, verifican el estado de un lead. Cada lead sería un escenario de prueba. Aunque esto es técnicamente posible, puede resultar menos natural que un script simple.
En resumen: Pytest es una opción, no una imposición. dejan claro que pytest-playwright es un plugin sobre el núcleo de Playwright, y puedes usar este último de forma totalmente independiente. Wikipedia también lo define como una "librería de automatización para pruebas y web scraping" .
💡 Decisión Final para tu Proyecto
Para tu proyecto, donde el objetivo es un scraper funcional y no un test suite, te recomiendo que empieces con un script independiente.
Script independiente: Es más rápido de escribir y depurar, y te da control total sobre el flujo de trabajo. Una vez que tengas la lógica de scraping funcionando perfectamente, podrías, si lo deseas, envolverlo en una función para que Laravel lo llame.
Si luego necesitas escalar: Si tu proyecto crece y necesitas procesar muchos leads en paralelo o generar reportes detallados de fallos, entonces considerar el enfoque con Pytest puede tener sentido para estructurar y mantener el código. Pero para empezar, no lo necesitas.
¿Te gustaría que te ayude a diseñar la estructura de un script Python con Playwright para tu caso específico (abrir web, llenar formulario, resolver desafío) que puedas probar de forma aislada?
Perfecto. Vamos a diseñar la estructura de un script Python con Playwright para tu caso específico. Te daré una base sólida que puedas probar y adaptar a tus necesidades.
🏗️ Estructura Base del Script
from playwright.sync_api import sync_playwright import re import time from datetime import datetime class LeadScraper: def __init__(self, headless=False, slow_mo=100): """ Inicializa el scraper. Args: headless: True para ejecutar sin interfaz gráfica, False para ver el navegador slow_mo: Milisegundos de pausa entre acciones (útil para depuración) """ self.headless = headless self.slow_mo = slow_mo self.browser = None self.page = None def iniciar_navegador(self): """Inicia el navegador y abre una nueva página""" self.playwright = sync_playwright().start() self.browser = self.playwright.chromium.launch( headless=self.headless, slow_mo=self.slow_mo ) self.page = self.browser.new_page() def cerrar_navegador(self): """Cierra el navegador y limpia recursos""" if self.browser: self.browser.close() if self.playwright: self.playwright.stop() def navegar_a_consulta(self, url): """Navega a la página de consulta""" self.page.goto(url) # Espera a que la página cargue completamente self.page.wait_for_load_state("networkidle") def llenar_formulario(self, numero_documento): """ Llena el campo del número de documento. NOTA: Los selectores son ejemplos; deberás ajustarlos. """ # Buscar el campo de documento (ajusta el selector) campo_documento = self.page.locator('input[name="documento"]') campo_documento.fill(numero_documento) def extraer_desafio(self): """ Extrae el texto del desafío matemático o CAPTCHA. NOTA: Los selectores son ejemplos; deberás ajustarlos. """ try: # Busca el texto del desafío (ajusta el selector) desafio_element = self.page.locator('.captcha-text, .math-challenge, #captcha-text') desafio_texto = desafio_element.text_content() print(f"📝 Desafío encontrado: {desafio_texto}") return desafio_texto except Exception as e: print(f"❌ No se pudo extraer el desafío: {e}") return None def resolver_desafio_matematico(self, texto_desafio): """ Resuelve el desafío matemático usando la lógica que desarrollamos. """ # Aquí va la lógica que ya hemos diseñado # (función resolver_captcha que vimos antes) return self.resolver_captcha(texto_desafio) def resolver_captcha(self, texto): """Versión simplificada - Puedes expandirla con tu lógica completa""" # Extrae números y operación patron = r'(\d+)\s*([+\-×÷*/])\s*(\d+)' match = re.search(patron, texto) if not match: return None num1 = int(match.group(1)) operador = match.group(2) num2 = int(match.group(3)) # Verifica si hay instrucción de exclusión if "no tenga en cuenta" in texto.lower() or "ignore" in texto.lower(): # Busca qué número ignorar patron_ignorar = r'(?:no tenga en cuenta|ignore)(?:\s+el\s+número)?\s+(\d+)' match_ignorar = re.search(patron_ignorar, texto.lower()) if match_ignorar: numero_a_ignorar = int(match_ignorar.group(1)) if num1 == numero_a_ignorar: num1 = 0 if num2 == numero_a_ignorar: num2 = 0 # Resuelve la operación ops = { '+': lambda a, b: a + b, '-': lambda a, b: a - b, '×': lambda a, b: a * b, '*': lambda a, b: a * b, '÷': lambda a, b: a // b, '/': lambda a, b: a // b } resultado = ops.get(operador, lambda a, b: a + b)(num1, num2) print(f"🧮 Resultado calculado: {resultado}") return str(resultado) def enviar_respuesta_desafio(self, respuesta): """ Envía la respuesta del desafío. NOTA: Los selectores son ejemplos; deberás ajustarlos. """ try: campo_respuesta = self.page.locator('input[name="captcha"], input[name="resultado"]') campo_respuesta.fill(respuesta) # Envía el formulario (puede ser un botón o presionar Enter) boton_enviar = self.page.locator('button[type="submit"], input[type="submit"]') boton_enviar.click() print("✅ Desafío enviado") return True except Exception as e: print(f"❌ Error al enviar respuesta: {e}") return False def extraer_resultado_lead(self): """ Extrae si el lead está activo o inactivo de la página de resultados. NOTA: Los selectores son ejemplos; deberás ajustarlos. """ # Espera a que cargue la página de resultados self.page.wait_for_load_state("networkidle") # Busca indicadores de "activo" o "inactivo" en la página pagina_html = self.page.content() # Detecta palabras clave en el HTML (ajusta según la página real) if "activo" in pagina_html.lower() or "encontrado" in pagina_html.lower(): return "activo" elif "inactivo" in pagina_html.lower() or "no encontrado" in pagina_html.lower() or "error" in pagina_html.lower(): return "inactivo" else: return "desconocido" def procesar_lead(self, numero_documento, url_consulta): """ Flujo principal para procesar un lead. """ try: print(f"🚀 Procesando lead: {numero_documento}") # 1. Navegar a la página self.navegar_a_consulta(url_consulta) # 2. Llenar formulario self.llenar_formulario(numero_documento) # 3. Extraer desafío desafio_texto = self.extraer_desafio() if not desafio_texto: print("⚠️ No se pudo extraer el desafío. Requiere intervención manual.") return {"estado": "error", "motivo": "desafío_no_encontrado"} # 4. Resolver desafío respuesta = self.resolver_desafio_matematico(desafio_texto) if respuesta is None: print("⚠️ No se pudo resolver automáticamente. Requiere intervención manual.") # Toma captura de pantalla para revisión manual self.page.screenshot(path=f"captcha_manual_{numero_documento}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png") return {"estado": "pendiente_manual", "motivo": "desafío_no_resuelto"} # 5. Enviar respuesta if not self.enviar_respuesta_desafio(respuesta): return {"estado": "error", "motivo": "fallo_envio_desafio"} # 6. Extraer resultado # Espera breve para que la página procese la respuesta time.sleep(2) resultado = self.extraer_resultado_lead() print(f"✅ Lead {numero_documento} procesado: {resultado}") return {"estado": "completado", "resultado": resultado} except Exception as e: print(f"❌ Error procesando lead {numero_documento}: {e}") return {"estado": "error", "motivo": str(e)} # --- Función Principal --- def main(): """ Función principal para ejecutar el scraper. """ # Configuración URL_CONSULTA = "https://ejemplo.com/consulta" # ¡CAMBIA ESTO! NUMERO_DOCUMENTO = "12345678" # ¡CAMBIA ESTO! # Crea el scraper (headless=False para ver el navegador) scraper = LeadScraper(headless=False, slow_mo=300) try: scraper.iniciar_navegador() resultado = scraper.procesar_lead(NUMERO_DOCUMENTO, URL_CONSULTA) print(f"\n📊 Resultado final: {resultado}") finally: scraper.cerrar_navegador() if __name__ == "__main__": main()
📋 Pasos para Probar el Script
1. Configuración Inicial
# Instala Playwright si no lo has hecho pip install playwright playwright install
2. Ajusta los Selectores
Los selectores en el script son genéricos. Deberás inspeccionar la página real y reemplazar:
| Lugar en el script | Selector ejemplo | Qué debes cambiar |
|---|---|---|
llenar_formulario() | 'input[name="documento"]' | El selector real del campo de documento |
extraer_desafio() | '.captcha-text' | El selector real donde aparece el desafío |
enviar_respuesta_desafio() | 'input[name="captcha"]' | El selector real del campo de respuesta |
enviar_respuesta_desafio() | 'button[type="submit"]' | El selector real del botón de enviar |
3. Cómo encontrar los selectores reales
Abre la página web en Chrome o Firefox.
Haz clic derecho en el elemento que te interesa (campo de texto, botón, etc.).
Selecciona "Inspeccionar" o "Inspeccionar elemento".
En las herramientas de desarrollo, busca el atributo
id,name,classo una combinación que identifique únicamente al elemento.
Ejemplos de selectores que podrías usar:
Por ID:
'#documento_input'Por nombre:
'input[name="documento"]'Por clase:
'.campo-documento'Por texto visible:
'button:has-text("Enviar")'
4. Ejecuta y Observa
python lead_scraper.py
Con headless=False verás el navegador abrirse y realizar las acciones paso a paso, lo que te permite depurar visualmente.
🚀 Próximos Pasos para Integrar con Laravel
Una vez que el script funcione correctamente de forma aislada, puedes integrarlo con Laravel usando cualquiera de estos enfoques:
Opción 1: Llamada directa desde PHP
// En tu controlador o job de Laravel $documento = $lead->documento; $resultado = shell_exec("python3 /ruta/al/script.py $documento 2>&1"); // Procesa $resultado (JSON, texto, etc.)
Opción 2: API HTTP (recomendado para mayor control)
# script_api.py - Versión con Flask from flask import Flask, request, jsonify from lead_scraper import LeadScraper app = Flask(__name__) @app.route('/procesar', methods=['POST']) def procesar(): data = request.json documento = data.get('documento') url = data.get('url') scraper = LeadScraper(headless=True) scraper.iniciar_navegador() resultado = scraper.procesar_lead(documento, url) scraper.cerrar_navegador() return jsonify(resultado) if __name__ == '__main__': app.run(host='127.0.0.1', port=5000)
Luego desde Laravel:
use Illuminate\Support\Facades\Http; $response = Http::post('http://127.0.0.1:5000/procesar', [ 'documento' => $lead->documento, 'url' => config('app.consulta_url') ]); $resultado = $response->json();
💡 Consejos para el Desarrollo
Usa
headless=Falsedurante el desarrollo para ver qué está pasando.Añade
slow_mo=300para que las acciones sean más lentas y puedas seguir el flujo.Guarda capturas de pantalla cuando ocurran errores, te ayudarán a depurar.
Loggea todo: guarda el texto del desafío, la respuesta calculada, el resultado extraído, etc.
Prueba con diferentes números de documento para ver cómo varía el comportamiento de la página.
¿Te gustaría que profundice en alguna parte específica, como la integración con Laravel o cómo manejar casos donde la página usa iframes o ventanas emergentes?
- Obtener enlace
- X
- Correo electrónico
- Otras aplicaciones
- Obtener enlace
- X
- Correo electrónico
- Otras aplicaciones
Comentarios