Playwright con Python: Tutorial Automatizar el Navegador
Playwright es una librería desarrollada por Microsoft que permite controlar navegadores web (Chromium, Firefox, Safari) mediante código. Puedes hacer clic en botones, rellenar formularios, esperar a que carguen elementos, extraer datos o hacer capturas de pantalla, todo de forma automática.
¿Por qué Playwright y no Selenium?
- Es más rápido y moderno (lanzado en 2020 vs 2004)
- Espera automáticamente a que los elementos estén listos (auto-wait)
- Soporta páginas con JavaScript pesado (React, Vue, Angular) sin configuración extra
- API más limpia y consistente
- Soporte nativo para múltiples pestañas, iframes y descargas
Si ya conoces Selenium, Playwright te resultará familiar pero notarás inmediatamente que hay menos fricción.
1. Instalación
Necesitas Python 3.8 o superior. Instala la librería y los navegadores con dos comandos:
pip install playwright playwright install
El segundo comando descarga Chromium, Firefox y WebKit (Safari). Si solo quieres Chromium para ahorrar espacio:
playwright install chromium
Verifica que funciona:
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto("https://example.com")
print(page.title())
browser.close()
Si ves Example Domain en la consola, todo está funcionando.
2. Conceptos clave antes de empezar
Antes de escribir código, entiende estos tres objetos que usarás constantemente:
| Objeto | Qué es | Analogía |
|---|---|---|
Browser | El navegador abierto | La ventana de Chrome |
Page | Una pestaña del navegador | Una pestaña dentro de Chrome |
Locator | Un elemento de la página | Un botón, input o div concreto |
La mayoría de tu código seguirá este patrón:
Browser → Page → Locator → Acción
3. Modos de ejecución: sync vs async
Playwright ofrece dos APIs:
Síncrona — más simple, ideal para scripts y automatizaciones lineales:
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
# tu código aquí
browser.close()
Asíncrona — para integrar con frameworks como FastAPI o cuando necesitas ejecutar varias tareas en paralelo:
import asyncio
from playwright.async_api import async_playwright
async def main():
async with async_playwright() as p:
browser = await p.chromium.launch()
page = await browser.new_page()
# tu código aquí
await browser.close()
asyncio.run(main())
Para este tutorial usaremos la API síncrona. Es más legible y suficiente para el 90% de los casos.
4. Navegar y esperar contenido
Abrir una URL
page.goto("https://quotes.toscrape.com")
Por defecto, goto espera a que la página haya cargado completamente (load event). Puedes cambiar esto:
# Esperar solo a que el HTML esté disponible (más rápido)
page.goto("https://ejemplo.com", wait_until="domcontentloaded")
# Esperar a que la red esté inactiva (útil en SPAs)
page.goto("https://ejemplo.com", wait_until="networkidle")
Esperar un elemento específico
Una de las mejores características de Playwright: espera automáticamente a que el elemento exista y sea visible antes de interactuar con él.
# Playwright espera hasta 30 segundos por defecto
page.locator("h1").wait_for()
# Cambiar el timeout a 10 segundos
page.locator("h1").wait_for(timeout=10000) # en milisegundos
5. Seleccionar elementos
Esta es la parte más importante. Playwright usa locators para encontrar elementos.
Por texto visible
# Encuentra un elemento que contenga exactamente ese texto
page.get_by_text("Iniciar sesión")
# Texto parcial
page.get_by_text("Iniciar", exact=False)
Por rol ARIA (recomendado para formularios)
page.get_by_role("button", name="Enviar")
page.get_by_role("link", name="Ver más")
page.get_by_role("textbox", name="Email")
Por placeholder
page.get_by_placeholder("Escribe tu email")
Por selector CSS (el más flexible)
page.locator("h1") # etiqueta
page.locator(".card-title") # clase
page.locator("#submit-btn") # id
page.locator("ul.results li") # anidado
page.locator("input[type='email']") # atributo
Por selector XPath (para casos complejos)
page.locator("//div[@class='producto']//span[@class='precio']")
Regla práctica: usa get_by_role y get_by_text cuando puedas. Usa CSS cuando necesites precisión. Recurre a XPath solo si no hay otra opción.
6. Acciones fundamentales
Hacer clic
page.locator("#boton-login").click()
# Clic derecho
page.locator(".elemento").click(button="right")
# Doble clic
page.locator(".elemento").dbl_click()
Rellenar inputs
# Limpia el campo y escribe
page.locator("input[name='email']").fill("usuario@ejemplo.com")
# Escribe carácter a carácter (útil para campos con validación en tiempo real)
page.locator("input[name='email']").type("usuario@ejemplo.com", delay=50)
Seleccionar en un <select>
page.locator("select#pais").select_option("ES") # por valor
page.locator("select#pais").select_option(label="España") # por texto visible
Checkbox y radio buttons
page.locator("input[type='checkbox']").check()
page.locator("input[type='checkbox']").uncheck()
page.locator("input[value='opcion2']").check()
Subir archivos
page.locator("input[type='file']").set_input_files("documento.pdf")
Pulsar teclas
page.keyboard.press("Enter")
page.keyboard.press("Tab")
page.keyboard.press("Control+A") # seleccionar todo
7. Extraer información
Texto de un elemento
titulo = page.locator("h1").inner_text()
print(titulo)
Atributo de un elemento
enlace = page.locator("a.leer-mas").get_attribute("href")
imagen = page.locator("img.portada").get_attribute("src")
Múltiples elementos
Cuando hay varios elementos que coinciden con el selector, usa all():
titulos = page.locator(".card-title").all()
for titulo in titulos:
print(titulo.inner_text())
# O en una línea con list comprehension
textos = [el.inner_text() for el in page.locator(".card-title").all()]
Ejemplo completo: extraer una lista de productos
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto("https://quotes.toscrape.com")
productos = []
items = page.locator(".quote").all()
for item in items:
texto = item.locator(".text").inner_text()
autor = item.locator(".author").inner_text()
productos.append({"texto": texto, "autor": autor})
browser.close()
for p in productos:
print(f"{p['autor']}: {p['texto'][:60]}...")
8. Paginación
La mayoría de sitios dividen el contenido en páginas. Aquí tienes el patrón estándar:
from playwright.sync_api import sync_playwright
resultados = []
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto("https://quotes.toscrape.com")
while True:
# Extraer datos de la página actual
items = page.locator(".quote").all()
for item in items:
resultados.append({
"texto": item.locator(".text").inner_text(),
"autor": item.locator(".author").inner_text()
})
# Buscar botón de siguiente página
siguiente = page.locator("li.next a")
if siguiente.count() == 0:
break # No hay más páginas
siguiente.click()
page.wait_for_load_state("domcontentloaded")
browser.close()
print(f"Total extraídos: {len(resultados)}")
9. Formularios y autenticación
Automatizar un login es uno de los casos más comunes:
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=False) # headless=False para ver el navegador
page = browser.new_page()
# Ir al login
page.goto("https://quotes.toscrape.com/login")
# Rellenar credenciales
page.get_by_label("Username").fill("admin")
page.get_by_label("Password").fill("password123")
# Enviar formulario
page.get_by_role("button", name="Login").click()
# Verificar que el login fue correcto
page.wait_for_url("**/") # espera a que la URL cambie al home
print("Login correcto:", page.url)
# A partir de aquí puedes navegar como usuario autenticado
page.goto("https://quotes.toscrape.com/")
browser.close()
Guardar la sesión para no hacer login cada vez
# Primera ejecución: hacer login y guardar cookies
context = browser.new_context()
page = context.new_page()
# ... proceso de login ...
context.storage_state(path="sesion.json")
# Siguientes ejecuciones: cargar sesión guardada
context = browser.new_context(storage_state="sesion.json")
page = context.new_page()
page.goto("https://app.ejemplo.com/dashboard")
# Ya estás autenticado sin hacer login
10. Screenshots y PDFs
Captura de pantalla
# Captura de la vista actual
page.screenshot(path="captura.png")
# Captura de la página completa (scroll completo)
page.screenshot(path="pagina-completa.png", full_page=True)
# Captura de un elemento específico
page.locator(".grafico-ventas").screenshot(path="grafico.png")
Generar PDF (solo funciona en Chromium)
page.pdf(path="reporte.pdf")
# Con configuración de página
page.pdf(
path="reporte.pdf",
format="A4",
print_background=True,
margin={"top": "2cm", "bottom": "2cm", "left": "2cm", "right": "2cm"}
)
Esto es especialmente útil para generar reportes: cargas una página HTML con tus datos y la exportas a PDF automáticamente.
11. Manejar diálogos y ventanas emergentes
Alertas JavaScript
# Aceptar automáticamente cualquier alert/confirm
page.on("dialog", lambda dialog: dialog.accept())
# O rechazar
page.on("dialog", lambda dialog: dialog.dismiss())
# Leer el mensaje antes de decidir
def manejar_dialogo(dialog):
print(f"Tipo: {dialog.type}, Mensaje: {dialog.message}")
dialog.accept()
page.on("dialog", manejar_dialogo)
Nueva pestaña o ventana
# Esperar a que se abra una nueva página al hacer clic
with page.expect_popup() as popup_info:
page.locator("a[target='_blank']").click()
nueva_pagina = popup_info.value
nueva_pagina.wait_for_load_state()
print(nueva_pagina.url)
12. Modo headless y configuración del navegador
Headless vs visible
# Headless: sin interfaz gráfica (más rápido, para producción) browser = p.chromium.launch(headless=True) # por defecto es True # Con ventana visible (para desarrollo y debugging) browser = p.chromium.launch(headless=False, slow_mo=500) # slow_mo=500 añade 500ms entre acciones para seguirlo con la vista
Simular un dispositivo móvil
iphone = p.devices["iPhone 13"]
context = browser.new_context(**iphone)
page = context.new_page()
page.goto("https://ejemplo.com")
Configurar viewport y user agent
context = browser.new_context(
viewport={"width": 1920, "height": 1080},
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0"
)
Bloquear imágenes para ir más rápido
def bloquear_recursos(route):
if route.request.resource_type in ["image", "font", "stylesheet"]:
route.abort()
else:
route.continue_()
page.route("**/*", bloquear_recursos)
13. Manejo de errores
Playwright lanza excepciones cuando algo falla. Envuelve las operaciones críticas en try/except:
from playwright.sync_api import sync_playwright, TimeoutError as PlaywrightTimeout
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto("https://ejemplo.com")
try:
# Espera 5 segundos máximo
page.locator(".precio").wait_for(timeout=5000)
precio = page.locator(".precio").inner_text()
except PlaywrightTimeout:
print("El elemento .precio no apareció en 5 segundos")
precio = None
browser.close()
Verificar si un elemento existe antes de interactuar
boton = page.locator(".ver-mas")
if boton.count() > 0:
boton.click()
else:
print("No hay botón 'ver más' en esta página")
14. Script completo de ejemplo
Este script combina todo lo anterior: navega varias páginas, extrae datos, maneja errores y guarda el resultado en CSV.
import csv
from playwright.sync_api import sync_playwright, TimeoutError as PlaywrightTimeout
def extraer_citas():
resultados = []
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
# Configurar el contexto con viewport realista
context = browser.new_context(
viewport={"width": 1280, "height": 800}
)
page = context.new_page()
# Bloquear imágenes para ir más rápido
page.route("**/*.{png,jpg,jpeg,gif,svg}", lambda r: r.abort())
print("Navegando a la página...")
page.goto("https://quotes.toscrape.com", wait_until="domcontentloaded")
pagina_num = 1
while True:
print(f"Extrayendo página {pagina_num}...")
try:
page.locator(".quote").first.wait_for(timeout=5000)
except PlaywrightTimeout:
print(f"No se cargaron elementos en página {pagina_num}")
break
# Extraer todas las citas de la página actual
quotes = page.locator(".quote").all()
for quote in quotes:
try:
texto = quote.locator(".text").inner_text()
autor = quote.locator(".author").inner_text()
tags = [t.inner_text() for t in quote.locator(".tag").all()]
resultados.append({
"texto": texto,
"autor": autor,
"tags": ", ".join(tags),
"pagina": pagina_num
})
except Exception as e:
print(f"Error extrayendo cita: {e}")
continue
# Siguiente página
siguiente = page.locator("li.next a")
if siguiente.count() == 0:
print("Última página alcanzada.")
break
siguiente.click()
page.wait_for_load_state("domcontentloaded")
pagina_num += 1
browser.close()
return resultados
def guardar_csv(datos, archivo="citas.csv"):
if not datos:
print("No hay datos para guardar.")
return
with open(archivo, "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=["texto", "autor", "tags", "pagina"])
writer.writeheader()
writer.writerows(datos)
print(f"✓ {len(datos)} citas guardadas en {archivo}")
if __name__ == "__main__":
citas = extraer_citas()
guardar_csv(citas)
15. Consejos para proyectos reales
Sé respetuoso con los sitios que automatizas. Añade pausas entre peticiones y no hagas scraping masivo sin revisar el robots.txt del sitio.
import time time.sleep(1) # pausa de 1 segundo entre páginas
Usa variables de entorno para credenciales, nunca las hardcodees en el script:
import os
usuario = os.getenv("APP_USER")
password = os.getenv("APP_PASS")
Estructura tus scripts como funciones o clases desde el principio. Un script que hace todo en línea es difícil de mantener cuando crece.
Playwright tiene un generador de código. Si no sabes qué selector usar, ejecuta:
playwright codegen https://ejemplo.com
Se abre el navegador y graba cada acción que haces, generando el código Python automáticamente.
Próximos pasos
Con lo que has visto en este tutorial puedes construir:
- Scripts de scraping que extraen y guardan datos de cualquier web
- Bots de formularios que automatizan registros, pedidos o reservas
- Generadores de reportes en PDF o capturas de pantalla programadas
- Tests automáticos que verifican que tu propia web funciona correctamente
Los siguientes artículos de esta serie profundizan en cada uno de estos casos con proyectos reales de cliente a cliente:
- → Introduccion al web scraping con Python y BeautifulSoup
- → Cómo hacer web scraping con Playwright y Python paso a paso
- → Automatizar formularios web con Playwright: casos reales
- → Captura de screenshots y generación de PDF con Playwright
- → Playwright para pruebas E2E: guía práctica
¿Tienes un proceso en tu empresa que podría automatizarse? Cuéntanoslo — hacemos una auditoría gratuita para ver si es viable.