Volver al blog
Guías
Ștefan RăcilăLast updated on Apr 29, 202613 min read

Tutorial de Scrapy Splash: Renderizar páginas JavaScript

Tutorial de Scrapy Splash: Renderizar páginas JavaScript
En resumen: Scrapy Splash combina el rápido motor de rastreo de Scrapy con el navegador sin interfaz gráfica Splash para renderizar páginas con gran cantidad de JavaScript. Este tutorial de Scrapy Splash te guía paso a paso por la instalación de Docker, la configuración del proyecto Scrapy, los conceptos básicos de SplashRequest, los scripts de Lua para desplazarse y hacer clic, la integración de un proxy y la resolución de los errores más comunes que encontrarás.

Scrapy es uno de los marcos de rastreo web más eficientes del ecosistema de Python, pero tiene un punto débil bien conocido: no puede ejecutar JavaScript. Cualquier sitio que cargue datos mediante renderización del lado del cliente, llamadas AJAX o marcos de aplicaciones de página única es invisible para una araña Scrapy estándar. Este es precisamente el problema que resuelve este tutorial de Scrapy Splash.

Scrapy Splash es una capa de integración entre Scrapy y el navegador sin interfaz gráfica Splash. Splash es un servicio de renderizado ligero basado en Qt desarrollado por Zyte (el mismo equipo detrás de Scrapy) que expone una API HTTP. En lugar de ejecutar un navegador de escritorio completo, Splash carga una página en un motor WebKit simplificado, ejecuta el JavaScript y devuelve el HTML completamente renderizado a tu araña. Tus métodos de análisis siguen funcionando con selectores CSS y XPath estándar como si nada hubiera cambiado.

En esta guía, instalarás Docker y Splash desde cero, configurarás tu proyecto de Scrapy, escribirás arañas que rendericen páginas dinámicas, crearás scripts de Lua para interacciones avanzadas, conectarás proxies y solucionarás los errores que suelen tropezar a la mayoría de los principiantes.

¿Qué es Scrapy Splash y cuándo debes utilizarlo?

Splash es un navegador sin interfaz gráfica con una API HTTP que renderiza páginas web cargadas con JavaScript. A diferencia de los navegadores completos, Splash está diseñado para ser ligero: se ejecuta dentro de un contenedor Docker, escucha en un puerto y devuelve HTML renderizado (o capturas de pantalla PNG, o registros HAR) a través de HTTP. Scrapy Splash es la biblioteca oficial que conecta Scrapy con este servicio de renderizado.

Recurre a Scrapy Splash cuando el sitio de destino cargue contenido crítico a través de JavaScript o AJAX y necesites el pipeline integrado, el middleware y las funciones de gestión de rastreo de Scrapy. Zyte creó Splash específicamente para flujos de trabajo de scraping, y se integra en el ciclo de vida de solicitud/respuesta de Scrapy sin problemas. Dicho esto, Splash utiliza un motor WebKit más antiguo, por lo que su compatibilidad con JavaScript no es tan moderna como la de las alternativas basadas en Chromium. Si tu sitio de destino depende de API de navegador de última generación, evalúa herramientas de navegador sin interfaz gráfica con un backend Chromium.

Para una representación sencilla de JS a gran escala (páginas de productos, listados de directorios, resultados paginados), Splash sigue siendo una opción práctica y eficiente en cuanto a recursos.

Requisitos previos y configuración del entorno

Antes de sumergirte en este tutorial de Scrapy Splash, confirma que dispones de lo siguiente:

  • Python 3.7+ instalado en tu sistema
  • Un proyecto de Scrapy (o la intención de crear uno)
  • Docker Desktop (o el motor Docker en Linux) para ejecutar el contenedor de Splash
  • Un terminal para ejecutar los comandos de Docker y la CLI de Scrapy

Esa es la lista completa. La siguiente sección trata sobre la instalación de Docker.

Instalación de Docker y ejecución del contenedor Splash

Splash se ejecuta dentro de un contenedor de Docker, por lo que primero hay que instalar Docker. Descarga Docker Desktop para macOS o Windows, o instala el motor de Docker directamente en Linux. Una vez que Docker esté en funcionamiento, descarga la imagen oficial de Splash:

docker pull scrapinghub/splash

A continuación, inicia el contenedor:

docker run -it -p 8050:8050 --rm scrapinghub/splash

Esto asigna el puerto 8050 del contenedor al puerto 8050 de tu host. La --rm elimina el contenedor cuando lo detienes.

Abre http://localhost:8050/ en tu navegador. Si ves la página interactiva predeterminada de Splash, el servicio está activo. Prueba una URL allí mismo para confirmar que la visualización funciona antes de escribir cualquier código de Scrapy.

Para una configuración de Docker de Splash con Scrapy destinada a producción, ten en cuenta los límites de recursos. El --max-timeout opción te permite aumentar el tiempo de espera predeterminado de 60 segundos (el máximo es de aproximadamente 90 segundos a menos que lo anules, aunque debes verificar el valor exacto en la documentación actual de Splash, ya que los detalles pueden variar). Limita la memoria con la opción --memory para evitar que las páginas descontroladas consuman los recursos de tu host.

Configuración de tu proyecto de Scrapy para Splash

Si aún no tienes un proyecto, crea uno:

scrapy startproject myproject

Instala el complemento scrapy-splash:

pip install scrapy scrapy-splash

Abre settings.py y añade estas entradas:

SPLASH_URL = 'http://localhost:8050'

DOWNLOADER_MIDDLEWARES = {
    'scrapy_splash.SplashCookiesMiddleware': 723,
    'scrapy_splash.SplashMiddleware': 725,
    'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 810,
}

SPIDER_MIDDLEWARES = {
    'scrapy_splash.SplashDeduplicateArgsMiddleware': 100,
}

DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter'

SPLASH_URL indica a scrapy-splash dónde se encuentra el servicio de renderizado. SplashCookiesMiddleware gestiona el reenvío de cookies entre Scrapy y Splash. SplashMiddleware intercepta los objetos SplashRequest y los redirige a través de la API HTTP de Splash. El DUPEFILTER_CLASS garantiza que el filtro de solicitudes duplicadas de Scrapy tenga en cuenta los argumentos específicos de Splash, evitando el filtrado accidental de solicitudes que solo difieren en los parámetros de renderización.

Con esta configuración, tu proyecto estará listo para cualquier araña de tutorial de Scrapy Splash que crees a continuación.

Tu primera araña del tutorial de Scrapy Splash: SplashRequest en acción

Genera un esqueleto de araña:

scrapy genspider quotes_js quotes.toscrape.com

Sustituye el patrón predeterminado start_urls por start_requests, ya que la clase de solicitud predeterminada de Scrapy no pasa por Splash:

import scrapy
from scrapy_splash import SplashRequest

class QuotesJsSpider(scrapy.Spider):
    name = 'quotes_js'

    def start_requests(self):
        yield SplashRequest(
            url='http://quotes.toscrape.com/js/',
            callback=self.parse,
            args={'wait': 2}
        )

    def parse(self, response):
        for quote in response.css('div.quote'):
            yield {
                'text': quote.css('span.text::text').get(),
                'author': quote.css('small.author::text').get(),
            }

La diferencia clave con respecto a una scrapy.Request es que SplashRequest envía primero la URL a Splash. Splash renderiza la página, espera los segundos especificados a que se ejecute JavaScript y devuelve el HTML completamente renderizado. Dentro de parse, trabajas con la respuesta exactamente como lo harías normalmente: selectores CSS, XPath, todo funciona porque las respuestas de Splash incluyen todas las propiedades de respuesta estándar.

Ejecútalo con scrapy crawl quotes_js y deberías ver los datos de la cotización renderizados en tu salida.

Control de la representación de la página con los argumentos de SplashRequest

SplashRequest acepta varios argumentos que controlan cómo Splash renderiza la página:

Argumento

Tipo

Propósito

wait

float

Segundos de espera tras la carga de la página antes de devolver el HTML

timeout

float

Tiempo máximo de renderizado (segundos). Por defecto 60, con un límite de aproximadamente 90 a menos que se anule

images

int (0/1)

Establecer en 0 para desactivar la carga de imágenes, acelerando así el renderizado

resource_timeout

float

Tiempo de espera por recurso individual (CSS, archivo JS, imagen)

http_method

cadena

Usar POST para el envío de formularios

body

cadena

Contenido del cuerpo POST, junto con http_method='POST'

Por ejemplo, para enviar una solicitud POST (útil para el envío de formularios):

yield SplashRequest(
    url='https://example.com/search',
    args={
        'wait': 1,
        'http_method': 'POST',
        'body': 'query=scrapy+splash',
    },
    callback=self.parse_results,
)

El http_method y body son útiles para sitios que procesan formularios de búsqueda o acciones de inicio de sesión del lado del servidor. Esto cubre los conceptos básicos de la representación de JavaScript de Scrapy, pero para la interacción con la página (hacer clic, desplazarse, esperar a elementos dinámicos), necesitas scripts de Lua.

Escribir scripts de Lua para interacciones avanzadas

El render.html punto final gestiona casos sencillos, pero en cuanto necesites interactuar con una página, pasarás al execute punto final con un script de Lua. Un script de Lua de Scrapy Splash te ofrece un control paso a paso sobre el navegador:

function main(splash, args)
  splash:go(args.url)
  splash:wait(1)
  return splash:html()
end

Envíalo a través de SplashRequest utilizando endpoint='execute' y pasa el script en args={'lua_source': script}. A partir de aquí, añade esperas de elementos, bucles de desplazamiento y acciones de clic.

Esperar a que se carguen elementos específicos

Un wait funciona cuando conoces el tiempo de carga de la página, pero es frágil. En su lugar, comprueba si existe un elemento DOM específico:

function main(splash, args)
  splash:go(args.url)
  while not splash:select('.target-element') do
    splash:wait(0.5)
  end
  splash:wait(0.5)
  return splash:html()
end

Este script se ejecuta en bucle hasta que splash:select() encuentre un elemento que coincida .target-element, esperando medio segundo entre cada intento. Una vez que aparece el elemento, una breve espera final se encarga del renderizado restante. Este patrón es mucho más fiable que estimar un retraso estático.

Desplazamiento por páginas de desplazamiento infinito

Splash no tiene comandos de desplazamiento integrados. En su lugar, inyecta JavaScript para manipular la posición de desplazamiento. Aquí hay un script de Lua para el desplazamiento infinito de Splash en Scrapy:

function main(splash, args)
  splash:go(args.url)
  splash:wait(2)
  local scroll_count = 5
  for i = 1, scroll_count do
    splash:runjs("window.scrollTo(0, document.body.scrollHeight)")
    splash:wait(2)
  end
  return splash:html()
end

El script se desplaza hasta el final, espera a que aparezca contenido nuevo y repite el proceso. Ajusta scroll_count y la duración de la espera para que se adapte al sitio. Compara document.body.scrollHeight antes y después de cada desplazamiento para detectar cuándo no aparece contenido nuevo.

Hacer clic en botones y navegar por las páginas

Los botones «Cargar más» y los enlaces de paginación requieren interacción con el ratón. Usa splash:select() para encontrar el elemento y simular un clic:

function main(splash, args)
  splash:go(args.url)
  splash:wait(2)
  local btn = splash:select('.load-more-btn')
  if btn then
    btn:mouse_click()
    splash:wait(2)
  end
  return splash:html()
end

Envuelve esto en un bucle para páginas con múltiples desencadenantes. Para la paginación, selecciona el enlace «Siguiente», haz clic en él, espera a que aparezca la nueva página y recopila el HTML en cada paso.

Ejecutar JavaScript personalizado dentro de Splash

A veces no necesitas un flujo de trabajo completo de Lua. Splash te permite inyectar JavaScript arbitrario con dos métodos: splash:evaljs() (devuelve un valor) y splash:runjs() (se ejecuta sin devolver ningún valor).

function main(splash, args)
  splash:go(args.url)
  splash:wait(1)
  local title = splash:evaljs("document.title")
  splash:runjs("document.querySelector('.popup-close').click()")
  splash:wait(0.5)
  return {html = splash:html(), title = title}
end

Esto resulta útil para descartar banners de cookies, cerrar ventanas modales o extraer un valor calculado antes de obtener el HTML de la página. También puedes pasar JavaScript a través del js_source parámetro en una SplashRequest estándar (no se requiere Lua), que ejecuta el JS después de que se cargue la página pero antes de que se capture la instantánea HTML.

Uso de proxies con Scrapy Splash

Rotar tu dirección IP ayuda a evitar bloqueos a cualquier volumen de rastreo significativo. Dirige las solicitudes a través de un proxy de Scrapy Splash pasando los detalles en los argumentos de SplashRequest:

yield SplashRequest(
    url='https://example.com',
    callback=self.parse,
    args={
        'wait': 2,
        'proxy': 'http://user:pass@proxyhost:port',
    },
)

También puedes configurar el proxy dentro de un script de Lua utilizando splash:on_request():

function main(splash, args)
  splash:on_request(function(request)
    request:set_proxy{
      host = "proxyhost",
      port = 8080,
      username = "user",
      password = "pass",
    }
  end)
  splash:go(args.url)
  splash:wait(2)
  return splash:html()
end

El enfoque de Lua te permite aplicar diferentes proxies a diferentes sub-solicitudes dentro de la misma carga de página. Ten en cuenta que Splash en sí mismo no elude los sistemas anti-bot; solo renderiza la página. Aún necesitas proxies residenciales o de centros de datos rotados adecuadamente para evitar bloqueos a nivel de IP.

Errores comunes y resolución de problemas

Aquí es donde la mayoría de las guías tutoriales de Scrapy Splash te dejan en la estacada. Estos son los errores con los que te encontrarás más a menudo:

Conexión rechazada en localhost:8050. El contenedor Docker de Splash no se está ejecutando. Compruébalo con docker ps. Si está en ejecución pero no es accesible, comprueba que el puerto 8050 no esté bloqueado por tu firewall u ocupado por otro proceso.

504 Tiempo de espera de la puerta de enlace agotado. La página tardó más en cargarse que el tiempo de espera permitido. Aumenta el timeout en tu SplashRequest. El límite predeterminado es de aproximadamente 90 segundos. Para renderizaciones más largas, inicia el contenedor con un valor más alto --max-timeout (consulte la documentación actual de Splash, ya que los detalles pueden variar entre versiones).

Errores de script de Lua («argumento incorrecto», «intento de indexar un valor nulo»). Estos suelen significar splash:select() devuelto nil porque el elemento aún no estaba en el DOM. Añade una espera o un bucle de sondeo antes de interactuar con él.

Contenedor de Docker eliminado (OOM). Splash puede consumir una cantidad significativa de memoria en páginas pesadas. Establece los límites de memoria de Docker con --memory 2g y desactive la carga de imágenes (images=0). Para múltiples instancias, utiliza Docker Compose con restricciones de recursos por contenedor.

Se ha devuelto un HTML en blanco o incompleto. Es posible que el JavaScript de la página necesite más tiempo. Aumenta wait. Si los recursos de terceros son lentos, configura resource_timeout para omitirlos.

Scrapy Splash vs Scrapy-Playwright vs Selenium

La elección de la herramienta de renderizado adecuada depende de tu proyecto. A continuación se comparan las tres opciones más comunes en el panorama de alternativas a Scrapy Splash:

Característica

Scrapy Splash

Scrapy-Playwright

Selenium

Motor del navegador

WebKit (basado en Qt)

Chromium, Firefox, WebKit

Chrome, Firefox, Edge

Integración con Scrapy

Nativa (scrapy-splash)

Nativa (scrapy-playwright)

Requiere middleware personalizado

Compatibilidad asíncrona

Limitado (API HTTP)

Asíncrono completo (basado en Playwright)

Sincronización por defecto

Uso de recursos

Bajo a moderado

Moderado

Alto

Compatibilidad con JS moderno

Parcial (WebKit antiguo)

Completo (Chromium)

Total

Evitar el bypass de bots

Sin función integrada

Ninguno integrado

Sin función integrada

Ideal para

Renderización JS ligera a gran escala

SPA complejas, sitios web modernos en JS

Proyectos que no sean de Scrapy, pruebas

Splash es la elección adecuada cuando se busca una sobrecarga mínima y las páginas de destino no dependen de API de navegador de última generación. Para aplicaciones modernas de página única, Scrapy-Playwright, con su backend Chromium, probablemente sea una mejor opción. Selenium funciona, pero carece de integración nativa con Scrapy. Ninguna de estas herramientas gestiona la protección antibots por sí sola, por lo que seguirás necesitando una capa de proxy para el scraping en producción. Utiliza este tutorial de Scrapy Splash como base y explora alternativas cuando el proyecto lo requiera.

Puntos clave

  • Splash se ejecuta en Docker y se conecta a Scrapy a través de una API HTTP. Una vez que el contenedor está en el puerto 8050 y settings.py esté configurado, tus arañas pueden renderizar páginas JavaScript con una sola llamada a SplashRequest.
  • Utiliza scripts de Lua cuando necesites interacción. Las esperas fijas cubren los casos sencillos, pero el sondeo de elementos, los bucles de desplazamiento y las acciones de clic requieren el execute un punto final con un script de Lua.
  • Los proxies son esenciales para el scraping en producción. Splash renderiza páginas, pero no elude las protecciones antibots. Dirige las solicitudes a través de proxies rotativos utilizando los argumentos de SplashRequest o splash:on_request() en Lua.
  • Splash es ligero, pero está quedando obsoleto. Se integra perfectamente con Scrapy, pero su motor WebKit carece de compatibilidad con algunas API modernas de JavaScript. Evalúa Scrapy-Playwright para sitios que necesiten un backend de Chromium.
  • Solucione los problemas de forma sistemática. La mayoría de los problemas de Splash se reducen a tiempos de espera agotados, elementos que faltan o límites de recursos de Docker.

Preguntas frecuentes

¿Puede Scrapy Splash gestionar aplicaciones de página única creadas con React o Vue?

Puede renderizar muchas aplicaciones de React y Vue, pero los resultados dependen de las API de JavaScript que utilice la aplicación. Splash se ejecuta en un motor WebKit antiguo, por lo que es posible que las aplicaciones que dependan de funciones modernas del navegador (como IntersectionObserver o la sintaxis ES2020+) no se rendericen correctamente. Prueba tu URL de destino en la interfaz web de Splash en localhost:8050 antes de crear un rastreador completo.

¿Cuánta memoria necesita un contenedor Docker de Splash para el scraping en producción?

Prevé al menos entre 1 y 2 GB por instancia para cargas de trabajo típicas. Las páginas con imágenes pesadas o JavaScript complejo pueden aumentar el consumo de memoria. Desactiva la carga de imágenes con images=0 para reducir el consumo, y configura la bandera --memory para evitar que un solo contenedor agote los recursos del host.

¿Se sigue manteniendo Scrapy Splash y cuáles son las alternativas activas?

Splash recibe actualizaciones esporádicas y ya no se desarrollan nuevas funciones de forma activa. Sigue funcionando para muchos casos de uso, pero la comunidad se ha decantado en gran medida por Scrapy-Playwright para los nuevos proyectos. Selenium sigue siendo una opción fuera del ecosistema de Scrapy. Cada herramienta tiene sus pros y sus contras en cuanto a compatibilidad con motores de navegador, capacidades asíncronas y uso de recursos.

¿Cómo se pasan las cookies o los encabezados personalizados a través de SplashRequest?

Incluye una cookies clave en el args , o configura los encabezados utilizando el headers . En un script de Lua, utiliza splash:set_custom_headers() antes de llamar a splash:go(). Las cookies del almacén de cookies de Scrapy se reenvían automáticamente cuando SplashCookiesMiddleware está habilitado en tu configuración.

Conclusión

Este tutorial de Scrapy Splash te ha guiado a través del flujo de trabajo completo: poner en marcha un contenedor Splash, configurar tu proyecto de Scrapy, escribir arañas con SplashRequest, crear scripts de Lua para desplazarse y hacer clic, y conectar proxies. Los patrones de resolución de problemas tratados aquí deberían ahorrarte horas de depuración.

Splash gestiona bien las tareas de renderizado sencillas, pero a estas alturas es una tecnología anticuada. Si tus objetivos superan los límites del JavaScript moderno, evalúa alternativas basadas en Chromium. Independientemente de la herramienta de renderizado que elijas, el verdadero cuello de botella en el scraping de producción rara vez es el navegador; lo es superar las defensas antibots y gestionar la infraestructura de proxies a gran escala.

Si prefieres evitar por completo los quebraderos de cabeza relacionados con la infraestructura, WebScrapingAPI se encarga de la rotación de proxies, la resolución de CAPTCHA y el renderizado de JavaScript a través de un único punto de acceso de API, para que puedas centrarte en analizar los datos en lugar de lidiar con la infraestructura.

Acerca del autor
Ștefan Răcilă, Desarrollador Full Stack @ WebScrapingAPI
Ștefan RăcilăDesarrollador Full Stack

Stefan Racila es ingeniero de DevOps y Full Stack en WebScrapingAPI, donde se encarga de desarrollar funciones para los productos y de mantener la infraestructura que garantiza la fiabilidad de la plataforma.

Empieza a crear

¿Estás listo para ampliar tu recopilación de datos?

Únete a más de 2000 empresas que utilizan WebScrapingAPI para extraer datos de la web a escala empresarial sin ningún gasto de infraestructura.