En esta guía sobre Scrapy Splash, aprenderás:
- Qué es Scrapy Splash
- Cómo utilizar Scrapy Splash en Python en un tutorial paso a paso
- Técnicas avanzadas de scraping con Splash en Scrapy
- Limitaciones del raspado de sitios web con esta herramienta
Sumerjámonos.
¿Qué es Scrapy Splash?
Scrapy Splash se refiere a la integración entre estas dos herramientas:
- Scrapy: Una biblioteca marco de rastreo de código abierto de Python para extraer los datos que necesita de los sitios web.
- Splash: Navegador ligero sin cabezal diseñado para renderizar páginas web con mucho JavaScript.
Puede que te estés preguntando por qué una herramienta tan potente como Scrapy necesita Splash. Bueno, Scrapy sólo puede manejar sitios estáticos, ya que se basa en las capacidades de análisis de HTML (en concreto, de Parsel). Sin embargo, cuando se raspan sitios web dinámicos, es necesario lidiar con el renderizado de JavaScript. Una solución común es utilizar un navegador automatizado, que es exactamente lo que Splash proporciona.
Con Scrapy Splash, puedes enviar una petición especial -conocida como SplashRequest- a
un servidor Splash. Este servidor renderiza completamente la página ejecutando JavaScript y devuelve el HTML procesado. Por lo tanto, permite a su Scrapy Spider recuperar datos de páginas dinámicas.
En resumen, necesitas Scrapy Splash si:
- Usted está trabajando con sitios web de JavaScript-pesado que Scrapy solo no puede raspar.
- Prefiere una solución ligera en comparación con Selenium o Playwright.
- Quiere evitar la sobrecarga de ejecutar un navegador completo para el scraping.
Si Scrapy Splash no satisface sus necesidades, considere estas alternativas:
- Selenium: Una capacidad de automatización del navegador en toda regla para el raspado de sitios web con mucho JavaScript, que proporciona extensiones interesantes como Selenium Wire.
- Playwright: Una herramienta de automatización de navegadores de código abierto que proporciona una automatización consistente entre navegadores y una API robusta, compatible con múltiples lenguajes de programación.
- Puppeteer: Una biblioteca Node.js de código abierto desarrollada que proporciona una API de alto nivel para automatizar y controlar Chrome a través del protocolo DevTools.
Scrapy Splash en Python: Un tutorial paso a paso
En esta sección, entenderás cómo utilizar Scrapy Splash para recuperar datos de una página web. La página de destino será una versión especial renderizada con JavaScript del popular sitio “Quotes to Scrape“:
Es como el habitual “Quotes to Scrape”, pero utiliza el desplazamiento infinito para cargar datos dinámicamente mediante peticiones AJAX activadas por JavaScript.
Requisitos
Para replicar este tutorial usando Scrapy Splash en Python, tu sistema debe cumplir los siguientes requisitos:
- Python 3.10.1 o superior.
- Docker 27.5.1 o superior.
Si no tiene esas dos herramientas instaladas en su máquina, siga los enlaces anteriores.
Requisitos previos, dependencias e integración de Splash
Supongamos que llamas a la carpeta principal de tu proyecto scrapy_splash/
. Al final de este paso, la carpeta tendrá la siguiente estructura:
scrapy_splash/
└── venv/
Donde venv/
contiene el entorno virtual. Puede crear el directorio del entorno virtual venv/
de la siguiente manera:
python -m venv venv
Para activarlo, en Windows, ejecute
venv\Scripts\activate
De forma equivalente, en macOS y Linux, ejecute:
source venv/bin/activate
En el entorno virtual activado, instale las dependencias con:
pip install scrapy scrapy-splash
Como último prerrequisito, necesitas extraer la imagen Splash a través de Docker:
docker pull scrapinghub/splash
A continuación, ponga en marcha el contenedor:
docker run -it -p 8050:8050 --rm scrapinghub/splash
Para obtener más información, siga las instrucciones de integración de Docker basado en SO.
Después de iniciar el contenedor Docker, espere hasta que el servicio Splash registre el siguiente mensaje:
Server listening on http://0.0.0.0:8050
El mensaje indica que Splash ya está disponible en http://0.0.0.0:8050.
Visite esa URL en su navegador y debería ver la siguiente página:
Dependiendo de su configuración, es posible que al seguir la URL http://0.0.0.0:8050
el servicio Splash no funcione. En este caso, intente utilizar uno de los siguientes en su lugar:
http://localhost:8050
http://127.0.0.1:8050
Nota: Recuerde que la conexión con el servidor Splash debe permanecer abierta mientras utiliza Scrapy-Splash. En otras palabras, si utilizó la CLI para ejecutar el contenedor Docker, mantenga ese terminal abierto y utilice un terminal distinto para los siguientes pasos de este procedimiento.
¡Maravilloso! Ya tienes lo que necesitas para scrapear páginas web con Scrapy Splash.
Paso 1: Iniciar un nuevo proyecto Scrapy
Dentro de la carpeta principal scrapy_splash/
, escriba el siguiente comando para iniciar un nuevo proyecto Scrapy:
scrapy startproject quotes
Con ese comando, Scrapy creará una carpeta quotes/
. Dentro de ella, generará automáticamente todos los archivos que necesites. Esta es la estructura de carpetas resultante:
scrapy_splash/
├── quotes/
│ ├── quotes/
│ │ ├── spiders/
│ │ ├── __init__.py
│ │ ├── items.py
│ │ ├── middlewares.py
│ │ ├── pipelines.py
│ │ └── settings.py
│ │
│ └── scrapy.cfg
└── venv/
Perfecto. Has empezado un nuevo proyecto Scrapy.
Paso nº 2: Generar la araña
Para generar una nueva araña que rastree el sitio web de destino, vaya a la carpeta quotes/
:
cd quotes
A continuación, genere una nueva araña con:
scrapy genspider words https://quotes.toscrape.com/scroll
Obtendrá el siguiente resultado:
Created spider 'words' using template 'basic' in module:
quotes.spiders.words
Como puedes ver, Scrapy ha creado automáticamente un archivo words.py
dentro de la carpeta spiders/
. El archivo words.
py contiene el siguiente código:
import scrapy
class WordsSpider(scrapy.Spider):
name = "words"
allowed_domains = ["quotes.toscrape.com"]
start_urls = ["https://quotes.toscrape.com/scroll"]
def parse(self, response):
pass
Pronto contendrá la lógica de raspado necesaria de la página de destino dinámica.
¡Hurra! Has generado la araña para raspar el sitio web de destino.
Paso #3: Configurar Scrapy para usar Splash
Ahora tienes que configurar Scrapy para que pueda utilizar el servicio Splash. Para ello, añade las siguientes configuraciones al archivo settings.py
:
# Set the Splash local server endpoint
SPLASH_URL = "http://localhost:8050"
# Enable the Splash downloader middleware
DOWNLOADER_MIDDLEWARES = {
"scrapy_splash.SplashCookiesMiddleware": 723,
"scrapy_splash.SplashMiddleware": 725,
"scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware": 810,
}
# Enable the Splash deduplication argument filter
SPIDER_MIDDLEWARES = {
"scrapy_splash.SplashDeduplicateArgsMiddleware": 100,
}
En las configuraciones anteriores:
SPLASH_URL
establece el punto final para el servidor Splash local. Aquí es donde Scrapy enviará las solicitudes de representación de JavaScript.DOWNLOADER_MIDDLEWARES
permite a determinados middlewares interactuar con Splash. En particular:Polylang placeholder do not modify
SPIDER_MIDDLEWARES
garantiza que las peticiones con los mismos argumentos Splash no se dupliquen, lo que es útil para reducir la carga innecesaria y mejorar la eficiencia.
Para obtener información más detallada sobre estas configuraciones, consulte la documentación oficial de Scrapy-Splash.
Bien. Ahora Scrapy puede conectarse a Splash y utilizarlo programáticamente para el renderizado de JavaScript.
Paso 4: Definir el script Lua para la renderización JavaScript
Scrapy puede ahora integrarse con Splash para renderizar páginas web que dependen de JavaScript, como la página de destino de esta guía. Para definir una lógica de renderizado e interacción personalizada, es necesario utilizar scripts Lua. Esto se debe a que Splash se basa en scripts Lua para interactuar con las páginas web a través de JavaScript y controlar el comportamiento del navegador mediante programación.
Específicamente, añade el script Lua de abajo a words.py
:
script = """
function main(splash, args)
splash:go(args.url)
-- custom rendering script logic...
return splash:html()
end
"""
En el fragmento anterior, la variable script
contiene la lógica Lua que Splash ejecutará en el servidor. En particular, este script ordena a Splash que:
- Navega a la URL definida con el método
splash:go()
. - Devuelve el contenido HTML renderizado con el método
splash:html()
.
Utilice el script Lua anterior en una función start_requests()
dentro de la clase WordsSpider
:
def start_requests(self):
for url in self.start_urls:
yield SplashRequest(
url,
self.parse,
endpoint="execute",
args={"lua_source": script}
)
El método start_requests(
) anula el método start_requests()
por defecto de Scrapy. De esta forma, Scrapy Splash puede ejecutar el script Lua para recuperar el HTML renderizado en JavaScript de la página. La ejecución del script Lua se produce a través del argumento "lua_source": script
en el método SplashRequest()
. Observe también el uso del endpoint Splash "execute"
(que aprenderá más pronto).
No olvides importar SplashRequest
desde Scrapy Splash:
from scrapy_splash import SplashRequest
Tu archivo words.py
ya está equipado con el script Lua correcto para acceder al contenido renderizado en JavaScript de la página.
Paso 5: Definir la lógica de análisis de datos
Antes de empezar, inspeccione un elemento HTML de cita en la página de destino para saber cómo analizarlo:
Allí se puede ver que los elementos de la cita se pueden seleccionar con .quote
. Dada una cita, a continuación, puede obtener:
- El texto de la cita de
.text
. - El autor de la cita de
.author
. - Las etiquetas de cita de
.tags
.
La lógica de raspado para recuperar todas las citas de la página de destino puede definirse mediante el siguiente método parse()
:
def parse(self, response):
# Retrieve CSS selectors
quotes = response.css(".quote")
for quote in quotes:
yield {
"text": quote.css(".text::text").get(),
"author": quote.css(".author::text").get(),
"tags": quote.css(".tags a.tag::text").getall()
}
parse()
procesa la respuesta devuelta por Splash. En detalle:
- Extrae todos los elementos
div
con la clasequote
utilizando el selector CSS".quote"
. - Recorre cada elemento de
la
cita para extraer el nombre, el autor y la etiqueta de cada cita.
Muy bien. La lógica de raspado de Scrapy Splash está completa.
Paso 6: Ponerlo todo junto y ejecutar el guión
Este es el aspecto que debe tener el archivo words.py
:
import scrapy
from scrapy_splash import SplashRequest
# Lua script for JavaScript rendering
script = """
function main(splash, args)
splash:go(args.url)
return splash:html()
end
"""
class WordsSpider(scrapy.Spider):
name = "words"
start_urls = ["https://quotes.toscrape.com/scroll"]
def start_requests(self):
for url in self.start_urls:
yield SplashRequest(
url,
self.parse,
endpoint="execute",
args={"lua_source": script}
)
def parse(self, response):
quotes = response.css(".quote")
for quote in quotes:
yield {
"text": quote.css(".text::text").get(),
"author": quote.css(".author::text").get(),
"tags": quote.css(".tags a.tag::text").getall()
}
Ejecute el script con este comando:
scrapy crawl words
Este es el resultado esperado:
El resultado deseado puede visualizarse mejor así:
2025-03-18 12:21:55 [scrapy.core.scraper] DEBUG: Scraped from <200 https://quotes.toscrape.com/scroll>
{'text': '“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”', 'author': 'Albert Einstein', 'tags': ['change', 'deep-thoughts', 'thinking', 'world']}
2025-03-18 12:21:55 [scrapy.core.scraper] DEBUG: Scraped from <200 https://quotes.toscrape.com/scroll>
{'text': '“It is our choices, Harry, that show what we truly are, far more than our abilities.”', 'author': 'J.K. Rowling', 'tags': ['abilities', 'choices']}
# omitted for brevity...
2025-03-18 12:21:55 [scrapy.core.engine] INFO: Closing spider (finished)
Observe que la salida contiene los datos de interés.
Ten en cuenta que si eliminas el método start_requests()
de la clase Words``Spider
, Scrapy no devolverá ningún dato. Esto se debe a que, sin Splash, no puede renderizar páginas que requieran JavaScript.
Muy bien. Has completado tu primer proyecto Scrapy Splash.
Nota sobre las salpicaduras
Splash es un servidor que se comunica a través de HTTP. Esto te permite scrapear páginas web con Splash usando cualquier cliente HTTP, llamando a sus endpoints. Los endpoints que proporciona son:
ejecutar
: Ejecuta un script de renderizado Lua personalizado y devuelve su resultado.render.html
: Devuelve el HTML de la página renderizada con javascript.render.png
: Devuelve una imagen (en formato PNG) de la página renderizada con javascript.render.jpeg
: Devuelve una imagen (en formato JPEG) de la página renderizada con javascript.render.har
: Devuelve información sobre la interacción de Splash con un sitio web en formato HAR.render.json
: Devuelve un diccionario codificado en JSON con información sobre la página web renderizada con javascript. Puede incluir información HTML, PNG y de otro tipo, en función de los argumentos pasados.
Para entender mejor cómo funcionan estos puntos finales, considere el punto final render.html
. Conéctate al endpoint con este código Python:
# pip install requests
import requests
import json
# URL of the Splash endpoint
url = "http://localhost:8050/render.html"
# Sending a POST request to the Splash endpoint
payload = json.dumps({
"url": "https://quotes.toscrape.com/scroll" # URL of the page to render
})
headers = {
"content-type": "application/json"
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
Este fragmento define:
- La instancia de Splash en localhost como la URL que hace una llamada al punto final
render.html
. - La página de destino para raspar dentro de la
carga útil
.
Ejecute el código anterior y obtendrá el HTML renderizado de toda la página:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Quotes to Scrape</title>
<link rel="stylesheet" href="/static/bootstrap.min.css">
<link rel="stylesheet" href="/static/main.css">
</head>
<body>
<!-- omitted for brevity... -->
</body>
</html>
Aunque Splash puede manejar HTML renderizado en JavaScript de forma independiente, el uso de Scrapy Splash con SplashRequest
hace que el web scraping sea mucho más fácil.
Scrapy Splash: Técnicas avanzadas de scraping
En el párrafo anterior, has completado un tutorial básico de Scrapy con integración de Splash. ¡Es hora de probar algunas técnicas avanzadas de scraping con Scrapy Splash!
Gestión del desplazamiento avanzado
La página de destino contiene citas que se cargan dinámicamente mediante AJAX gracias al desplazamiento infinito:
Para gestionar la interacción con el desplazamiento infinito, es necesario cambiar el script Lua de la siguiente manera:
script = """
function main(splash, args)
local scroll_delay = 1.0 -- Time to wait between scrolls
local max_scrolls = 10 -- Maximum number of scrolls to perform
local scroll_to = 1000 -- Pixels to scroll down each time
splash:go(args.url)
splash:wait(scroll_delay)
local scroll_count = 0
while scroll_count < max_scrolls do
scroll_count = scroll_count + 1
splash:runjs("window.scrollBy(0, " .. scroll_to .. ");")
splash:wait(scroll_delay)
end
return splash:html()
end
"""
Este script modificado se basa en estas variables:
max_scrolls
define el número máximo de desplazamientos a realizar. Puede que sea necesario modificar este valor en función de la cantidad de contenido que se desee extraer de la página.scroll_to
especifica el número de píxeles a desplazar hacia abajo cada vez. Puede ser necesario ajustar su valor en función del comportamiento de la página.splash:runjs()
ejecuta la función JavaScriptwindow.scrollBy()
para desplazar la página hacia abajo el número de píxeles especificado.splash:wait()
asegura que el script espere antes de cargar nuevo contenido. La cantidad de tiempo (en segundos) a esperar se define mediante la variablescroll_delay
.
En términos más sencillos, el script Lua anterior simula un número definido de desplazamientos en un escenario de página web de desplazamiento infinito.
El código del archivo words.py
tendrá el siguiente aspecto:
import scrapy
from scrapy_splash import SplashRequest
# Lua script for infinite scrolling
script = """
function main(splash, args)
local scroll_delay = 1.0 -- Time to wait between scrolls
local max_scrolls = 10 -- Maximum number of scrolls to perform
local scroll_to = 1000 -- Pixels to scroll down each time
splash:go(args.url)
splash:wait(scroll_delay)
local scroll_count = 0
while scroll_count < max_scrolls do
scroll_count = scroll_count + 1
splash:runjs("window.scrollBy(0, " .. scroll_to .. ");")
splash:wait(scroll_delay)
end
return splash:html()
end
"""
class WordsSpider(scrapy.Spider):
name = "words"
start_urls = ["https://quotes.toscrape.com/scroll"]
def start_requests(self):
for url in self.start_urls:
yield SplashRequest(
url,
self.parse,
endpoint="execute",
args={"lua_source": script}
)
def parse(self, response):
# Retrieve CSS selectors
quotes = response.css("div.quote")
for quote in quotes:
yield {
"text": quote.css("span.text::text").get(),
"author": quote.css("span small.author::text").get(),
"tags": quote.css("div.tags a.tag::text").getall()
}
Ejecute el script utilizando el comando que se indica a continuación:
scrapy crawl words
El rastreador imprimirá todas las citas raspadas de forma coherente con la variable max_scrolls
. Este es el resultado esperado:
Observe que la salida incluye ahora muchas más citas que antes. Esto confirma que las páginas se han desplazado hacia abajo con éxito y que se han cargado y raspado nuevos datos.
Perfecto. Ahora ha aprendido a gestionar el scroll infinito con Scrapy Splash.
Esperar al elemento
Las páginas web pueden recuperar datos dinámicamente o renderizar nodos en el navegador. Esto significa que el DOM final puede tardar en renderizarse. Para evitar errores al recuperar datos de un sitio web, siempre debes esperar a que un elemento se cargue en la página antes de interactuar con él.
En este ejemplo, el elemento a esperar será el texto de la primera cita:
Para implementar la lógica de espera, escribe un script Lua como el siguiente:
script = """
function main(splash, args)
splash:go(args.url)
while not splash:select(".text") do
splash:wait(0.2)
print("waiting...")
end
return { html=splash:html() }
end
"""
Este script crea un bucle while
que espera 0.2 segundos si el elemento de texto
está en la página. Para verificar si el elemento .text
está en la página, puede utilizar el método splash:select()
.
Esperar el momento
Dado que las páginas web de contenido dinámico tardan en cargarse y renderizarse, puedes esperar unos segundos antes de acceder al contenido HTML. Esto se puede lograr a través del método splash:wait()
así:
script = """
function main(splash, args)
splash:wait(args.wait)
splash:go(args.url)
return { html=splash:html() }
end
"""
En este caso, los segundos que debe esperar el script se expresan en el método SplashRequest()
con un argumento de script Lua.
Por ejemplo, configure"wait" : 2.0
para indicar al script Lua que espere 2 segundos:
import scrapy
from scrapy_splash import SplashRequest
script = """
function main(splash, args)
splash:wait(args.wait)
splash:go(args.url)
return { html=splash:html() }
end
"""
class WordsSpider(scrapy.Spider):
name = "words"
start_urls = ["https://quotes.toscrape.com/scroll"]
def start_requests(self):
for url in self.start_urls:
yield SplashRequest(
url,
self.parse,
endpoint="execute",
args={"lua_source": script, "wait": 2.0} # Waiting for 2 seconds
)
# ...
Nota: Una espera dura(splash:wait()
) es útil para pruebas locales ya que asegura que la página se carga antes de proceder. Este enfoque no es ideal para producción ya que añade retrasos innecesarios, perjudicando el rendimiento y la escalabilidad. Además, no se puede saber de antemano la cantidad correcta de tiempo que se debe esperar.
Bien hecho. Has aprendido a esperar cierto tiempo en Scrapy Splash.
Limitaciones del uso de Scrapy Splash
En este tutorial, has aprendido a utilizar Scrapy Splash para extraer datos de la Web en diferentes escenarios. Aunque esta integración es sencilla, tiene algunos inconvenientes.
Por ejemplo, configurar Splash requiere ejecutar un servidor Splash separado con Docker, lo que añade complejidad a su infraestructura de scraping. Además, la API de scripting Lua de Splash es algo limitada en comparación con herramientas más modernas como Puppeteer y Playwright.
Sin embargo, como ocurre con todos los navegadores headless, la mayor limitación procede del propio navegador. Las tecnologías anti-scraping pueden detectar cuando un navegador se está automatizando en lugar de utilizarse normalmente, lo que provoca bloqueos de scripts.
Olvídese de estos retos con Scraping Browser, unnavegador de scraping basado en la nube diseñado para una escalabilidad infinita. Incluye resolución de CAPTCHA, gestión de huellas dactilares del navegador y anulación de robots, para que no tengas que preocuparte de que te bloqueen.
Conclusión
En este artículo has aprendido qué es Scrapy Splash y cómo funciona. Empezaste con lo básico y luego exploraste escenarios de scraping más complejos.
También ha descubierto las limitaciones de la herramienta, en particular su vulnerabilidad frente a los sistemas anti-bot y anti-scraping. Para superar estos retos, Scraping Browser es una solución excelente. Esta es solo una de las muchas soluciones de raspado de Bright Data que puede probar:
- Servicios proxy: Cuatro tipos diferentes de proxies para eludir las restricciones de ubicación, incluidos más de 150 millones de IP residenciales.
- API de Web Scraper: Puntos finales dedicados para extraer datos web frescos y estructurados de más de 100 dominios populares.
- API SERP: API para manejar toda la gestión de desbloqueo en curso para SERP y extraer una página.
Regístrese ahora en Bright Data y comience su prueba gratuita para probar nuestras soluciones de scraping.
No se requiere tarjeta de crédito