Saltar al contenido
Volver al blog

JavaScript con Scrapy: primero los datos, renderizar solo cuando sea necesario

Mihai MaximÚltima actualización el 13 min read
JavaScript con Scrapy: primero los datos, renderizar solo cuando sea necesario
En resumen: el uso de JavaScript con Scrapy no siempre requiere un navegador. En primer lugar, examina la respuesta sin procesar, las solicitudes de red y el estado de la página incrustada; añade «scrapy-playwright» solo cuando los datos de interés dependan realmente de la ejecución en el navegador y, a continuación, controla de forma deliberada las esperas, las interacciones, la limpieza y la concurrencia.

Utilizar JavaScript con Scrapy significa añadir la ejecución en el navegador para aquellas páginas cuyos datos necesarios solo aparecen tras la ejecución del código del lado del cliente. El objetivo práctico no es renderizar todas las páginas, sino identificar la ruta más económica y fiable hacia los datos: HTML sin procesar, una solicitud JSON, un objeto JavaScript incrustado o, solo cuando sea necesario, un DOM creado por el navegador.

Esa distinción es importante porque el descargador de Scrapy recibe la respuesta del servidor, mientras que Chrome o Firefox pueden realizar solicitudes adicionales y modificar el documento posteriormente. Por lo tanto, una página de React, Vue o Angular puede parecer completa en DevTools aunque response.text apenas contenga algo más que una estructura básica de la aplicación.

Esta guía comienza con un diagnóstico que deja el navegador para el final y, a continuación, crea un flujo de trabajo específico con Scrapy y Playwright para las solicitudes que realmente necesitan ejecutarse. También verás cómo utilizar esperas basadas en condiciones, gestionar clics y el estado de la sesión, comparar opciones de renderizado y mantener la estabilidad de los rastreadores dinámicos en producción. El resultado es un flujo de trabajo que conserva el eficiente canal HTTP de Scrapy siempre que sea posible, sin pretender que todos los sitios web modernos puedan rastrearse únicamente a partir del marcado estático.

Busca la fuente de datos antes de la representación

Antes de intentar ejecutar JavaScript con Scrapy, comprueba lo que el servidor ya ha devuelto. La guía oficial de Scrapy sobre contenido dinámico recomienda localizar primero la fuente subyacente. Sigue este orden: respuesta sin procesar, solicitud de red, estado incrustado y, por último, un navegador sin interfaz gráfica.

Compara la respuesta de Scrapy con el DOM renderizado

Guarda o busca response.text un valor distintivo que puedas ver en el navegador, como un ID de producto en lugar de un precio formateado. A continuación, compáralo con el panel «Elementos» de DevTools. Si el valor solo existe en el DOM renderizado, es probable que lo haya insertado JavaScript, pero eso aún no demuestra que sea necesario el renderizado. La aplicación podría haber obtenido el mismo valor de un punto final accesible.

Una guía más amplia sobre el scraping web con Scrapy resulta una referencia interna útil en este caso, especialmente para la inspección de respuestas, los selectores y la depuración de solicitudes.

Reproduce la solicitud XHR o fetch

Abre DevTools, selecciona «Red», recarga la página y filtra por «Fetch/XHR». Inspecciona las respuestas candidatas hasta que encuentres los campos que necesitas. Anota la URL, el método HTTP, la cadena de consulta o el cuerpo de la solicitud, los encabezados relevantes y cualquier cookie o token vinculado a la sesión.

Reproduce la solicitud válida más pequeña en Scrapy en lugar de copiar todos los encabezados del navegador:

def parse(self, response):
    yield scrapy.Request(
        "https://example.com/api/products?page=1",
        headers={"Accept": "application/json"},
        callback=self.parse_products,
    )

def parse_products(self, response):
    payload = response.json()
    for row in payload["results"]:
        yield {"id": row["id"], "name": row["name"]}

De esta forma se evitan los tiempos de ejecución del DOM, el consumo de memoria del navegador y los cambios en los selectores visuales. Si el punto final utiliza paginación, valores de cursor o datos POST, sigue directamente esos parámetros. Cuando haya autenticación, conserva las cookies necesarias o el flujo de tokens en lugar de codificar de forma estática una credencial de corta duración.

Analiza el JSON y los datos incrustados en los scripts

A veces, el HTML inicial ya contiene el estado de la aplicación dentro de un <script> elemento. Extrae ese script con un selector CSS o XPath. Utiliza json.loads() cuando el contenido sea JSON válido; utiliza chompjs cuando se trate de sintaxis de objeto JavaScript con claves sin comillas, comas finales o diferencias similares.

import chompjs

script = response.css("script[data-page-state]::text").get()
state = chompjs.parse_js_object(script) if script else {}
items = state.get("products", [])

Es preferible utilizar un analizador estructurado en lugar de una expresión regular amplia. Las expresiones regulares pueden aislar una asignación claramente delimitada, pero se vuelven inestables ante objetos anidados y cadenas con caracteres de escape. Combinar solicitudes JSON directas con el análisis de etiquetas de script suele resolver el contenido dinámico de Scrapy sin necesidad de abrir un navegador.

Añade renderizado de JavaScript con scrapy-playwright

Cuando los datos dependan de un comportamiento exclusivo del navegador, como código de aplicación, API web o estado basado en la interacción, añade la representación de forma selectiva. Este ejemplo de JavaScript con Scrapy utiliza scrapy-playwright como vía de integración principal, al tiempo que mantiene las solicitudes normales en el descargador estándar de Scrapy.

Instalar y configurar la integración

Un patrón de configuración muy documentado consiste en instalar el complemento y un binario del navegador Playwright en el mismo entorno del proyecto:

python -m pip install scrapy-playwright
python -m playwright install chromium

Añade el reactor asyncio y los controladores de descarga de Playwright a settings.py:

TWISTED_REACTOR = "twisted.internet.asyncioreactor.AsyncioSelectorReactor"

DOWNLOAD_HANDLERS = {
    "http": "scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler",
    "https": "scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler",
}

PLAYWRIGHT_BROWSER_TYPE = "chromium"

Fija las versiones compatibles de las dependencias en los proyectos desplegables y prueba el mismo archivo de bloqueo tanto en desarrollo como en producción. La documentación proporcionada no valida la superficie de configuración actual del complemento, por lo que debes consultar la documentación actual del proyecto antes de considerar esta plantilla lista para su lanzamiento.

Renderiza una solicitud y analízala con los selectores de Scrapy

Marca solo la solicitud que requiera un navegador. Una espera significativa del selector garantiza que la llamada de retorno reciba el HTML una vez que aparezca el componente de destino:

import scrapy
from scrapy_playwright.page import PageMethod

class ProductsSpider(scrapy.Spider):
    name = "products"

    def start_requests(self):
        yield scrapy.Request(
            "https://example.com/products",
            meta={
                "playwright": True,
                "playwright_page_methods": [
                    PageMethod(
                        "wait_for_selector",
                        "[data-product-card]",
                    )
                ],
            },
        )

    def parse(self, response):
        for card in response.css("[data-product-card]"):
            yield {
                "name": card.css("[data-name]::text").get(),
                "price": card.css("[data-price]::text").get(),
            }

La llamada de retorno sigue recibiendo una respuesta de Scrapy, por lo que los patrones de extracción CSS y XPath existentes siguen siendo útiles. Mantén las páginas de categorías estáticas, las llamadas a la API y los recursos en solicitudes normales. Un tutorial específico de Scrapy-Playwright para sitios con gran cantidad de JavaScript es la siguiente referencia interna lógica cuando necesites múltiples contextos, eventos de página o una configuración más detallada del proyecto.

Esperar e interactuar con contenido dinámico

Que se haya completado un evento de navegación no significa que los datos de la aplicación estén listos. La representación de JavaScript de Scrapy solo resulta fiable cuando la araña espera a que se establezca un estado relacionado con los campos que pretende extraer.

Prefiere las esperas basadas en el estado a los retrasos fijos

Espera a un selector estable, una respuesta de API conocida, una transición de URL o un indicador de la aplicación. Un selector como [data-results-loaded="true"] expresa la intención mejor que esperar tres segundos. Los retrasos fijos pueden resultar demasiado cortos en una ejecución lenta y suponer una pérdida de tiempo en una rápida.

Utiliza tiempos de espera limitados y registra qué condición ha fallado. Si no existe ninguna señal estable, una breve espera fija puede servir como alternativa, pero establece un límite máximo para toda la solicitud del navegador. De este modo, la espera de Scrapy ante un fallo de JavaScript se hace observable, en lugar de dejar las páginas abiertas indefinidamente.

Hacer clic, desplazarse, paginar y mantener el estado de la sesión

Los métodos de página pueden realizar una interacción controlada antes de que se devuelva la respuesta:

bplaywright_page_methods": [
    PageMethod("click", "button.load-more"),
    PageMethod("wait_for_selector", "[data-page='2']"),
]

Para el desplazamiento infinito, repite la acción de desplazamiento solo mientras aumente el recuento de elementos y detente tras un número definido de pasadas sin cambios. Para la paginación, da prioridad a la solicitud de datos del sitio si existe; de lo contrario, haz clic en el control «Siguiente» y espera a que cambie un marcador de página o el ID de la primera fila.

Mantén las solicitudes autenticadas relacionadas en el mismo contexto de navegador con el mismo nombre para que las cookies y el almacenamiento local puedan persistir. Con playwright_include_page=True, cierra la página activa en un finally bloque. Cierra un contexto dedicado tras su última solicitud de sesión, no mientras otras solicitudes sigan dependiendo de él:

async def parse_last_session_page(self, response):
    page = response.meta["playwright_page"]
    context = page.context
    try:
        return {"title": await page.title()}
    finally:
        await page.close()
        await context.close()

Dado que las API de inclusión y limpieza pueden cambiar, confirma los metadatos de la solicitud actual y las reglas del ciclo de vida antes de la implementación. Las páginas que se escapan acaban agotando el límite de páginas del navegador y hacen que una araña de JavaScript con Scrapy, que por lo demás sería correcta, parezca haberse colgado.

Compara las opciones de JavaScript de Scrapy

La mejor integración depende de si necesitas un navegador real, del nivel de interacción que requiera el flujo y de quién vaya a gestionar la infraestructura de renderizado. No elijas únicamente por lo breve que parezca la primera demostración.

Playwright, Selenium, Splash y las API de renderizado alojadas

Opción

Más adecuada

Principales ventajas e inconvenientes

scrapy-playwright

Renderización selectiva en el navegador dentro de un proyecto de Scrapy, incluyendo interacciones modernas

Consumo de CPU y memoria del navegador, gestión asíncrona del ciclo de vida y detalles de integración dependientes de la versión

Integración basada en Selenium

Equipos con automatización WebDriver o código de pruebas ya establecido

Gestión de controladores y navegadores, mayor integración con Scrapy y compatibilidad que debe comprobarse para el paquete elegido

Integración basada en Splash

Un servicio de renderizado HTTP o flujos de trabajo existentes basados en Lua

Un servicio independiente que gestionar, además de un comportamiento de JavaScript que puede diferir del de un navegador completo actual

API de renderizado alojada

Descarga de la infraestructura de navegador, proxy y reintentos

Límites específicos del proveedor, semántica de los tiempos de espera, concurrencia, precios y menor control local

Una comparación específica entre Scrapy y Selenium puede resultar útil si ya mantienes código de WebDriver. Un tutorial de Scrapy Splash resulta más útil cuando tu arquitectura se decanta por un servicio de renderizado independiente. Para JavaScript con Scrapy, utiliza la opción que satisfaga la interacción mínima requerida, dejando las solicitudes sencillas sin necesidad de navegador.

La representación no es una garantía contra los bots. Un navegador completo sigue pudiendo recibir una respuesta 403, un CAPTCHA, un límite de frecuencia o una verificación de cuenta, por lo que debes evaluar los controles de acceso por separado de la ejecución del DOM.

Ejecuta arañas con renderizado de forma fiable en producción

Un navegador sin interfaz gráfica con Scrapy es más lento y pesado que una solicitud HTTP normal. La fiabilidad en producción se consigue limitando el trabajo del navegador, acotando los fallos y midiendo los recursos.

Controla el coste, la concurrencia y los recursos del navegador

Renderiza solo las URL que se haya comprobado que lo requieren. Almacena en caché páginas representativas durante el desarrollo del analizador, bloquea los medios innecesarios con precaución y establece límites conservadores para los contextos del navegador y las páginas. Aumenta la concurrencia gradualmente mientras supervisas la memoria, la CPU, los tiempos de espera y las páginas abiertas.

Utiliza presupuestos de tiempo de espera separados para la navegación y las esperas posteriores a la carga. Vuelve a intentar en caso de fallos transitorios de red o del proceso del navegador, pero limita el número de intentos y evita volver a intentar fallos deterministas, como un selector eliminado de forma permanente. Cierra las páginas tanto en caso de éxito como de excepciones, y recicla los «workers» del navegador que no funcionen correctamente. Estos controles evitan que el uso de JavaScript con Scrapy convierta un objetivo lento en una contrapresión que afecte a todo el rastreador.

Una guía sobre cómo extraer datos de la web sin ser bloqueado es un complemento interno útil para los encabezados, las tasas de solicitud, los proxies y los diagnósticos de acceso, aspectos que son independientes de la corrección de la representación.

Solucionar problemas de contenido que falta, es lento o está bloqueado

Síntoma

Comprueba lo siguiente

El campo existe en el navegador pero no response.text

Busca la solicitud Fetch/XHR o el script incrustado antes de añadir la visualización

La respuesta renderizada sigue sin datos

Espera a que haya un selector específico para el campo o a que se cumpla una condición de red y, a continuación, verifica el selector con el DOM final

La primera página funciona, pero las siguientes están vacías

Conserva las cookies, los tokens, el estado del contexto y los parámetros de paginación

El rastreador se ralentiza con el tiempo

Cuenta las páginas y los contextos abiertos, confirma la limpieza y reduce la concurrencia de renderizado

Tiempos de espera repetidos

Separar la navegación de las esperas de la aplicación, capturar datos de diagnóstico y limitar los reintentos

403 o CAPTCHA

Trátalo como un problema de control de acceso, no como una prueba de que JavaScript haya fallado

Cuando falle un selector, guarda el HTML renderizado y compáralo con la sesión del navegador utilizada para las pruebas manuales. Si las respuestas están bloqueadas, revisa los códigos de estado y las páginas de verificación antes de modificar la lógica de espera. Este ciclo de «síntoma-acción» mantiene la depuración basada en pruebas, en lugar de añadir tiempos de espera más largos a cada solicitud.

Puntos clave

  • Busca HTML sin procesar, puntos finales JSON y el estado de la aplicación incrustado antes de añadir un navegador.
  • Activa scrapy-playwright solo en las solicitudes que requieran ejecución y, a continuación, sigue utilizando los selectores de Scrapy en la respuesta renderizada.
  • Espera a que el estado de la página sea observable, limita los tiempos de espera y cierra cada página incluida o contexto dedicado.
  • Trata la representación, el escalado y el acceso antibots como problemas de ingeniería independientes con diagnósticos distintos.

Preguntas frecuentes

¿Puede una araña de Scrapy mezclar solicitudes estándar con solicitudes renderizadas en JavaScript?

Sí. Mantén los objetos scrapy.Request en el descargador predeterminado y añade los metadatos de renderizado solo a las URL que necesiten un navegador. Ambos tipos de solicitud pueden alimentar el mismo canal de procesamiento de elementos. Este modelo selectivo preserva el rendimiento de Scrapy para páginas estáticas, al tiempo que permite que un subconjunto más pequeño utilice la ejecución en el navegador.

¿Cómo puedo ejecutar código JavaScript personalizado o hacer clic en un elemento con scrapy-playwright?

Utiliza una entrada de método de página para operaciones que puedan ejecutarse antes de la llamada de retorno, como un clic o una evaluate llamada. Para la lógica condicional o los valores devueltos por JavaScript personalizado, incluye el objeto de página activo en los metadatos de la respuesta y utiliza una llamada de retorno asíncrona. Cierra siempre esa página en finally, incluso cuando la extracción genere una excepción.

¿Cómo puedo capturar una captura de pantalla o inspeccionar el HTML renderizado durante la depuración?

Incluye la página de Playwright en la respuesta y, a continuación, llama a page.screenshot(path="debug.png", full_page=True) dentro de una llamada de retorno asíncrona. Guarda await page.content() junto con la captura de pantalla cuando el comportamiento del selector no esté claro. Utiliza nombres de archivo únicos que incluyan un ID de solicitud y evita dejar estos diagnósticos activados a gran escala, ya que las capturas de pantalla añaden sobrecarga de E/S y de almacenamiento.

¿Un navegador sin interfaz gráfica evita las respuestas 403, los CAPTCHA o los bloqueos?

No. Un navegador sin interfaz gráfica ejecuta JavaScript, pero los sitios web pueden seguir evaluando la reputación de la IP, la frecuencia de las solicitudes, las cookies, el comportamiento de la cuenta, las huellas del navegador y los patrones de navegación. Analiza por separado el estado devuelto y el contenido de los retos. La visualización puede hacer que la página sea funcional sin que el tráfico sea de confianza o esté autorizado.

¿Se puede ejecutar scrapy-playwright en Docker o en un entorno de CI?

Sí. El contenedor debe incluir el binario del navegador seleccionado y sus dependencias del sistema operativo, y su versión de Playwright debe coincidir con el paquete de Python que instales. Fija las dependencias, prueba la imagen sin un servidor de pantalla, proporciona suficiente memoria compartida y ejecuta una pequeña prueba de humo renderizada antes de iniciar la araña completa.

Conclusión: JavaScript con Scrapy en la práctica

La forma fiable de gestionar páginas dinámicas es ir de lo más sencillo a lo más complejo. Compara la respuesta sin procesar de Scrapy con el DOM renderizado, localiza la solicitud o el estado incrustado que proporciona los campos que faltan y analiza los datos estructurados directamente siempre que sea posible. Solo entonces debes añadir la ejecución en el navegador.

Cuando sea necesario el renderizado, hazlo de forma selectiva. Espera a un estado vinculado a los datos, no a un retraso arbitrario; conserva el contexto de sesión solo cuando el flujo de trabajo lo requiera; y cierra las páginas y los contextos de forma predecible. En producción, la concurrencia del navegador, los límites de tiempo de espera, los reintentos y las métricas de recursos son tan importantes como los selectores. Un navegador que se renderiza correctamente en un portátil puede convertirse en el cuello de botella del rastreador o encontrarse con controles de acceso a gran escala.

Si el bloqueo en la capa de solicitudes se convierte en la limitación, en lugar de la lógica de interacción, WebScrapingAPI ofrece una API de scraper que devuelve HTML sin procesar mientras gestiona la rotación de proxies y los CAPTCHAs en segundo plano. Esto te permite mantener el análisis y el flujo de elementos de Scrapy al tiempo que descargas la capa de recuperación inestable. Empieza por la ruta más sencilla que puedas verificar, implementa los controles necesarios y añade complejidad al navegador solo cuando los datos indiquen que es necesario.

Acerca del autor

Mihai Maxim, Desarrollador Full Stack @ WebScrapingAPI

Mihai Maxim

Desarrollador Full Stack

Mihai Maxim es desarrollador full stack en WebScrapingAPI, donde colabora en todas las áreas del producto y ayuda a crear herramientas y funciones fiables para la plataforma.

Extracción de datos web con AWS Lambda: guía para Python y Java 2026
Guías

Extracción de datos web con AWS Lambda: guía para Python y Java 2026

En resumen: el web scraping con AWS Lambda funciona mejor cuando cada invocación es breve, está delimitada y se puede reintentar de forma independiente. Empieza con HTTP directo, AWS SAM y S3, y añade SQS, contenedores, renderización en navegador, proxies o una capa de recuperación gestionada solo cuando la carga de trabajo demuestre que los necesita.

Suciu Dan33 min read
Leer artículo
Cómo utilizar GoSpider: rastrear, depurar URL y extraer datos
Guías

Cómo utilizar GoSpider: rastrear, depurar URL y extraer datos

En resumen: GoSpider es un rastreador de línea de comandos diseñado para descubrir URL, no un extractor completo de datos estructurados. Esta guía sobre cómo utilizar GoSpider muestra cómo realizar un rastreo limitado, gestionar correctamente los resultados, pasar los datos de Colly a CSV y seguir una ruta de diagnóstico para respuestas 403 o páginas que requieren renderización con JavaScript.

Suciu Dan24 min read
Leer artículo
Cómo raspar Redfin: Guía Python de Datos Inmobiliarios
Guías

Cómo raspar Redfin: Guía Python de Datos Inmobiliarios

TL;DR: Redfin expone puntos finales de API ocultos que devuelven JSON estructurado para los listados de propiedades, lo que permite omitir por completo el frágil análisis HTML. Esta guía te guía a través de la construcción de un raspador de Python que extrae datos de alquiler y venta, busca por ubicación, supervisa los nuevos listados a través de mapas de sitio XML y exporta resultados limpios a CSV o JSON.

Suciu Dan14 min read
Leer artículo

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.