首页 > 编程语言 >【Java】Jsoup 解析HTML报告

【Java】Jsoup 解析HTML报告

时间:2024-08-02 21:06:29浏览次数:17  
标签:Java String ZipFile zipFile Element Jsoup HTML child 读取

一、需求背景

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

 

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

/* 2、读取【漏洞分布】信息 */
/* 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);
}

  

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); }

  

 

标签:Java,String,ZipFile,zipFile,Element,Jsoup,HTML,child,读取
From: https://www.cnblogs.com/mindzone/p/18339177

相关文章

  • 基于Java养老院管理系统设计和实现(源码+LW+调试文档+讲解等)
    详细视频演示:请联系我获取更详细的演示视频系统技术介绍:后端框架SpringBootSpringBoot内置了Tomcat、Jetty和Undertow等服务器,这意味着你可以直接使用它们而不需要额外的安装和配置。SpringBoot的一个主要优点是它的自动配置功能。它可以根据你的项目中的依赖关......
  • javascript学习 - DOM 元素获取、属性修改
    什么是WebAPIWebAPI是指网页服务器或者网页浏览器的应用程序接口。简单来讲,就是我们在编写JavaScript代码时,可以通过WebAPI来操作HTML网页和浏览器。WebAPI又可以分为两类:DOM(文档对象模型)BOM(浏览器对象模型)DOM(DocumentObjectModel),即文档对象模型,主要用......
  • javascript学习 - DOM 事件
    事件什么是事件在之前DOM的学习中,我们主要学习了如何获取DOM元素,并且学会了如何给获取的元素进行属性修改等操作。但这些基本都是静态的修改,并没有接触到一些动作。而今天要学习的事件,其实就是这些动作的总称。所谓事件,就是在编程时系统内所发生的动作或者发生的事情......
  • Java学习记录
    对java进行配置通过下载jdk文件,然后在系统中设置环境变量,将新建变量JAVA_HOME,写入正确的jdk文件的路径接着在path中新建变量,将jdk的文件路径导入测试jdk是否安装成功:打开cmd在运行框输入cmd,如果显示如下信息则表示jdk安装成功Java语言的版本JavaSE​标准版,是为开发......
  • JavaScript 中的闭包和事件委托
    包(Closures)闭包是JavaScript中一个非常强大的特性,它允许函数访问其外部作用域中的变量,即使在该函数被调用时,外部作用域已经执行完毕。闭包可以帮助我们实现数据的私有化、封装和模块化,使代码更简洁、易读和可维护。闭包的定义简单来说,闭包是指有权访问另一个函数作用域中......
  • Dockerfile 构建java程序的docker镜像
    Dockerfile示例#设置jdk版本FROMopenjdk:8#设置容器内部工作目录为/java,后续命令将在该目录下执行操作WORKDIR/java#置容器的时区为亚洲/上海,以确保正确的时间设置。ENVTZ=Asia/Shanghai#在容器中设置正确的时区信息。RUNln-snf/usr/share/zoneinfo/$TZ/etc/local......
  • java如何避免NullPointerException(空指针异常,NPE)
    本文将简单的介绍nep以及如何避免npe1.npe简介空指针异常(NullPointerException)意思是指java中的异常类。当应用程序试图在需要对象的地方使用null时,抛出该异常。这种情况包括:调用null对象的实例方法。访问或修改null对象的字段。将null作为一个数组,获得其长度......
  • 用Java手搓一个依赖注入框架
    1、bean容器publicclassContainer{privatefinalstaticLoggerlog=Logger.getLogger(Container.class.getSimpleName());privateMap<String,Object>context=newHashMap<>();privateList<SuspendBean>suspendBeans=newArr......
  • Java集合
    单列集合思维导图遍历方式适用场景双列集合可变参数Collections工具类常用API......
  • JavaSE基础编程十题(数组和方法部分)
    写在前面继续昨天Java中的数组和方法部分的习题,今天写十题编程题,来看看你能写出来几题。答案也是仅供参考,如果有更好的解法欢迎在下面留言!题目展示1.数组查找操作:定义一个长度为10的一维字符串数组,在每一个元素存放一个单词;然后运行时从命令行输入一个单词,程序判断数组是否包......