有需要用到jsoup来获取数据,因为之前没有用过,所以就想写一个入门案例来巩固一下,这个案例的功能是爬取豆瓣电影Top250的电影数据(电影名称,简介,评分,评价等),并且将数据存到Excel表格中。
这是豆瓣电影Top250的网址,https://movie.douban.com/top250?start=0
用到的技术有jsoup和apche poi
jsoup用来获取数据,apche poi用来将获取到的数据存入Excel表格
Jsoup 是一个用于解析、提取和操作 HTML 文档的 Java 库。它提供了简单且易于使用的 API,使您能够轻松地从 HTML 页面中提取数据。
Jsoup 可以加载 HTML 文档并将其解析为文档对象模型(DOM),能够轻松地遍历和操作文档中的元素和内容。
Apache POI 是一个用于操作 Microsoft Office 格式文件(如 Word 文档、Excel 表格和 PowerPoint 演示文稿)的 Java 库。它提供了一组丰富的 API,使得在 Java 程序中读取、写入和修改 Office 文件变得简单和方便。
<dependency>
<groupId>org.jsoupgroupId>
<artifactId>jsoupartifactId>
<version>1.14.1version>
dependency>
<dependency>
<groupId>org.apache.poigroupId>
<artifactId>poiartifactId>
<version>5.0.0version>
dependency>
<dependency>
<groupId>org.apache.poigroupId>
<artifactId>poi-ooxmlartifactId>
<version>5.0.0version>
dependency>
说这么多作用不大,我们直接来实操一下就能明白了。
下面是一个jsoup入门的demo,只需要把jsoup相关的依赖导入即可开始使用。
这个Demo的功能是,获取百度网站的标题:百度一下,你就知道
package com.example;
import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import java.io.IOException;
public class Test5 {
public static void main(String[] args) throws IOException {
Connection connection = Jsoup.connect("https://www.baidu.com/")
//这里可以加各种请求头,来模拟真实的请求(根据自己的真实请求来加)
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36")
.method(Connection.Method.GET);
Document doc = connection.get();
//获取标题元素
Element title = doc.select("head").select("title").first();
System.out.println(title);
//获取元素内容
String titleName = title.text();
System.out.println(titleName);
}
}
Jsoup 提供了许多常用的 API,使您能够解析、操作和提取 HTML 文档中的数据。以下是一些常用的 Jsoup API:
connect(String url)
方法可以建立与指定 URL 的连接。例如:Connection connection = Jsoup.connect("http://www.example.com");
get()
方法可以获取连接的 URL 对应的文档对象。例如:Document doc = Jsoup.connect("http://www.example.com").get();
parse(String html)
方法可以将 HTML 字符串解析为文档对象。例如:String html = "Hello, Jsoup!
";
Document doc = Jsoup.parse(html);
getElementById(String id)
: 通过元素的 ID 获取元素。getElementsByTag(String tagName)
: 通过标签名获取元素。getElementsByClass(String className)
: 通过类名获取元素。select(String cssQuery)
: 使用 CSS 选择器选择元素。例如:
Element element = doc.getElementById("myElement");
Elements elements = doc.getElementsByTag("a");
Elements elements = doc.getElementsByClass("myClass");
Elements elements = doc.select("div.container > p");
text()
: 获取元素的文本内容。attr(String attributeKey)
: 获取元素指定属性的值。absUrl(String attributeKey)
: 获取元素指定属性的绝对路径 URL。例如:
String text = element.text();
String href = element.attr("href");
String imgUrl = element.absUrl("src");
clean(String bodyHtml)
方法可以清理 HTML 内容并生成干净和规范的 HTML。例如:String html = "Hello Jsoup!
";
String cleanHtml = Jsoup.clean(html, Whitelist.basic());
重点关注一下选择器的内容:
选择器是 Jsoup 中非常强大和灵活的功能,它允许您使用类似于 CSS 选择器的语法来选择和查找 HTML 文档中的元素。通过使用选择器,您可以根据元素的标签名、类名、ID、属性等进行定位和筛选。
以下是一些常用的选择器示例:
Elements elements = doc.select("a");
Elements elements = doc.select(".myClass");
Element element = doc.select("#myElement").first();
Elements elements = doc.select("a[href]");
Elements elements = doc.select("a[href^=http://www.example.com]");
元素:
Elements elements = doc.select("div > p");
元素:
Elements elements = doc.select("div p");
Element element = doc.select("a").first();
我们知道,像页面中是有很多标签的,我们需要从这些标签中分析并取到自己想要的内容。
jsoup中是提供了api来调用取这些元素跟内容的,我们只需要分析页面标签的层级,一层一层去取自己想要的内容就可以了(关于这个案例,可以直接去代码中调试,很容易就能看明白了,使用文字反而不好讲清楚),这个过程类似于dom文档解析。
这里我们可以看到直接取到了25个电影的详情标签元素了,接下来就分析拿数据就可以了。
代码是比较简单的,重要的是分析过程,过程会分析了,就能爬取任何想要的内容了。
我以豆瓣top250电影为例来进行分析:
https://movie.douban.com/top250
我们可以看到这里是标题,下面就是内容列表了
分析方法:
1、直接按F12打开开发者模式,点击这个元素选择,直接在页面上选取你想获取到的内容,就可以看到他在标签中的位置了。
然后就是通过一层一层的标签选择,取到某一个element,然后获取他里面的内容。
2、我们看到的只是当前页面的东西,当前是第1页,那么我们该如何获取所有页,比如10页,100页的全部内容呢?
这时候就需要分析跳转下一页时,当前网站的url变化了
我们可以看到第一页start=0,第二页start=25,因为一页有25条内容,所以每次多25
到这里我们就知道了,获取完当前页内容后,让这个start这个参数的值+25,放到下一次请求的url中就可以了。
public class FinalMovie {
public static void main(String[] args) throws IOException, ParseException, InterruptedException {
int num=0;
String url="https://movie.douban.com/top250?start=";
//循环获取10页的全部内容
for (int i = 0; i < 10; i++) {
getMovie(url+num);
//睡眠两秒
Thread.sleep(2000);
num=num+25;
}
}
/**
* 获取豆瓣top250电影信息
* @param url 目标网站网址
* @throws IOException
* @throws ParseException
*/
private static void getMovie(String url) throws IOException, ParseException {
Connection connection = Jsoup.connect(url)
//真实的User-Agent
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36")
.method(Connection.Method.GET);
Document doc = connection.get();
//取id为content下面的h1
Elements select = doc.select("#content > h1");
String title = select.text();
//System.out.println(title);
Elements ol = doc.select("div.article > ol");
//System.out.println(ol);
//遍历 ol 中的每个 - 标签
List<Movie> movieList=new ArrayList<Movie>();
for (Element li : ol) {
//取li标签下的所有
Elements infos = li.select("div.item").select("div.info");
//System.out.println(info);
for (Element info : infos) {
Movie movie=new Movie();
//ArrayList nameList=new ArrayList();
//取所有的div class=hd
Elements hds = info.select("div.hd");
//往里进一层,取hd中的a
Elements as = hds.select("a[href]");
for (Element a : as) {
Element element = a.selectFirst("span.title");
String name = element.text();
//System.out.println(name);
movie.setMovieName(name);
//nameList.add(name);
}
//取所有的div class=bd
Elements bds = info.select("div.bd");
for (Element bd : bds) {
Element p = bd.selectFirst("p");
String text = p.text();
movie.setMovieIntroduce(text);
//System.out.println(p);
Elements stars = bd.select("div.star");
for (Element star : stars) {
Element child = star.child(1);
//System.out.println(child);
String text1 = child.text();
movie.setMovieStar(text1);
}
Element p1 = bd.select("p").last();
//System.out.println(p1);
String text1 = p1.text();
movie.setMovieEvaluate(text1);
}
//System.out.println(movie.toString());
movieList.add(movie);
}
}
for (Movie movie : movieList) {
System.out.println(movie);
}
//将数据写入到excel表中
writeDataToExcel(movieList,"moviesData.xlsx",true);
System.out.println("15条数据写入excel成功");
}
}
将集合插入excel表格代码:
/**
* 将集合数据写入到Excel中
* @param dataList 数据集合
* @param outputPath 输出路径
* @param append 是否追加数据,true为追加,false为覆盖
* @throws IOException
*/
public static void writeDataToExcel(List<?> dataList, String outputPath, boolean append) throws IOException {
Workbook workbook;
if (append && new File(outputPath).exists()) {
workbook = WorkbookFactory.create(new FileInputStream(outputPath));
} else {
workbook = new XSSFWorkbook();
}
Sheet sheet = workbook.getSheet("Sheet1");
if (sheet == null) {
sheet = workbook.createSheet("Sheet1");
}
// 获取当前已有数据的最后一行索引
int lastRow = sheet.getLastRowNum();
// 创建表头(仅当需要创建新的表格时)
if (lastRow == 0) {
Row headerRow = sheet.createRow(0);
Object firstData = dataList.get(0);
List<String> propertiesList = getObjectPropertiesList(firstData);
for (int i = 0; i < propertiesList.size(); i++) {
Cell cell = headerRow.createCell(i);
cell.setCellValue(propertiesList.get(i));
}
}
int rowCount = lastRow + 1;
// 写入数据
for (Object data : dataList) {
Row dataRow = sheet.createRow(rowCount++);
int colCount = 0;
List<String> propertiesList = getObjectPropertiesList(data);
for (String property : propertiesList) {
Cell cell = dataRow.createCell(colCount++);
Object value = getObjectPropertyValue(data, property);
if (value != null) {
if (value instanceof String) {
cell.setCellValue((String) value);
} else if (value instanceof Number) {
cell.setCellValue(((Number) value).doubleValue());
} else if (value instanceof Date) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
cell.setCellValue(dateFormat.format((Date) value));
}
// 根据需要添加其他数据类型的处理逻辑
}
}
}
FileOutputStream outputStream = new FileOutputStream(outputPath);
workbook.write(outputStream);
workbook.close();
outputStream.close();
}
/**
* 获取对象的属性列表
* @param object 对象
* @return 属性列表
*/
private static List<String> getObjectPropertiesList(Object object) {
List<String> propertiesList = new ArrayList<>();
Field[] fields = object.getClass().getDeclaredFields();
for (Field field : fields) {
propertiesList.add(field.getName());
}
return propertiesList;
}
/**
* 获取对象的属性值
* @param object 对象
* @param property 属性名
* @return 属性值
*/
private static Object getObjectPropertyValue(Object object, String property) {
try {
Field field = object.getClass().getDeclaredField(property);
field.setAccessible(true);
return field.get(object);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
电影实体类:
(这里我为了方便就简单的都设置成了string类型)
package com.example.domain;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
public class Movie {
//电影名称
private String movieName;
//电影介绍
private String movieIntroduce;
//电影评分
private String movieStar;
//电影评价
private String movieEvaluate;
public Movie() {
}
public Movie(String movieName, String movieIntroduce, String movieStar, String movieEvaluate) {
this.movieName = movieName;
this.movieIntroduce = movieIntroduce;
this.movieStar = movieStar;
this.movieEvaluate = movieEvaluate;
}
@Override
public String toString() {
return "Movie{" +
"movieName='" + movieName + '\'' +
", movieIntroduce='" + movieIntroduce + '\'' +
", movieStar='" + movieStar + '\'' +
", movieEvaluate='" + movieEvaluate + '\'' +
'}';
}
public String getMovieName() {
return movieName;
}
public void setMovieName(String movieName) {
this.movieName = movieName;
}
public String getMovieIntroduce() {
return movieIntroduce;
}
public void setMovieIntroduce(String movieIntroduce) {
this.movieIntroduce = movieIntroduce;
}
public String getMovieStar() {
return movieStar;
}
public void setMovieStar(String movieStar) {
this.movieStar = movieStar;
}
public String getMovieEvaluate() {
return movieEvaluate;
}
public void setMovieEvaluate(String movieEvaluate) {
this.movieEvaluate = movieEvaluate;
}
}
错误写法:
String text = hd.select(“a[href]”).select(“span.title”).text();
System.out.println(text);
这样就会把所有电影名称放到一块进行输出,就像这样:
四、附录(完整的可以直接运行的代码)
把实体类粘进去即可。
package com.example;
import com.example.domain.Movie;
import com.example.domain.Product;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class FinalMovie {
public static void main(String[] args) throws IOException, ParseException, InterruptedException {
int num=0;
String url="https://movie.douban.com/top250?start=";
//循环获取10页的全部内容
for (int i = 0; i < 10; i++) {
getMovie(url+num);
//睡眠两秒
Thread.sleep(2000);
num=num+25;
}
}
/**
* 获取豆瓣top250电影信息
* @param url 目标网站网址
* @throws IOException
* @throws ParseException
*/
private static void getMovie(String url) throws IOException, ParseException {
Connection connection = Jsoup.connect(url)
//真实的User-Agent
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36")
.method(Connection.Method.GET);
Document doc = connection.get();
//取id为content下面的h1
Elements select = doc.select("#content > h1");
String title = select.text();
//System.out.println(title);
Elements ol = doc.select("div.article > ol");
//System.out.println(ol);
//遍历 ol 中的每个 - 标签
List<Movie> movieList=new ArrayList<Movie>();
for (Element li : ol) {
//取li标签下的所有
Elements infos = li.select("div.item").select("div.info");
//System.out.println(info);
for (Element info : infos) {
Movie movie=new Movie();
//ArrayList nameList=new ArrayList();
//取所有的div class=hd
Elements hds = info.select("div.hd");
//往里进一层,取hd中的a
Elements as = hds.select("a[href]");
for (Element a : as) {
Element element = a.selectFirst("span.title");
String name = element.text();
//System.out.println(name);
movie.setMovieName(name);
//nameList.add(name);
}
//取所有的div class=bd
Elements bds = info.select("div.bd");
for (Element bd : bds) {
Element p = bd.selectFirst("p");
String text = p.text();
movie.setMovieIntroduce(text);
//System.out.println(p);
Elements stars = bd.select("div.star");
for (Element star : stars) {
Element child = star.child(1);
//System.out.println(child);
String text1 = child.text();
movie.setMovieStar(text1);
}
Element p1 = bd.select("p").last();
//System.out.println(p1);
String text1 = p1.text();
movie.setMovieEvaluate(text1);
}
//System.out.println(movie.toString());
movieList.add(movie);
}
}
for (Movie movie : movieList) {
System.out.println(movie);
}
//将数据写入到excel表中
writeDataToExcel(movieList,"moviesData2.xlsx",true);
System.out.println("15条数据写入excel成功");
}
/**
* 将集合数据写入到Excel中
* @param dataList 数据集合
* @param outputPath 输出路径
* @param append 是否追加数据,true为追加,false为覆盖
* @throws IOException
*/
public static void writeDataToExcel(List<?> dataList, String outputPath, boolean append) throws IOException {
Workbook workbook;
if (append && new File(outputPath).exists()) {
workbook = WorkbookFactory.create(new FileInputStream(outputPath));
} else {
workbook = new XSSFWorkbook();
}
Sheet sheet = workbook.getSheet("Sheet1");
if (sheet == null) {
sheet = workbook.createSheet("Sheet1");
}
// 获取当前已有数据的最后一行索引
int lastRow = sheet.getLastRowNum();
// 创建表头(仅当需要创建新的表格时)
if (lastRow == 0) {
Row headerRow = sheet.createRow(0);
Object firstData = dataList.get(0);
List<String> propertiesList = getObjectPropertiesList(firstData);
for (int i = 0; i < propertiesList.size(); i++) {
Cell cell = headerRow.createCell(i);
cell.setCellValue(propertiesList.get(i));
}
}
int rowCount = lastRow + 1;
// 写入数据
for (Object data : dataList) {
Row dataRow = sheet.createRow(rowCount++);
int colCount = 0;
List<String> propertiesList = getObjectPropertiesList(data);
for (String property : propertiesList) {
Cell cell = dataRow.createCell(colCount++);
Object value = getObjectPropertyValue(data, property);
if (value != null) {
if (value instanceof String) {
cell.setCellValue((String) value);
} else if (value instanceof Number) {
cell.setCellValue(((Number) value).doubleValue());
} else if (value instanceof Date) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
cell.setCellValue(dateFormat.format((Date) value));
}
// 根据需要添加其他数据类型的处理逻辑
}
}
}
FileOutputStream outputStream = new FileOutputStream(outputPath);
workbook.write(outputStream);
workbook.close();
outputStream.close();
}
/**
* 获取对象的属性列表
* @param object 对象
* @return 属性列表
*/
private static List<String> getObjectPropertiesList(Object object) {
List<String> propertiesList = new ArrayList<>();
Field[] fields = object.getClass().getDeclaredFields();
for (Field field : fields) {
propertiesList.add(field.getName());
}
return propertiesList;
}
/**
* 获取对象的属性值
* @param object 对象
* @param property 属性名
* @return 属性值
*/
private static Object getObjectPropertyValue(Object object, String property) {
try {
Field field = object.getClass().getDeclaredField(property);
field.setAccessible(true);
return field.get(object);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
}