Automatizar el navegador con Python y Playwright
Playwright es la herramienta de automatización de navegador más moderna para Python. A diferencia de Selenium, ofrece: API asíncrona nativa, soporte multi-navegador (Chromium, Firefox, WebKit), intercepción de red, grabación de vídeo y esperas automáticas. Ideal para scraping de SPAs, tests E2E y automatización de tareas web.
Instalación
pip install playwright
playwright install # descarga Chromium, Firefox y WebKit (~300 MB)
playwright install chromium # solo Chromium (más ligero)
Uso básico: navegar y capturar pantalla
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
# Lanzar Chromium en modo visible (headless=False para ver el navegador)
navegador = p.chromium.launch(headless=True)
pagina = navegador.new_page()
# Navegar
pagina.goto("https://example.com")
# Esperar a que el contenido esté listo
pagina.wait_for_load_state("networkidle")
# Capturar pantalla completa
pagina.screenshot(path="captura.png", full_page=True)
# Obtener el título
print(f"Título: {pagina.title()}")
# Obtener el HTML
html = pagina.content()
print(f"HTML ({len(html)} caracteres)")
navegador.close()
Interactuar con formularios
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
navegador = p.chromium.launch(headless=True)
pagina = navegador.new_page()
pagina.goto("https://httpbin.org/forms/post")
# Rellenar campos de texto
pagina.fill('input[name="custname"]', "Ana García")
pagina.fill('input[name="custtel"]', "600 123 456")
pagina.fill('input[name="custemail"]', "ana@ejemplo.com")
pagina.fill('textarea[name="comments"]', "Comentario de prueba")
# Seleccionar opción en lista desplegable
pagina.select_option('select[name="size"]', 'medium')
# Marcar checkbox/radio
pagina.check('input[value="cheese"]')
# Hacer clic en botón
pagina.click('button[type="submit"]')
# Esperar navegación tras el envío
pagina.wait_for_load_state("networkidle")
print("Formulario enviado")
print(pagina.content()[:500])
navegador.close()
Esperas y elementos dinámicos
from playwright.sync_api import sync_playwright, TimeoutError
with sync_playwright() as p:
navegador = p.chromium.launch(headless=True)
pagina = navegador.new_page()
pagina.goto("https://news.ycombinator.com")
# Esperar a que un elemento específico aparezca
pagina.wait_for_selector(".storylink", timeout=10000)
# Esperar a que un elemento sea visible
pagina.wait_for_selector("#hnmain", state="visible")
# Esperar a que una URL cambie
# pagina.wait_for_url("**/otro-path/**", timeout=5000)
# Extraer texto de todos los títulos
titulos = pagina.query_selector_all(".storylink")
for titulo in titulos[:5]:
print(f" • {titulo.inner_text()}")
# Manejo de timeouts
try:
pagina.wait_for_selector(".elemento-que-no-existe", timeout=3000)
except TimeoutError:
print("El elemento no apareció en 3 segundos")
navegador.close()
Scraping con JavaScript
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
navegador = p.chromium.launch(headless=True)
pagina = navegador.new_page()
pagina.goto("https://github.com/trending")
pagina.wait_for_load_state("networkidle")
# Ejecutar JavaScript para extraer datos
repos = pagina.evaluate("""
() => {
const articulos = document.querySelectorAll('article.Box-row');
return Array.from(articulos).map(a => ({
nombre: a.querySelector('h2')?.innerText?.trim(),
desc: a.querySelector('p')?.innerText?.trim(),
estrellas: a.querySelector('.octicon-star')?.closest('a')?.innerText?.trim(),
lenguaje: a.querySelector('[itemprop=programmingLanguage]')?.innerText?.trim(),
}));
}
""")
for repo in repos[:5]:
print(f"★ {repo['estrellas']:6} | {repo['nombre']} — {repo['desc'][:60] if repo['desc'] else ''}")
navegador.close()
Interceptar solicitudes de red
from playwright.sync_api import sync_playwright
import json
def interceptar_api(ruta_url_pattern):
"""Intercepta respuestas de API para extraer datos sin renderizado HTML."""
datos_capturados = []
with sync_playwright() as p:
navegador = p.chromium.launch(headless=True)
pagina = navegador.new_page()
def capturar_respuesta(respuesta):
if ruta_url_pattern in respuesta.url:
try:
datos = respuesta.json()
datos_capturados.append(datos)
print(f"API interceptada: {respuesta.url} → {len(datos_capturados)} respuestas")
except Exception:
pass
pagina.on("response", capturar_respuesta)
pagina.goto("https://ejemplo.com/pagina-con-api")
pagina.wait_for_load_state("networkidle")
# Hacer scroll para activar lazy loading
for _ in range(3):
pagina.evaluate("window.scrollTo(0, document.body.scrollHeight)")
pagina.wait_for_timeout(1500)
navegador.close()
return datos_capturados
datos = interceptar_api("/api/productos")
Modo asíncrono (para mayor rendimiento)
import asyncio
from playwright.async_api import async_playwright
async def scraper_async(urls):
async with async_playwright() as p:
navegador = await p.chromium.launch(headless=True)
resultados = []
# Crear múltiples páginas en paralelo
async def procesar_url(url):
pagina = await navegador.new_page()
try:
await pagina.goto(url, wait_until="domcontentloaded", timeout=30000)
titulo = await pagina.title()
h1 = await pagina.inner_text("h1") if await pagina.query_selector("h1") else ""
return {"url": url, "titulo": titulo, "h1": h1}
except Exception as e:
return {"url": url, "error": str(e)}
finally:
await pagina.close()
tareas = [procesar_url(url) for url in urls]
resultados = await asyncio.gather(*tareas)
await navegador.close()
return resultados
urls_a_procesar = [
"https://example.com",
"https://python.org",
"https://github.com",
]
resultados = asyncio.run(scraper_async(urls_a_procesar))
for r in resultados:
print(f"{r.get('titulo', 'Error')}: {r.get('url')}")
Generar PDF desde una página web
from playwright.sync_api import sync_playwright
def pagina_a_pdf(url, ruta_pdf, esperar_selector=None):
with sync_playwright() as p:
navegador = p.chromium.launch(headless=True)
pagina = navegador.new_page()
pagina.goto(url, wait_until="networkidle")
if esperar_selector:
pagina.wait_for_selector(esperar_selector)
# Generar PDF (solo Chromium)
pagina.pdf(
path=ruta_pdf,
format="A4",
margin={"top": "2cm", "bottom": "2cm",
"left": "1.5cm", "right": "1.5cm"},
print_background=True,
)
print(f"PDF generado: {ruta_pdf}")
navegador.close()
pagina_a_pdf(
"https://python.org",
"python_org.pdf",
esperar_selector="h1"
)
Autenticación y cookies
from playwright.sync_api import sync_playwright
import json
def login_y_guardar_session(url_login, credenciales, ruta_cookies):
with sync_playwright() as p:
navegador = p.chromium.launch(headless=False) # visible para debug
pagina = navegador.new_page()
pagina.goto(url_login)
pagina.fill('input[name="username"]', credenciales['usuario'])
pagina.fill('input[name="password"]', credenciales['contrasena'])
pagina.click('button[type="submit"]')
pagina.wait_for_load_state("networkidle")
# Guardar cookies y estado de autenticación
estado = pagina.context.storage_state()
with open(ruta_cookies, 'w') as f:
json.dump(estado, f)
print(f"Sesión guardada: {ruta_cookies}")
navegador.close()
def usar_sesion_guardada(url, ruta_cookies):
with sync_playwright() as p:
navegador = p.chromium.launch(headless=True)
# Crear contexto con el estado de sesión guardado
contexto = navegador.new_context(storage_state=ruta_cookies)
pagina = contexto.new_page()
pagina.goto(url)
pagina.wait_for_load_state("networkidle")
print(f"Accedido como usuario autenticado: {pagina.title()}")
pagina.screenshot(path="autenticado.png")
navegador.close()
Recurso adicional
Para convertir capturas de pantalla y documentos entre PNG, PDF, WebP y otros formatos sin necesidad de programar, usa KaijuConverter — gratis y sin registro.
Conversiones relacionadas
Conversiones frecuentes del catálogo: