使用Selenium和Jsoup框架进行Java爬虫
Java爬虫示例:使用Selenium和Jsoup框架爬取NASA网站
在Java中使用Selenium和Jsoup框架编写爬虫,可以结合Selenium模拟浏览器行为获取动态加载的内容,然后利用Jsoup解析HTML结构并抽取所需数据。基本步骤如下:
步骤1:环境准备与依赖引入
安装Selenium WebDriver:首先需要下载对应浏览器(如Chrome或Firefox)的WebDriver,并配置到系统的PATH环境变量中。
chrome://settings/help,更新chrome浏览器浏览器最新版本,从以下页面下载对应版本的驱动。可以直接加到代码中,也可以添加到path环境变量中。
Chrome for Testing availability:https://googlechromelabs.github.io/chrome-for-testing/#stable
我这里解压后,直接把地址放项目代码里面了,如果经常用,可以放path环境变量里面
// chrome驱动地址
String webDriverPath = "D:\\Environments\\chromedriver-win64\\chromedriver.exe";
Maven或Gradle项目依赖:
- 引入Selenium库(以Maven为例):
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>latest_version</version> <!-- 替换为最新稳定版 -->
</dependency>
- 引入Jsoup库:
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>latest_version</version> <!-- 替换为最新稳定版 -->
</dependency>
本示例使用的是:
<!-- https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-java -->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.17.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.jsoup/jsoup -->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.17.2</version>
</dependency>
步骤2:初始化WebDriver对象
- 创建一个WebDriver实例来驱动浏览器,例如使用ChromeDriver:
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
// 设置ChromeDriver路径(如果不在系统PATH中)
System.setProperty("webdriver.chrome.driver", "path/to/chromedriver.exe");
// 初始化WebDriver
WebDriver driver = new ChromeDriver();
步骤3:访问目标网站并等待页面加载完成
- 使用get()方法打开指定URL:
driver.get("http://example.com");
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10)); // 使用Java 8+的时间API创建Duration对象
// 等待页面加载完成的一个通用做法是等待某个代表页面内容加载完毕的元素出现或变为可见
wait.until(ExpectedConditions.presenceOfElementLocated(By.tagName("body")));
// 或者等待特定元素加载完成
// WebElement element = wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("main-content")));
步骤4:获取页面源代码或动态生成的内容
- 如果页面内容是通过JavaScript动态加载的,则需确保所有内容加载完毕后,再获取页面源代码:
String pageSource = driver.getPageSource();
只获取到HTML文档 标签内的所有内容(包括子元素及其文本),而不是整个HTML文档(即不包含部分和其他顶级元素)时,使用如下代码:
JavascriptExecutor jsExecutor = (JavascriptExecutor) driver;
String pageSource = (String) jsExecutor.executeScript("return document.body.innerHTML;");
这种方式对于动态渲染且仅需抓取可视区域或body内部数据时更为精确或者高效。
步骤5:使用Jsoup解析HTML文档
- 将从WebDriver获取的页面源代码转换为Jsoup的Document对象进行解析:
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
Document doc = Jsoup.parse(pageSource);
步骤6:定位并提取数据
- 使用Jsoup提供的API根据CSS选择器或其他方式定位元素并提取数据:
// 例如,提取所有文章标题
Elements titles = doc.select("h2.title");
for (Element title : titles) {
String articleTitle = title.text();
System.out.println(articleTitle);
}
这里就是分析目标网页的特征,看看真实的图片链接是怎么怎样的
https://www.nasa.gov/image-of-the-day/page/8/
// 使用Jsoup提供的API根据CSS选择器或其他方式定位元素并提取数据:
List<Element> imgElements = doc.select("img");
List<Future<Void>> futures = new ArrayList<>();
for (Element imgElement : imgElements) {
// 获取srcset属性值
String imageUrl = imgElement.attr("src");
String realImageUrl = null;
System.out.println("imageUrl: " + imageUrl);
if (imageUrl.startsWith(preString) && imageUrl.endsWith(endString)) {
realImageUrl = imageUrl;
System.out.println("realImageUrl " + realImageUrl);
}
if (realImageUrl != null) {
futures.add(executor.submit(new ImageDownloaderPlus(realImageUrl, outPutPath)));
}
}
步骤7:处理异常和清理资源
在程序结束前,记得关闭WebDriver以释放浏览器资源:
driver.quit();
注意事项
- Selenium主要用于处理JavaScript渲染的内容和交互式登录等场景,对于静态网页或者不需要模拟用户交互的情况,可以直接用Jsoup抓取。
- 在实际应用中,应合理控制爬取频率,遵守网站robots.txt规则,避免对服务器造成过大压力。
完整示例代码
完整示例代码
对需要配置修改项目进行说明
// 要爬取的网页地址
String url = "https://www.nasa.gov/image-of-the-day/page/8/";
// 定义前缀,检查是否以某个开头,过滤不必要的图片
String preString = "https://www.nasa.gov/wp-content/uploads";
// 定义后缀,检查是否以某个结束,过滤不必要的图片
String endString = ".jpg";
// 输出目录,这里是项目下的images/nasa/
String outPutPath = "images/nasa/";
// chrome驱动地址
String webDriverPath = "D:\\Environments\\chromedriver-win64\\chromedriver.exe";
- url:要爬取的图片网页地址
- preString与endString:过滤掉不需要的图片,比如什么网站图标之类的,根据实际的情况来
- outPutPath:输出目录,这里是项目下的images/nasa/
- webDriverPath:前面下载解压的chrome驱动地址
最后完整的示例代码如下:
import java.io.IOException;
import java.net.URISyntaxException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
public class PixabayImageScraperPlus {
private static final ExecutorService executor = Executors.newFixedThreadPool(5); // 创建一个固定大小的线程池
public static void main(String[] args)
throws IOException, InterruptedException, URISyntaxException, ExecutionException {
// 要爬取的网页地址
String url = "https://www.nasa.gov/image-of-the-day/page/8/";
// 定义前缀,检查是否以某个开头,过滤不必要的图片
String preString = "https://www.nasa.gov/wp-content/uploads";
// 定义后缀,检查是否以某个结束,过滤不必要的图片
String endString = ".jpg";
// 输出目录,这里是项目下的images/nasa/
String outPutPath = "images/nasa/";
// chrome驱动地址
String webDriverPath = "D:\\Environments\\chromedriver-win64\\chromedriver.exe";
System.setProperty("webdriver.chrome.driver", webDriverPath);
ChromeOptions options = new ChromeOptions();
// 如果要在无界面模式下运行,但有界面有时候更方便,看实际需求
// options.addArguments("--headless");
WebDriver driver = new ChromeDriver(options);
try {
System.out.println("获取url");
driver.get(url);
// 等页面关键内容加载结束后,可以手动将网页加载结束掉,而不用一直等加载结束
System.out.println("休眠等待准备");
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10)); // 使用Java 8+的时间API创建Duration对象
// 等待页面加载完成的一个通用做法是等待某个代表页面内容加载完毕的元素出现或变为可见
wait.until(ExpectedConditions.presenceOfElementLocated(By.tagName("body")));
// // 休眠等待准备好,也可以直接设定一个时间
// Thread.sleep(100000);
System.out.println("休眠等待结束");
// 如果页面内容是通过JavaScript动态加载的,则需确保所有内容加载完毕后,再获取页面源代码:
JavascriptExecutor jsExecutor = (JavascriptExecutor) driver;
String pageSource = (String) jsExecutor.executeScript("return document.body.innerHTML;");
// 将从WebDriver获取的页面源代码转换为Jsoup的Document对象进行解析:
Document doc = Jsoup.parse(pageSource);
// 使用Jsoup提供的API根据CSS选择器或其他方式定位元素并提取数据:
List<Element> imgElements = doc.select("img");
List<Future<Void>> futures = new ArrayList<>();
for (Element imgElement : imgElements) {
// 获取srcset属性值
String imageUrl = imgElement.attr("src");
String realImageUrl = null;
System.out.println("imageUrl: " + imageUrl);
if (imageUrl.startsWith(preString) && imageUrl.endsWith(endString)) {
realImageUrl = imageUrl;
System.out.println("realImageUrl " + realImageUrl);
}
if (realImageUrl != null) {
futures.add(executor.submit(new ImageDownloaderPlus(realImageUrl, outPutPath)));
}
}
// 等待所有任务完成并设置超时时间
for (Future<Void> future : futures) {
try {
future.get(3, TimeUnit.MINUTES); // 设置每个任务的最大执行时间为3分钟
} catch (TimeoutException e) {
System.err.println("图片下载任务超时: " + future.toString());
future.cancel(true); // 超时的任务取消
}
}
// 确保所有任务执行完毕后再关闭线程池
executor.shutdown();
boolean finishedInTime = executor.awaitTermination(5, TimeUnit.MINUTES); // 总等待时间5分钟
if (!finishedInTime) {
System.err.println("在规定时间内,有些任务未完成。");
}
} finally {
// 在程序结束前,记得关闭WebDriver以释放浏览器资源:
driver.quit();
}
}
}
多线程处理下载,注意通过Thread.sleep(100);减轻服务器压力
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.concurrent.Callable;
public class ImageDownloaderPlus implements Callable<Void> {
private final String imageUrl;
private final String savePath;
ImageDownloaderPlus(String imageUrl, String savePath) {
this.imageUrl = imageUrl;
this.savePath = savePath;
}
@Override
public Void call() throws Exception {
if (!new File(savePath).exists()) {
Files.createDirectories(Paths.get(savePath));
}
Path localFile = Paths.get(savePath, imageUrl.substring(imageUrl.lastIndexOf("/") + 1));
if (Files.exists(localFile)) { // 检查文件是否已存在
System.out.println("Image already exists: " + localFile);
InputStream in = null; // 增加InputStream变量用于显式关闭
try {
URI uri = new URI(imageUrl);
in = uri.toURL().openStream(); // 打开输入流
} catch (IOException e) {
System.err.println("打开输入流失败: " + imageUrl);
return null;
} finally {
if (in != null) {
in.close(); // 当文件已存在时,显式关闭输入流
}
}
return null;
}
try (InputStream in = new URI(imageUrl).toURL().openStream()) {
Files.copy(in, localFile, StandardCopyOption.REPLACE_EXISTING);
System.out.println("Downloaded image: " + imageUrl);
} catch (IOException e) {
System.err.println("下载图片失败: " + imageUrl);
}
// 减轻服务器压力
Thread.sleep(100);
return null;
}
}
参考链接:https://www.yuque.com/ican/machine/xgufmiahx1xi3llr?#