En el blog anterior de este tutorial, proporcionamos un ejemplo exitoso del uso de Java para rastrear datos de una sola página en el sitio Scrapeme.
Entonces, ¿hay una forma más específica de rastrear datos?
Sí, la hay. En este artículo, obtendrá 3 herramientas más útiles para hacer rastreo web utilizando Java:
El proceso de rastreo concurrente es más rápido y eficiente que los métodos normales de rastreo web. ¿No me cree? Lo aprenderá a través de la siguiente explicación y demostración de código específico:
Tomemos como ejemplo de análisis el rastreo de ScrapeMe:
Podemos ver que los enlaces a cada una de las páginas de datos están dentro del elemento a.page-numbers y los detalles son los mismos para cada página. Por lo tanto, sólo tenemos que iterar sobre estos enlaces paginados para obtener los enlaces a todas las demás páginas.
A continuación, podemos iniciar un hilo separado para cada página para realizar el rastreo de datos para obtener todos los datos de la página. Si hay muchas tareas, es posible que necesitemos utilizar un pool de hilos para configurar el número de hilos según nuestro dispositivo.
A modo de comparación, vamos a realizar primero todas las capturas de datos sin utilizar concurrencia:
Scraper.class
import org.jsoup.*;
import org.jsoup.nodes.*;
import org.jsoup.select.*;
import java.io.IOException;
import java.util.*;
public class Scraper {
/**
first page of scrapeme products list
*/
private static final String SCRAPEME_SITE_URL = "https://scrapeme.live/shop";
public static void scrape(List<ScrapeMeProduct> scrapeMeProducts, Set<String> pagesFound, List<String> todoPages) {
// html doc for scrapeme page
Document doc;
// remove page from todoPages
String url = todoPages.removeFirst();
try {
doc = Jsoup.connect(url).userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36").header("Accept-Language", "*").get();
// select product nodes
Elements products = doc.select("li.product");
for (Element product : products) {
ScrapeMeProduct scrapeMeProduct = new ScrapeMeProduct();
scrapeMeProduct.setUrl(product.selectFirst("a").attr("href")); // parse and set product url
scrapeMeProduct.setImage(product.selectFirst("img").attr("src")); // parse and set product image
scrapeMeProduct.setName(product.selectFirst("h2").text()); // parse and set product name
scrapeMeProduct.setPrice(product.selectFirst("span").text()); // parse and set product price
scrapeMeProducts.add(scrapeMeProduct);
}
// add to pages found set
pagesFound.add(url);
Elements paginationElements = doc.select("a.page-numbers");
for (Element pageElement : paginationElements) {
String pageUrl = pageElement.attr("href");
// add new pages to todoPages
if (!pagesFound.contains(pageUrl) && !todoPages.contains(pageUrl)) {
todoPages.add(pageUrl);
}
// add to pages found set
pagesFound.add(pageUrl);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static List<ScrapeMeProduct> scrapeAll() {
// products
List<ScrapeMeProduct> scrapeMeProducts = new ArrayList<>();
// all pages found
Set<String> pagesFound = new HashSet<>();
// pages list waiting for scrape
List<String> todoPages = new ArrayList<>();
// add the first page to scrape
todoPages.add(SCRAPEME_SITE_URL);
while (!todoPages.isEmpty()) {
scrape(scrapeMeProducts, pagesFound, todoPages);
}
return scrapeMeProducts;
}
}
Main.class
import io.xxx.basic.ScrapeMeProduct;
import io.xxx.basic.Scraper;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<ScrapeMeProduct> products = Scraper.scrapeAll();
System.out.println(products.size() + " products scraped");
// then you can do whatever you want
}
}
En el código de modo no concurrente anterior, creamos una lista llamada todoPages que contiene las URL de las páginas que se van a raspar. Hacemos un bucle a través de ella hasta que todas las páginas han sido raspadas. Sin embargo, durante el bucle, puede llevar mucho tiempo ejecutar secuencialmente y esperar a que se completen todas las tareas.
¿Cómo podemos acelerar nuestra eficiencia?
Le encantará saber que podemos utilizar la programación concurrente de Java para optimizar el raspado web. Ayuda a iniciar múltiples hilos para ejecutar tareas simultáneamente y luego fusionar los resultados.
Este es el método optimizado:
Scraper.class
// duplicates omitted
public static void concurrentScrape() {
// using synchronized collections
List<ScrapeMeProduct> pokemonProducts = Collections.synchronizedList(new ArrayList<>());
Set<String> pagesDiscovered = Collections.synchronizedSet(new HashSet<>());
List<String> pagesToScrape = Collections.synchronizedList(new ArrayList<>());
pagesToScrape.add(SCRAPEME_SITE_URL);
// new thread pool with CPU cores
ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
scrape(pokemonProducts, pagesDiscovered, pagesToScrape);
try {
while (!pagesToScrape.isEmpty()) {
executorService.execute(() -> scrape(pokemonProducts, pagesDiscovered, pagesToScrape));
// sleep for a while for all pending threads to end
TimeUnit.MILLISECONDS.sleep(300);
}
executorService.shutdown();
executorService.awaitTermination(5, TimeUnit.MINUTES);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
En este código, hemos aplicado las colecciones sincronizadas Collections.synchronizedList
y Collections.synchronizedSet
para garantizar el acceso seguro y la modificación entre múltiples hilos.
A continuación, utilizamos Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors())
para crear un pool de hilos con el mismo número de hilos que el número de núcleos de CPU para maximizar la utilización de los recursos del sistema.
Por último, utilizamos el método executionService.awaitTermination
para esperar a que se completen todas las tareas del pool.
Comprueba los resultados:
Los navegadores sin cabeza son cada vez más habituales durante el rastreo de datos web, especialmente cuando se trabaja con contenidos dinámicos o se ejecuta JavaScript.
Los rastreadores web tradicionales sólo pueden recuperar contenido HTML estático y no pueden ejecutar código JavaScript ni simular interacciones del usuario. Por tanto, con el auge de los sitios web modernos que utilizan tecnología JavaScript para cargar contenidos dinámicamente o realizar acciones interactivas, los rastreadores web tradicionales se enfrentan a un enorme desafío.
Un navegador headless es un navegador sin interfaz gráfica de usuario que ejecuta código JavaScript en segundo plano y ofrece las mismas funciones y API que un navegador normal.
Al utilizar un navegador sin cabeza, podemos simular los comportamientos del usuario en el navegador, incluida la carga de páginas, los clics, la cumplimentación de formularios, etc., con el fin de capturar el contenido web con mayor precisión. En el lenguaje Java, Selenium WebDriver y Playwright son librerías populares de controladores de navegadores sin cabeza.
Los navegadores antidetección (navegadores de huellas dactilares) se consideran las herramientas de rastreo de datos más eficaces y seguras.
Con el desarrollo de la tecnología de seguridad web, los sitios web son cada vez más estrictos en sus defensas contra los rastreadores web. Los rastreadores tradicionales suelen ser fáciles de identificar e interceptar, y uno de los principales métodos de identificación es: la huella digital del navegador, un "perro guardián" especial que distingue entre usuarios reales y rastreadores.
Entender y tratar con navegadores con huellas dactilares se ha convertido, por tanto, en algo crucial en el contexto del rastreo web.
Un navegador antidetección es un navegador que imita el comportamiento del navegador de un usuario real y tiene características únicas de huella digital del navegador. Estas características incluyen, entre otras, cadenas de agente de usuario, resolución de pantalla, información sobre el sistema operativo, listas de complementos, configuración de idioma, etcétera. Con esta información, los sitios web pueden identificar la verdadera identidad de los visitantes. Los usuarios de navegadores con huella digital pueden personalizar sus características para ocultar su verdadera identidad.
En comparación con los navegadores sin cabeza normales, los navegadores antidetección se centran más en simular el comportamiento de navegación de los usuarios reales y generar características de huellas dactilares del navegador similares a las de los usuarios reales. El objetivo es eludir el mecanismo anti rastreo del sitio web y ocultar la identidad del rastreador en la medida de lo posible, con el fin de mejorar la tasa de éxito del rastreo. Actualmente, los principales navegadores antidetección admiten el modo headless.
En la siguiente sección, utilizaremos Selenium WebDriver para reconstruir nuestra actividad de rastreo anterior de acuerdo con los requisitos de rastreo reales, como las huellas dactilares personalizadas, eludir los mecanismos anti rastreo, verificar automáticamente Cloudflare, etc.
Añadir dependencia selenium-java
// gradle => build.gradle => dependencies
implementation "org.seleniumhq.selenium:selenium-java:4.14.1"
Descargando el navegador de huellas dactilares Nstbrowser y registrando una cuenta, ¡podrás disfrutarlo gratis!
La funcionalidad del lado del cliente está disponible para experimentar, pero necesitamos funcionalidad relacionada con la automatización. Puedes consultar la documentación de la API.
De acuerdo con la documentación de la interfaz, necesitamos generar y copiar nuestra API Key por adelantado:
Scraper.class
import io.xxx.basic.ScrapeMeProduct;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
public class NstbrowserScraper {
// scrapeme site url
private static final String SCRAPEME_SITE_URL = "https://scrapeme.live/shop";
// Nstbrowser LaunchNewBrowser api url
private static final String NSTBROWSER_LAUNCH_BROWSER_API = "http://127.0.0.1:8848/api/agent/devtool/launch";
/**
* Launches a new browser instance using the Nstbrowser LaunchNewBrowser API.
*/
public static void launchBrowser(String port) throws Exception {
String config = buildLaunchNewBrowserQueryConfig(port);
String launchUrl = NSTBROWSER_LAUNCH_BROWSER_API + "?config=" + config;
URL url = new URL(launchUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
// set request headers
conn.setRequestProperty("User-Agent", "Mozilla/5.0");
conn.setRequestProperty("Accept-Language", "en-US,en;q=0.5");
conn.setRequestProperty("x-api-key", "your Nstbrowser api key");
conn.setDoOutput(true);
try (BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()))) {
String inputLine;
StringBuilder response = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
// deal with response ...
}
}
/**
* Builds the JSON configuration for launching a new browser instance.
*
*/
private static String buildLaunchNewBrowserQueryConfig(String port) {
String jsonParam = """
{
"once": true,
"headless": false,
"autoClose": false,
"remoteDebuggingPort": %port,
"fingerprint": {
"name": "test",
"kernel": "chromium",
"platform": "mac",
"kernelMilestone": "120",
"hardwareConcurrency": 10,
"deviceMemory": 8
}
}
""";
jsonParam = jsonParam.replace("%port", port);
return URLEncoder.encode(jsonParam, StandardCharsets.UTF_8);
}
/**
* Scrapes product data from the Scrapeme website using Nstbrowser headless browser.
*
*/
public static List<ScrapeMeProduct> scrape(String port) {
ChromeOptions options = new ChromeOptions();
// enable headless mode
options.addArguments("--headless");
// set driver path
System.setProperty("webdriver.chrome.driver", "your chrome webdriver path");
System.setProperty("webdriver.http.factory", "jdk-http-client");
// create options
// debuggerAddress
options.setExperimentalOption("debuggerAddress", "127.0.0.1:" + port);
options.addArguments("--remote-allow-origins=*");
WebDriver driver = new ChromeDriver(options);
driver.get(SCRAPEME_SITE_URL);
// products data
List<ScrapeMeProduct> pokemonProducts = new ArrayList<>();
List<WebElement> products = driver.findElements(By.cssSelector("li.product"));
for (WebElement product : products) {
ScrapeMeProduct pokemonProduct = new ScrapeMeProduct();
pokemonProduct.setUrl(product.findElement(By.tagName("a")).getAttribute("href")); // parse and set product url
pokemonProduct.setImage(product.findElement(By.tagName(("img"))).getAttribute("src")); // parse and set product image
pokemonProduct.setName(product.findElement(By.tagName(("h2"))).getText()); // parse and set product name
pokemonProduct.setPrice(product.findElement(By.tagName(("span"))).getText()); // parse and set product price
pokemonProducts.add(pokemonProduct);
}
// quit browser
driver.quit();
return pokemonProducts;
}
public static void main(String[] args) {
// browser remote debug port
String port = "9222";
try {
launchBrowser(port);
} catch (Exception e) {
throw new RuntimeException(e);
}
List<ScrapeMeProduct> products = scrape(port);
products.forEach(System.out::println);
}
}
Este blog describe brevemente cómo utilizar programas Java para el rastreo de sitios web en Programación Concurrente y Anti-Detection Browser.
Al mostrar cómo utilizar Nstbrowser Anti-Detection Browser para el rastreo de datos y proporcionar ejemplos de código detallados, ¡seguramente le dará una comprensión más profunda de la información y las operaciones de Java, Headless Browser y Fingerprint Browser!