Volver al blog

Cómo usar Selenium con PowerShell [Tutorial 2025]

Emily Chen
Emily Chen

Advanced Data Extraction Specialist

01-Mar-2025

Selenium sigue siendo una opción popular para el web scraping y las pruebas mediante automatización del navegador. Estas tareas se escriben tradicionalmente utilizando lenguajes de scripting, incluido PowerShell. Para aprovechar las capacidades de Selenium con PowerShell, la comunidad de desarrolladores creó enlaces dedicados que hacen posible esta integración.

Sin embargo, el web scraping se ha vuelto cada vez más desafiante debido a los sofisticados sistemas anti-bot como Cloudflare. En esta guía completa, explorará:

  • Comenzar con Selenium y PowerShell
  • Interactuar con páginas web en un navegador
  • Evitar bloqueos de sistemas anti-bot
  • Resolver desafíos de Cloudflare con Scrapeless Scraping Browser

¡Vamos a sumergirnos!

¿Se puede usar Selenium con PowerShell?

Selenium es la biblioteca de navegador headless más popular en la comunidad de TI. Su API rica y consistente la hace perfecta para crear scripts de automatización del navegador para pruebas y web scraping.

PowerShell es un shell de comandos potente disponible en entornos de cliente y servidor de Windows. La automatización del navegador complementa perfectamente la creación de scripts, razón por la cual la comunidad de desarrolladores creó selenium-powershell, un puerto de Selenium WebDriver para PowerShell.

Aunque la biblioteca actualmente busca mantenedores, sigue siendo el módulo principal para la automatización web en PowerShell.

Nota: Si necesita repasar los conceptos básicos antes de comenzar con este tutorial de Selenium PowerShell, revise los fundamentos del scraping de navegador headless y el web scraping con PowerShell.

Web Scraping con Selenium y PowerShell: Conceptos básicos

Nota: Los ejemplos de este artículo están diseñados para sistemas operativos Windows, ya que algunas funciones como el módulo Selenium-PowerShell y los objetos COM son específicos de Windows. Si está utilizando macOS o Linux, es posible que deba adaptar estos scripts según corresponda.

En esta sección, presentaremos los conceptos básicos del uso de Selenium con PowerShell. Antes de pasar a sitios protegidos por Cloudflare más complejos como G2, es útil comprender los fundamentos.

Nuestro objetivo: Reseñas de G2

Para este tutorial, nos centraremos en el scraping de reseñas de G2, específicamente reseñas de Airtable en: https://www.g2.com/products/airtable/reviews. Este es un ejemplo del mundo real de un sitio protegido por Cloudflare que presenta desafíos para los métodos de scraping tradicionales.

Esto es lo que hace que G2 sea un objetivo interesante:

  1. Está protegido por Cloudflare, que bloquea la mayoría de los intentos básicos de scraping
  2. Contiene reseñas valiosas de software empresarial que podrían ser útiles para la investigación de mercado
  3. Las reseñas están estructuradas de una manera que las hace ideales para la extracción de datos
  4. Representa el tipo de datos empresariales de alto valor que a menudo se encuentran detrás de los sistemas anti-bot

Primero, veamos cómo un enfoque tradicional de Selenium con PowerShell tendría dificultades con este sitio y luego cómo Scrapeless puede resolver el problema.

Obtener el controlador Chrome correcto (febrero de 2025)

Un paso crítico al configurar Selenium con PowerShell es asegurarse de tener la versión correcta de ChromeDriver que coincida con su navegador Chrome. A partir del 28 de febrero de 2025, estas son las últimas versiones de Chrome y sus controladores web correspondientes:

Chrome Estable (133.0.6943.141):

  • ChromeDriver para Windows de 64 bits: chromedriver-win64.zip
  • ChromeDriver para Windows de 32 bits: chromedriver-win32.zip
  • ChromeDriver para Mac (Intel): chromedriver-mac-x64.zip
  • ChromeDriver para Mac (M1/M2): chromedriver-mac-arm64.zip
  • ChromeDriver para Linux: chromedriver-linux64.zip

Si está utilizando una versión diferente de Chrome (Beta, Dev o Canary), asegúrese de descargar el ChromeDriver correspondiente de la sección correspondiente de la página de descarga de Chrome para pruebas.

Descargue el ChromeDriver adecuado para su sistema, extraiga el archivo ZIP y coloque el ejecutable en la carpeta de su proyecto. Luego, haga referencia a esta ubicación en su script de PowerShell:

Copy
$Driver = Start-SeChrome -WebDriverDirectory '/path/to/chromedriver/folder' -Headless

El uso de la versión correcta de ChromeDriver es esencial; si hay una discrepancia de versión, Selenium arrojará errores y no podrá controlar Chrome correctamente.

Intentando raspar G2 con Selenium básico

Ahora, intentemos acceder a las reseñas de G2 con nuestra configuración básica de Selenium:

Copy
Import-Module -Name Selenium

# Inicializar Selenium WebDriver
$Driver = Start-SeChrome -WebDriverDirectory './chromedriver-win64' -Headless

# Visitar la página de reseñas de G2
Enter-SeUrl 'https://www.g2.com/products/airtable/reviews' -Driver $Driver

# Esperar un momento a que se cargue la página
Start-Sleep -Seconds 5

# Obtener el título de la página
$Title = $Driver.Title
Write-Host "Título de la página: $Title"

# Imprimir el origen de la página
$Html = $Driver.PageSource
$Html | Out-File -FilePath "g2_response.html"

# Cerrar el navegador
Stop-SeDriver -Driver $Driver

Cuando ejecute este script, en lugar de obtener la página real de reseñas de G2, probablemente verá:

  1. La página de desafío de Cloudflare
  2. Un título como "Solo un momento..." o "Comprobando su navegador..."
  3. HTML que contiene el desafío de JavaScript de Cloudflare

Esto se debe a que Cloudflare detecta navegadores automatizados como Selenium y presenta un desafío que las configuraciones básicas de Selenium no pueden resolver automáticamente.

El desafío de scraping de G2: Comprender la estructura de la reseña

Antes de intentar extraer datos, comprendamos lo que estamos intentando raspar. Las reseñas de G2 tienen un formato estructurado que incluye:

  1. Título de la reseña: generalmente un resumen citado de la reseña
  2. Calificación: se muestra como estrellas (de 5)
  3. Fecha: cuándo se publicó la reseña
  4. Texto de la reseña: dividido en secciones como "¿Qué te gusta más?" y "¿Qué no te gusta?"
  5. Información del usuario: detalles sobre quién escribió la reseña

Así es como se ve una reseña típica de G2 en la estructura HTML:

Copy
<div class="paper__bd">
  <div class="d-f mb-1">
    <div class="f-1 d-f ai-c mb-half-small-only">
      <div class="stars large xlarge--medium-down stars-10"></div>
      <div class="time-stamp pl-4th ws-nw">
        <span><span class="x-current-review-date" data-poison-date="">
          <meta content="2025-02-11" itemprop="datePublished">
          <time datetime="2025-02-11">Feb 11, 2025</time>
        </span></span>
      </div>
    </div>
  </div>
  <div class="d-f">
    <div class="f-1">
      <div data-poison="">
        <a class="pjax" href="https://www.g2.com/products/airtable/reviews/airtable-review-10822745">
          <div class="m-0 l2" itemprop="name">"Helped us get a handle on our complicated team-based scheduling and management."</div>
        </a>
      </div>
      <div data-poison-text="">
        <div itemprop="reviewBody">
          <div>
            <div class="l5 mt-2">What do you like best about Airtable?</div>
            <div>
              <p class="formatted-text">The best part about airtable is it's customiizable nature. We worked with a tech specialist to configure Airtable to meet our unique needs and it has so far been adaptable to everything we needed it to do...</p>
            </div>
          </div>
          <!-- More review sections... -->
        </div>
      </div>
    </div>
  </div>
</div>

La solución: Scrapeless Scraping Browser para evitar Cloudflare

Incluso con configuraciones avanzadas de Selenium, muchos proyectos de scraping son bloqueados por los sistemas de seguridad de Cloudflare. Aquí es donde Scrapeless Scraping Browser entra como una solución que cambia el juego.

¿Qué es Scrapeless Scraping Browser?

Qué es Scrapeless Scraping Browser

Scrapeless Scraping Browser es una solución de alto rendimiento que proporciona un entorno de navegador headless, lo que le permite evitar desafíos de JavaScript sin mantener su propia infraestructura. Se integra con Puppeteer y Playwright para una automatización perfecta y ofrece:

  • Tasa de éxito del 99.9% evitando desafíos de Cloudflare
  • Compatibilidad perfecta con Puppeteer/Playwright
  • Impulsado por IA, adaptándose automáticamente a las políticas de seguridad más recientes
  • Cobertura global para acceder a contenido restringido geográficamente

Inicie sesión en Scrapeless panel de control inmediatamente y cree su CLAVE API en la administración de CLAVE API

obtener clave api

Uso de Scrapeless Scraping Browser para extraer reseñas de G2

Ahora que entendemos por qué Selenium estándar con PowerShell tiene dificultades con sitios protegidos por Cloudflare como G2, implementemos una solución utilizando Scrapeless Scraping Browser.

Configuración de Scrapeless Scraping Browser con Puppeteer

La forma más eficaz de usar Scrapeless Scraping Browser es a través de Node.js con Puppeteer. Así es como se configura:

  1. Instalar Node.js
  • Descargue e instale Node.js desde nodejs.org
  • Verifique la instalación abriendo un símbolo del sistema y escribiendo:
Copy
node --version
npm --version
  1. Crear una carpeta de proyecto
  • Cree una nueva carpeta para su proyecto de scraping
  • Abra un símbolo del sistema en esta carpeta
  1. Instalar el paquete requerido
  • Instale puppeteer-core con npm:
Copy
npm install puppeteer-core
  1. Crear su script de scraping
  • Cree un archivo llamado scrape.js con el siguiente código:
Copy
// scrape.js
const puppeteer = require('puppeteer-core');

async function scrapeG2Reviews() {
  // Reemplace con su token de API Scrapeless real
  const API_TOKEN = 'YOUR_API_TOKEN_HERE';
  
  // Configurar la conexión a Scrapeless Scraping Browser
  const connectionURL = `wss://browser.scrapeless.com/browser?token=${API_TOKEN}&session_ttl=180&proxy_country=ANY`;
  
  console.log('Conectando a Scrapeless Scraping Browser...');
  
  const browser = await puppeteer.connect({
    browserWSEndpoint: connectionURL,
    defaultViewport: null,
  });
  
  console.log('¡Conectado!');
  
  const page = await browser.newPage();
  
  // Establecer un tiempo de espera más largo para la navegación
  page.setDefaultNavigationTimeout(120000);
  
  // Arreglo para guardar todas las reseñas
  const allReviews = [];
  
  // Número de páginas para raspar
  const pagesToScrape = 3;
  
  for (let currentPage = 1; currentPage <= pagesToScrape; currentPage++) {
    console.log(`Navegando a la página ${currentPage}...`);
    
    // Navegar directamente a la página específica
    const pageUrl = currentPage === 1 
      ? 'https://www.g2.com/products/airtable/reviews' 
      : `https://www.g2.com/products/airtable/reviews?page=${currentPage}`;
      
    await page.goto(pageUrl, {
      waitUntil: 'networkidle2',
      timeout: 60000
    });
    
    console.log(`Página ${currentPage} cargada. Tomando captura de pantalla...`);
    
    // Tomar una captura de pantalla para verificar que la página se cargó correctamente
    await page.screenshot({ path: `g2_page_${currentPage}.png` });
    
    // Esperar a que se cargue el contenido de la página
    // Probar diferentes selectores posibles ya que la estructura podría haber cambiado
    try {
      console.log('Esperando a que se cargue el contenido...');
      
      // Primero intentar con el selector original
      await page.waitForSelector('.paper__bd', { timeout: 10000 })
        .then(() => console.log('Selector encontrado: .paper__bd'))
        .catch(() => console.log('Selector .paper__bd no encontrado, intentando alternativas...'));
      
      // Si todavía estamos aquí, vamos a verificar la estructura de la página usando otro enfoque
      console.log('Analizando la estructura de la página...');
      
      // Obtener el título de la página para verificar que estamos en la página correcta
      const pageTitle = await page.title();
      console.log('Título de la página:', pageTitle);
      
      // Extraer reseñas con un enfoque más genérico
      console.log('Extrayendo reseñas...');
      
      // Intentar extraer reseñas con un enfoque más resistente
      const pageReviews = await page.evaluate(() => {
        // Buscar contenedores de reseñas con múltiples selectores
        const reviewElements = Array.from(
          document.querySelectorAll('.paper__bd, .review-item, .reviews-section article, [data-testid="review-card"]')
        );
        
        console.log(`Se encontraron ${reviewElements.length} elementos de reseña potenciales`);
        
        if (reviewElements.length === 0) {
          // Si no se encuentran reseñas con selectores específicos, vamos a volcar la estructura de la página
          const html = document.documentElement.outerHTML;
          return { error: 'No se encontraron reseñas', html: html.substring(0, 5000) }; // Primeros 5000 caracteres para depuración
        }
        
        return reviewElements.map(review => {
          // Intentar obtener el título con múltiples selectores potenciales
          const titleElement = 
            review.querySelector('[itemprop="name"], .review-title, h3, h4') ||
            review.querySelector('a.pjax div');
            
          const title = titleElement ? titleElement.innerText.trim() : 'Sin título';
          
          // Intentar obtener la fecha con múltiples selectores potenciales
          const dateElement = 
            review.querySelector('time, .review-date, [data-testid="review-date"]') ||
            review.querySelector('.time-stamp');
            
          const date = dateElement ? dateElement.innerText.trim() : 'Sin fecha';
          
          // Intentar obtener la calificación con múltiples enfoques
          let rating = 0;
          const starsElement = review.querySelector('.stars, .rating, [data-testid="star-rating"]');
          
          if (starsElement) {
            // Verificar si hay una clase stars-X
            const starsClass = starsElement.className;
            const starsMatch = starsClass.match(/stars-(\d+)/);
            if (starsMatch) {
              rating = parseInt(starsMatch[1]) / 2;
            } else {
              // De lo contrario, verificar la etiqueta aria que podría contener la calificación
              const ariaLabel = starsElement.getAttribute('aria-label');
              if (ariaLabel) {
                const ratingMatch = ariaLabel.match(/(\d+(\.\d+)?)/);
                if (ratingMatch) {
                  rating = parseFloat(ratingMatch[1]);
                }
              }
            }
          }
          
          // Intentar obtener el texto de la reseña con múltiples selectores potenciales
          const textElements = review.querySelectorAll('.formatted-text, .review-content, p, [data-testid="review-content"]');
          let content = '';
          
          textElements.forEach(el => {
            const text = el.innerText.trim();
            if (text && !text.includes('Review collected by and hosted on G2.com')) {
              content += text + '\n\n';
            }
          });
          
          return {
            title,
            date,
            rating,
            content: content.trim() || 'No se encontró contenido'
          };
        });
      });
      
      if (pageReviews.error) {
        console.log('Error al extraer reseñas:', pageReviews.error);
        console.log('Fragmento de HTML de la página:', pageReviews.html);
      } else {
        console.log(`Se extrajeron ${pageReviews.length} reseñas de la página ${currentPage}`);
        
        // Agregar a nuestra colección
        allReviews.push(...pageReviews);
      }
      
    } catch (error) {
      console.error('Error en la página:', error);
      // Tomar otra captura de pantalla en caso de error para depuración
      await page.screenshot({ path: `error_page_${currentPage}.png` });
      
      // Volcar el HTML de la página para depuración
      const html = await page.content();
      require('fs').writeFileSync(`error_page_${currentPage}.html`, html);
      console.log(`HTML de la página de error guardado en error_page_${currentPage}.html`);
    }
    
    // Pequeña pausa entre páginas para ser amables con el servidor
    await new Promise(r => setTimeout(r, 2000));
  }
  
  console.log(`Total de reseñas extraídas: ${allReviews.length}`);
  
  // Guardar todas las reseñas en un archivo
  require('fs').writeFileSync('g2_reviews.json', JSON.stringify(allReviews, null, 2));
  console.log('Reseñas guardadas en g2_reviews.json');
  
  await browser.close();
  console.log('¡Hecho!');
}

scrapeG2Reviews().catch(console.error);
  1. Ejecutar el script
  • Ejecute el script con:
Copy
node scrape.js

El formato de salida JSON

Después de ejecutar el script, tendrá un archivo g2_reviews.json con contenido que se parece a esto:

Copy
[
  {
    "title": "\"Helped us get a handle on our complicated team-based scheduling and management.\"",
    "date": "Feb 11, 2025",
    "rating": 5,
    "content": "What do you like best about Airtable?\nThe best part about airtable is it's customiizable nature. We worked with a tech specialist to configure Airtable to meet our unique needs and it has so far been adaptable to everything we needed it to do. It has a huge capacity in terms of features and we were able to get it up and running in a fairly short period of time.\n\nWhat do you dislike about Airtable?\nThere are no real downsides except the intial learning curve. I believe we could have built Airtable out as we needed but it would have been a bigger intial time investment. Working with a tech specialist who knew Airtable well really helped us get up and running fast. It was worth the cost."
  },
  {
    "title": "\"Great for organization, has changed our workflow\"",
    "date": "Feb 8, 2025",
    "rating": 4.5,
    "content": "What do you like best about Airtable?\nWhat I like best about Airtable is its flexibility and customizability. It allows us to create databases that perfectly fit our needs, with custom fields, views, and formulas. The ability to link records between tables is incredibly powerful.\n\nWhat do you dislike about Airtable?\nSometimes the learning curve can be steep for new users, especially when dealing with more complex formulas and automations. The pricing can also become quite expensive as your needs grow and you require more records or features."
  },
  {
    "title": "\"Powerful database tool with a spreadsheet interface\"",
    "date": "Jan 29, 2025",
    "rating": 4,
    "content": "What do you like best about Airtable?\nAirtable combines the familiarity of spreadsheets with the power of a database. I appreciate how it allows for different views of the same data - grid, kanban, calendar, etc. It's intuitive enough for non-technical users but powerful enough for complex data management.\n\nWhat do you dislike about Airtable?\nThe free tier is quite limited, and pricing jumps significantly for premium features. Sometimes performance can slow down with large datasets, and there are occasional sync issues when multiple team members are editing simultaneously."
  }
]

Integración de PowerShell: Uso de Scrapeless Scraping Browser desde PowerShell

Si prefiere trabajar con PowerShell, puede crear un script envoltorio que llame al script de Node.js y procese los resultados:

  1. Crear un script envoltorio de PowerShell
  • Cree un archivo llamado run-scraper.ps1 con el siguiente código:
Copy
# Archivo: run-scraper.ps1

Write-Host "Iniciando el scraping de G2.com con Scrapeless Scraping Browser..."

# Crear carpeta de salida
$outputFolder = "G2_Reviews_" + (Get-Date -Format "yyyyMMdd_HHmmss")
New-Item -ItemType Directory -Path $outputFolder -Force | Out-Null

# Ejecutar el script de Node.js y capturar la salida
$jsonFile = Join-Path $outputFolder "reviews.json"
node scrape.js > $jsonFile

Write-Host "Scraping de Node.js completado. Procesando resultados..."

# Verificar si el archivo contiene datos JSON válidos
try {
    # Leer el contenido del archivo
    $content = Get-Content -Path $jsonFile -Raw
    
    # Extraer la parte JSON (asumiendo que es la salida después de todos los mensajes console.log)
    if ($content -match '\[.*\]') {
        $jsonContent = $Matches[0]
        Set-Content -Path $jsonFile -Value $jsonContent
        
        # Analizar el JSON en objetos de PowerShell
        $reviews = $jsonContent | ConvertFrom-Json
        
        # Exportar a CSV
        $csvPath = Join-Path $outputFolder "g2_reviews.csv"
        $reviews | Export-Csv -Path $csvPath -NoTypeInformation -Encoding UTF8
        
        Write-Host "Se extrajeron correctamente $($reviews.Count) reseñas"
        Write-Host "Resultados guardados en: $csvPath"
        
        # Análisis básico
        $averageRating = ($reviews | Measure-Object -Property rating -Average).Average
        Write-Host "Calificación promedio: $averageRating de 5"
        
        $ratingDistribution = $reviews | Group-Object -Property rating | Sort-Object -Property Name
        foreach ($ratingGroup in $ratingDistribution) {
            Write-Host "$($ratingGroup.Name) estrellas: $($ratingGroup.Count) reseñas"
        }
    } else {
        Write-Error "No se pudieron encontrar datos JSON en la salida"
    }
} catch {
    Write-Error "Error al procesar los datos JSON: $_"
}
  1. Ejecutar el script envoltorio de PowerShell
  • Abra PowerShell
  • Navegue a la carpeta de su proyecto
  • Ejecute el script:
Copy
.\run-scraper.ps1
  1. Revisar los resultados
  • El script crea una carpeta con fecha con resultados JSON y CSV
  • Las estadísticas básicas se muestran en la consola
  • El archivo CSV se puede abrir en Excel u otras aplicaciones para un análisis posterior

Ejemplo avanzado: Scraping de varias páginas con Scrapeless Scraping Browser

Para un caso de uso más avanzado, aquí hay un ejemplo que muestra cómo raspar reseñas de varias páginas:

Copy
// Archivo: scrape-g2-multipage.js
const puppeteer = require('puppeteer-core');

async function scrapeG2ReviewsMultiPage() {
  // Reemplace con su token de API Scrapeless real
  const API_TOKEN = 'YOUR_API_TOKEN_HERE';
  
  // Configurar la conexión a Scrapeless Scraping Browser
  const connectionURL = `wss://browser.scrapeless.com/browser?token=${API_TOKEN}&session_ttl=300&proxy_country=ANY`;
  
  console.log('Conectando a Scrapeless Scraping Browser...');
  
  const browser = await puppeteer.connect({
    browserWSEndpoint: connectionURL,
    defaultViewport: null,
  });
  
  console.log('¡Conectado! Abriendo G2.com...');
  
  const page = await browser.newPage();
  
  // Establecer un tiempo de espera de navegación razonable
  page.setDefaultNavigationTimeout(60000);
  
  // Arreglo para guardar todas las reseñas
  const allReviews = [];
  
  // Función para extraer reseñas de la página actual
  const extractReviews = async () => {
    return page.evaluate(() => {
      return Array.from(document.querySelectorAll('.paper__bd')).map(review => {
        // Título
        const titleElement = review.querySelector('a.pjax div[itemprop="name"]');
        const title = titleElement ? titleElement.innerText : 'Sin título';
        
        // Fecha
        const dateElement = review.querySelector('time');
        const date = dateElement ? dateElement.innerText : 'Sin fecha';
        
        // Calificación
        const starsElement = review.querySelector('.stars');
        let rating = 0;
        if (starsElement) {
          const starsClass = starsElement.className;
          const match = starsClass.match(/stars-(\d+)/);
          if (match) {
            rating = parseInt(match[1])/2;
          }
        }
        
        // Contenido de la reseña
        const contentElements = review.querySelectorAll('.formatted-text');
        let content = '';
        contentElements.forEach(el => {
          const text = el.innerText;
          if (!text.includes('Review collected by and hosted on G2.com')) {
            content += text + '\n\n';
          }
        });
        
        return {
          title,
          date,
          rating,
          content: content.trim()
        };
      });
    });
  };
  
  // Navegar a la primera página
  await page.goto('https://www.g2.com/products/airtable/reviews', {
    waitUntil: 'networkidle2'
  });
  
  // Esperar a que se carguen las reseñas
  await page.waitForSelector('.paper__bd', {timeout: 30000});
  
  // Tomar una captura de pantalla de la primera página
  await page.screenshot({ path: 'page1.png' });
  
  // Número de páginas para raspar (límite para demostración)
  const pagesToScrape = 3;
  let currentPage = 1;
  
  while (currentPage <= pagesToScrape) {
    console.log(`Raspando página ${currentPage}...`);
    
    // Extraer reseñas de la página actual
    const pageReviews = await extractReviews();
    console.log(`Se encontraron ${pageReviews.length} reseñas en la página ${currentPage}`);
    
    // Agregar a nuestra colección
    allReviews.push(...pageReviews);
    
    // Verificar si hay un botón de siguiente página
javascript Copy
const hasNextPage = await page.evaluate(() => {
      const nextButton = document.querySelector('a.next_page');
      return nextButton && !nextButton.classList.contains('disabled');
    });
    
    if (hasNextPage && currentPage < pagesToScrape) {
      // Navegar directamente a la URL de la siguiente página
      currentPage++;
      await page.goto(`https://www.g2.com/products/airtable/reviews?page=${currentPage}`, {
        waitUntil: 'networkidle2'
      });
      
      // Esperar a que se carguen las reseñas en la nueva página
      await page.waitForSelector('.paper__bd', {timeout: 30000});
      
      // Tomar una captura de pantalla de la nueva página
      await page.screenshot({ path: `page${currentPage}.png` });
    } else {
      break;
    }
  }
  
  console.log(`Reseñas totales extraídas: ${allReviews.length}`);
  
  // Mostrar todas las reseñas como JSON
  console.log(JSON.stringify(allReviews, null, 2));
  
  await browser.close();
  console.log('¡Listo!');
}

scrapeG2ReviewsMultiPage().catch(console.error);

En Scrapeless, solo accedemos a datos disponibles públicamente y cumplimos estrictamente con las leyes, regulaciones y políticas de privacidad del sitio web aplicables. El contenido de este blog es sólo para fines de demostración y no implica ninguna actividad ilegal o infractora. No ofrecemos garantías y renunciamos a toda responsabilidad por el uso de la información de este blog o enlaces de terceros. Antes de realizar cualquier actividad de scraping, consulte a su asesor legal y revise los términos de servicio del sitio web de destino u obtenga los permisos necesarios.

Artículos más populares

Catalogar