【Java】Jsoup 解析HTML报告

news2024/11/17 6:47:19

一、需求背景

有好几种报告文件,目前是人肉找报告信息填到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标题元素

三、父子元素操作获取兄弟元素

报告明细列表,发现标题是xx名称,xx等级摘要信息,点击详情是把下一行展示出来

然后在下一行的tr中列出xx的全部信息

使用siblingIndex不准确,元素是动态的,可以第一张表10个,第二章表20个这样

所以在表格读取的时候改用 parent() + child()方式读取

在选取表格所有摘要行后,通过父元素的indexOf方法获取当前摘要行的下标

再加一就是下一个明细行的下表了

同样还可以通过当前元素的child方法直接去第N个子元素

这个方式相比select方法不用从元素集合中获取,确定是唯一的一个元素

/* 2、读取【漏洞分布】信息 */
Element vulnTable = doc.getElementById("vuln_distribution");
Element vulnTableBody = vulnTable.child(1);
Elements allTrList = vulnTableBody.children();
Elements vulnTitleTrList = vulnTable.select("tr[style='cursor:pointer;']");
for (Element vrTr : vulnTitleTrList) {
    /* 2-1、漏洞名称 */
    String vt = vrTr.child(1).text();
    int vrTrIdx = allTrList.indexOf(vrTr);
    Element vrDetailTr = allTrList.get(vrTrIdx + 1);
    Element vrDetailTableBody = vrDetailTr.child(1).child(0).child(0);
    /* 2-2、漏洞主机 */
    String ipHosts = vrDetailTableBody.child(0).child(1).text();
    ipHosts = ipHosts.replaceAll("&nbsp", "").replaceAll(" 点击查看详情;", "");
    /* 2-3、漏洞描述 */
    String vulnDesc = vrDetailTableBody.child(1).child(1).text();
    /* 2-4、威胁分值 */
    String vulnTag = vrDetailTableBody.child(3).child(1).text();
    String format = StrFormatter.format("reportTime: {}, ip: {}, name: {}, tag: {} desc: {}, ", date, ipHosts, vt, vulnTag, vulnDesc);
    System.out.println(format);
}

文章转载自:emdzz

原文链接:https://www.cnblogs.com/mindzone/p/18339177

体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1981655.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

adb环境变量配置(附详细图解)

adb环境变量配置&#xff08;附详细图解&#xff09; 1、找到ADB工具的位置。通常&#xff0c;如果你Android Studio已经安装了Android SDK&#xff0c;ADB工具位于SDK的platform-tools目录下。我这里的目录是C:\Users\user\AppData\Local\Android\Sdk\platform-tools\adb.exe…

微信防封指南请收好

一、新号与老号的添加限制 建议新注册的微信号主动添加好友的数量不宜过多&#xff0c;推荐每日添加不超过5个好友&#xff1b;对于老号&#xff0c;建议每日添加不超过20个好友。保持适度的添加速度&#xff0c;避免被系统判定为异常操作。 二、避免使用营销性词汇 在发送消…

【计算机视觉】图像处理基本知识

一 基本的图像操作和处理 1.1 PIL&#xff1a;Python图像处理类库 PIL&#xff08;Python Imaging Library&#xff0c;图像处理库&#xff09;提供了通用的图像处理功能&#xff0c;以及大量有用的基本图像操作。PIL库已经集成在Anaconda库中&#xff0c;推荐使用Anaconda&a…

openmetadata自定义连接器开发教程

openmetadata自定义连接器开发教程 一、开发通用自定义连接器教程 官网教程链接&#xff1a; 1.https://docs.open-metadata.org/v1.3.x/connectors/custom-connectors 2.https://github.com/open-metadata/openmetadata-demo/tree/main/custom-connector &#xff08;一&…

【ARM】SMMU系统虚拟化(3)_ VMSAv8-64 address translation stages

讲解颗粒度granule size如何影响地址转换的过程&#xff1a; 对于每个颗粒度来说&#xff1a; 输入的地址范围如何影响起始的lookup levels。对于stage2 转换来说&#xff0c;给链接的转换页表造成的可能的影响。TTBR 地址和indexing对于起始的lookup 1.以4KB的translation g…

初始JVM ! ! ! 相信我,中国人不骗中国人,这一篇也就入门了!!!

目录 1.JVM到底是什么&#xff1f; 2.JVM的三大核心是什么&#xff1f; 1. 解释和运行 2. 内存管理 3. 即时编译&#xff08;JIT&#xff09; 3.常见的Jvm虚拟机有哪些&#xff1f; 1.3.1 Java虚拟机规范 1.3.2 Java虚拟机规范 4. JVM的组成 4.1、类的生命周期 4.1…

Android手机端远程控制ESP32引脚电平实例

一、背景介绍 ESP32是一款高度集成的低功耗系统级芯片&#xff0c;它结合了双核处理器、无线通信、低功耗特性和丰富的外设&#xff0c;适用于各种物联网&#xff08;IoT&#xff09;应用&#xff0c;如果与Android结合&#xff0c;在手机端远程控制ESP32引脚高低电平输出&…

武汉流星汇聚:依托亚马逊平台助力初创企业出海,共创国际品牌辉煌

在全球跨境电商的浩瀚蓝海中&#xff0c;亚马逊如同一座灯塔&#xff0c;以其庞大的市场规模、强大的品牌影响力和成熟的运营体系&#xff0c;引领着无数企业扬帆出海&#xff0c;探索未知的市场机遇。对于中国卖家而言&#xff0c;亚马逊不仅是通往全球消费者的桥梁&#xff0…

一文详解:企业优化仓库管理——WMS系统

如有你有一间小仓库&#xff0c;几位工人埋头于一摞摞的纸质单据中&#xff0c;手工记录着每一次货物的进出与变动。目前看来&#xff0c;这样的时间和效率&#xff0c;作为管理者的你还可以接受。 但是&#xff0c;令你头疼的是&#xff0c;随着企业规模的扩大&#xff0c;你…

springboot信息安全技术在投票网站-计算机毕业设计源码12740

目录 1 绪论 1.1 选题背景与意义 1.2国内外研究现状 1.3论文结构与章节安排 2系统分析 2.1 可行性分析 2.2 系统流程分析 2.2.1系统开发流程 2.2.2 用户登录流程 2.2.3 系统操作流程 2.2.4 添加信息流程 2.2.5 修改信息流程 2.2.6 删除信息流程 2.3 系统功能分析 …

ai自动配音工具

AI拟音大师&#xff0c;给你的无声视频添加生动而且同步的音效 &#x1f61d;文件夹是一种基于文本的视频到音频生成框架,可以生成高质量的音频,在语义上相关,并与输入视频时间同步。 下载地址&#xff1a;https://pan.quark.cn/s/5a2be1cc5551

微服务概述及如何搭建微服务

1. 微服务架构—SpringCloud 1.1 单体应用架构 将项目所有模块【功能】打成jar包或者war包&#xff0c;然后部署一个进程 优点&#xff1a; 部署简单&#xff1a;由于是完整的结构体&#xff0c;可以直接部署在一个服务器上即可技术单一&#xff1a;项目不需要复杂的技术栈&am…

windows启动nacos时报Caused by: java.lang.UnsatisfiedLinkErro错误

Caused by: java.lang.UnsatisfiedLinkError: C:\Users\Administrator\AppData\Local\Temp\2\librocksdbjni6009210463092880400.dll: Can’t find dependent libraries 因为电脑没有vc&#xff0c;或vc版本问题&#xff0c;下载对应的vc安装就可以了 VC“2015-2022”运行库官…

基础复习(反射、注解、动态代理)

反射 反射&#xff0c;指的是加载类的字节码到内存&#xff0c;并以编程的方法解刨出类中的各个成分&#xff08;成员变量、方法、构造器等&#xff09;。 1.获取类的字节码 &#xff08;3种方式&#xff09; public class Test1Class{public static void main(String[] arg…

全球社区的建立:Facebook在跨文化交流中的角色

在全球化日益加深的今天&#xff0c;跨文化交流成为了人们日常生活中的重要部分。Facebook作为全球最大的社交网络平台之一&#xff0c;正在发挥着越来越重要的作用。通过其广泛的用户基础和丰富的功能&#xff0c;Facebook不仅连接了来自不同国家和地区的人们&#xff0c;也在…

数据迁移数亿小文件该如何做到?

在数字化转型的浪潮中&#xff0c;企业在数据管理上遇到了诸多挑战&#xff0c;尤其是面对海量小文件的数据迁移问题。这类迁移任务不仅复杂&#xff0c;而且对效率、数据一致性和完整性的要求极高。 处理数亿小文件的数据迁移并非易事。当文件数量庞大&#xff0c;尤其是文件大…

吴恩达老师机器学习-ex8

data1 导入库&#xff0c;读取数据并进行可视化 因为这次的数据是mat文件&#xff0c;需要使用scipy库中的loadmat进行读取数据。 通过对数据类型的分析&#xff0c;发现是字典类型&#xff0c;查看该字典的键&#xff0c;可以发现又X等关键字。 import numpy as np import…

matplotlib库学习之绘图透明度设置(精炼准确)

matplotlib库学习之透明颜色设置 一、简介 在数据可视化中&#xff0c;透明度设置可以使图表更具层次感&#xff0c;特别是在多层叠加图表时。matplotlib库提供了多种方法来设置图表各个部分的透明度&#xff0c;包括图形、文本、图例、坐标轴等部分。 二、为什么要设置成透明…

某PM2项目管理系统 ExcelIn.aspx 文件上传漏洞复现

FOFA:body="PM2项目管理系统BS版增强工具.zip" 访问漏洞url抓包 上传压缩包 请求包 POST /FlowChartDefine/ExcelIn.aspx HTTP/1.1 Host: Accept-Encoding: gzip, deflate Content-Type: multipart/form-data; boundary=---------------------------335518608136…

WPF学习(2)-UniformGrid控件(均分布局)+StackPanel控件(栈式布局)

UniformGrid控件&#xff08;均分布局&#xff09; UniformGrid和Grid有些相似&#xff0c;只不过UniformGrid的每个单元格面积都是相等的&#xff0c;不管是横向的单元格&#xff0c;或是纵向的单元格&#xff0c;它们会平分整个UniformGrid。 UniformGrid控件提供了3个属性…