【Java】Jsoup 解析HTML报告
一、需求背景
有好几种报告文件,目前是人肉找报告信息填到Excel上生成统计信息
跟用户交流了下需求和提供的几个文件,发现都是html文件
其实所谓的报告的文件,就是一些本地可打开的静态资源,里面也有js、img等等
二、方案选型
前面老板一直说是文档解析,我寻思这不就是写爬虫吗....
因为是在现有系统上加新功能实现,现有系统还是Java做后端服务,所以之前学的Python就不想用了
写Python还需要单独起个服务部署起来,Java有JSOUP能用,没Python那么好用就是...
三、落地实现
1、JSOUP依赖坐标:
<!-- https://mvnrepository.com/artifact/org.jsoup/jsoup -->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.18.1</version>
</dependency>
2、文件读取问题
我发现每种类型的报告文件的存放方式都不一样
第一种单HTML文件:
这种相对简单,只需要读取路径后直接访问文件内容即可
String reportFilePath = "C:/Users/Administrator/Desktop/report-type/xxx.html";
String htmlContent = new String(Files.readAllBytes(Paths.get(reportFilePath)), StandardCharsets.UTF_8);
Document doc = Jsoup.parse(htmlContent);
第二种单Zip压缩文件:
单层压缩,可以通过zipFile的API访问,取出压缩条目一个个用条目名称进行判断
再通过zipFile打开读取流对该条目进行读取
String targetFile = "index.html";
ZipEntry targetEntry = null;
String reportFilePath = "C:/Users/Administrator/Desktop/report-type/xxxhtml.zip";
ZipFile zipFile = isWinSys() ? new ZipFile(new File(reportFilePath), ZipFile.OPEN_READ, Charset.forName("GBK")) : new ZipFile(reportFilePath);
Enumeration<? extends ZipEntry> zipEntries = zipFile.entries();
while (zipEntries.hasMoreElements()) {
ZipEntry zipEntry = zipEntries.nextElement();
boolean isDirectory = zipEntry.isDirectory();
if (isDirectory) continue;
String name = zipEntry.getName();
if (targetFile.equals(name)) {
targetEntry = zipEntry;
break;
}
}
boolean hasFind = Objects.nonNull(targetEntry);
if (!hasFind) return; /* 没有可读取的目标文件 */
InputStream inputStream = zipFile.getInputStream(targetEntry);
String htmlCode = IoUtil.readUtf8(inputStream);
Document doc = Jsoup.parse(htmlCode);
执行完成后记得要释放资源:
/* 资源释放 */
inputStream.close();
zipFile.close();
第三种多Zip嵌套压缩文件:
文件被压缩了两次,要解压两边才可以访问
1、读取内嵌的Zip文件时发现MALFORM报错,需要根据操作系统设置读取编码...
https://blog.csdn.net/qq_25112523/article/details/136060946
然后在创建ZipFile对象的API加了一个操作系统的判断
public static boolean isWinSys() {
String property = System.getProperty("os.name");
return property.contains("win") || property.contains("Win");
}
2、ZipFile只对单层压缩有用,如果是嵌套的压缩文件就不支持了
这个报告文件的情况是第一层只有一个条目,所以上传上来的文件我只关心里面只有一个内嵌的压缩文件就行
当匹配这个条件交给ZipFile读取输入流,转换成Zip输入流,否则不处理
可以在下面代码看到,对被压缩的文件进行inputStream读取后,要改用ZipInputStream读取
zipInputStream 等效 zipFile + zipEntries的合体,包含了条目迭代信息
但是只有一个getNextEntry方法,只能写While循环不断判断下一个条目是否还存在
文件名叫report.html,判断条目名是否匹配后结束循环
再利用IO工具类直接读取ZipInputStream即可 (getNextEntry方法就是让ZipInputStream不断切换到当前条目的引用)
如果要处理复杂情况要在While里面才能实现的,建议每个条目结束之后调用closeEntry方法
String targetSuffix = ".zip";
String targetFile = "report.html";
String reportFilePath = "C:/Users/Administrator/Desktop/report-type/xx_20240729153751.zip";
ZipFile zipFile = isWinSys() ? new ZipFile(new File(reportFilePath), ZipFile.OPEN_READ, Charset.forName("GBK")) : new ZipFile(reportFilePath);
Enumeration<? extends ZipEntry> enumeration = zipFile.entries();
/* 转换成集合条目,迭代条目不能判断size */
List<ZipEntry> zipEntrieList = new ArrayList<>();
while (enumeration.hasMoreElements()) {
ZipEntry zipEntry = enumeration.nextElement();
zipEntrieList.add(zipEntry);
}
/* 只有1个zip压缩文件时才处理 */
if (CollectionUtils.isEmpty(zipEntrieList)) return;
boolean isOnlyOneEntry = zipEntrieList.size() == 1;
boolean anyMatch = zipEntrieList.stream().anyMatch(ze -> ze.getName().endsWith(targetSuffix));
if (!isOnlyOneEntry || !anyMatch) return;
ZipEntry zipEntry = zipEntrieList.get(0);
/* 通过ZipInputStream不断切换条目找到目标文件 */
InputStream inputStream = zipFile.getInputStream(zipEntry);
ZipInputStream zipInputStream = new ZipInputStream(inputStream);
/* 在内层中寻找目标文件 */
ZipEntry reportEntry = zipInputStream.getNextEntry();
while (Objects.nonNull(reportEntry)) {
String name = reportEntry.getName();
if (targetFile.equals(name)) break;
reportEntry = zipInputStream.getNextEntry();
}
String htmlCode = IoUtil.readUtf8(zipInputStream);
Document doc = Jsoup.parse(htmlCode);
同样这里也需要释放资源:
/* 资源释放 */
zipInputStream.close();
inputStream.close();
zipFile.close();
3、常见查询API使用
一、常见API方法
下班到家才反应过来ownText是元素自己的文本内容,过滤掉其他嵌套的元素文本
也可以直接使用cssQuery
doc.select("table.y-report-ui-report-info-grid")
二、使用兄弟元素查找对应关系
有一个特殊的情况就是有些元素按文档结构应该是一个逐层关联的结构
先有A,然后B在A里面,C又在B里面这样
但是这个是摊开来的结构,A -> B -> C -> D,元素id和类名也没用直接关系,这样是很难构建关联的
只能通过元素的顺序推断结构:
1、获取当前ip标题元素和下一个ip标题元素的兄弟元素下标值
2、将idp元素的兄弟元素下标值取出
3、比较idp元素是否在两者之间,如果为是表示idp元素属于第一个ip标题元素
【Java】Jsoup 解析HTML报告的更多相关文章
- json-lib-2.4-jdk15.jar所需全部JAR包.rar java jsoup解析开彩网api接口json数据实例
json-lib-2.4-jdk15.jar所需全部JAR包.rar java jsoup解析开彩网api接口json数据实例 json-lib-2.4-jdk15.jar所需全部JAR包.rar ...
- [java] jsoup 解析网页获取省市区域信息
到国家统计局抓取数据, 到该class下解析数据 /** * jsoup解析网页 * @author xwolf * @date 2016-12-13 18:11 * @since V1.0.0 */ ...
- jsoup Java HTML解析器:使用选择器语法来查找元素
jsoup Java HTML解析器:使用选择器语法来查找元素 使用选择器语法来查找元素 问题 你想使用类似于CSS或jQuery的语法来查找和操作元素. 方法 可以使用Element.select( ...
- atitit. java jsoup html table的读取解析 总结
atitit. java jsoup html table的读取解析 总结 1. 两个大的parser ,,,jsoup 跟个 htmlparser 1 2. 资料比较 1 3. jsoup越佳. ...
- Java爬虫系列三:使用Jsoup解析HTML
在上一篇随笔<Java爬虫系列二:使用HttpClient抓取页面HTML>中介绍了怎么使用HttpClient进行爬虫的第一步--抓取页面html,今天接着来看下爬虫的第二步--解析抓取 ...
- [java] jsoup使用简介-汇率换算器实现-插曲2
[java] jsoup使用简介-汇率换算器实现-插曲2 // */ // ]]> [java] jsoup使用简介-汇率换算器实现-插曲2 Table of Contents 1 系列文章 ...
- jsoup解析HTML及简单实例
jsoup 中文参考文献 http://www.open-open.com/jsoup/ 本文将利用jsoup,简单实现网络抓取的功能,并给出一个小实例,该实例效果为:获取作者本人在博客园写的所 ...
- Android开发探秘之三:利用jsoup解析HTML页面
这节主要是讲解jsoup解析HTML页面.由于在android开发过程中,不可避免的涉及到web页面的抓取,解析,展示等等,所以,在这里我主要展示下利用jsoup jar包来抓取cnbeta.com网 ...
- 一步步教你为网站开发Android客户端---HttpWatch抓包,HttpClient模拟POST请求,Jsoup解析HTML代码,动态更新ListView
本文面向Android初级开发者,有一定的Java和Android知识即可. 文章覆盖知识点:HttpWatch抓包,HttpClient模拟POST请求,Jsoup解析HTML代码,动态更新List ...
- Jsoup 解析 HTML
Jsoup 文档 方法 要取得一个属性的值,可以使用Node.attr(String key) 方法 对于一个元素中的文本,可以使用Element.text()方法 对于要取得元素或属性中的HTML内 ...
随机推荐
- Redis 常用的数据结构简介与实例测试【Redis 系列二】
〇.都有哪些数据结构? Redis 提供了较为丰富的数据类型,常见的有五种:String(字符串),Hash(哈希),List(列表),Set(集合).Zset(有序集合). 随着 Redis 版本的 ...
- ts symbol 作为属性名
在 TypeScript 中,如果你希望在一个对象中使用某个 symbol 作为属性名,你必须使用中括号 [] 括起来,并在括号中放入该 symbol. 这是因为当你使用 symbol 作为属 ...
- Linux扩展篇-shell编程(八)-shell字符串截取
shell字符串截取,一般包含从指定位置和从指定字符截取. 一.从指定位置截取 1) 从字符串左边开始计数 格式: ${string: start :length} 从 string 字符串的左边第 ...
- #define 用法解法
宏的优点是能整理代码 缺点是宏替换来的代码需要人工解除宏后才能暴漏源代码 改底层时候是需要解一解的 它可以做到main函数中只有一个宏字母 这个宏定义时候,定义成了几个函数的源代码 所以main里面就 ...
- JAVA RSA 私钥签名 公钥验证签名 公钥验签
JAVA RSA 私钥签名 公钥验证签名 公钥验签 1.待签名字符串转为byte数组时,一般使用UTF8. 2.将私钥字符串(PKCS8格式)转为PKCS8EncodedKeySpec对象. 3.使用 ...
- 高性能版本的零内存分配LikeString函数(ZeroMemAllocLikeOperator)
继上一篇文章在.NET Core,除了VB的LikeString,还有其它方法吗?(四种LikeString实现分享)分享了四种实现方式,笔者对这四种实现方式,不管是执行性能还是内存分配性能上,都不太 ...
- gitlab自动定时备份文件,备份失败发送邮件
一.需求 为预防gitlab出现故障,每天定时备份,备份完成后把之前的备份文件删除,备份成功或失败的时候自动发送邮件提醒,这里的gitlab为docker部署. 二.备份命令准备 1)备份命令 创建一 ...
- Oracle常用统计
测试, 这是测消息 1.按天 select to_char(t.STARTDATE+15/24, 'YYYY-MM-DD') as 天,sum(1) as 数量from HOLIDAY tgroup ...
- java实现微信登录
前言 上一篇做了php的微信登录,所以也总结一下Java的微信授权登录并获取用户信息这个功能的开发流程. 配置 配置什么的就不多说了,详细的配置可以直接前往我上一篇查看. https://www.cn ...
- TI AM64x工业核心板规格书(双核ARM Cortex-A53 + 单/四核Cortex-R5F + 单核Cortex-M4F,主频1GHz)
1 核心板简介 创龙科技SOM-TL64x是一款基于TI Sitara系列AM64x双核ARM Cortex-A53 + 单/四核Cortex-R5F + 单核Cortex-M4F设计的多核工业级核心 ...