
java爬虫&html解析-Jsoup(绿盟极光报告)
一、类库选取
Java爬虫解析HTML文档的工具有:htmlparser, Jsoup。
主要是实现的功能需求,选取Jsoup,对html进行解析,爬去数据。
Jsoup可以直接解析某个URL地址、HTML文本内容,它提供非常丰富的处理Dom树的API。
Jsoup最强大的莫过于它的CSS选择器支持:
例如:document.select("div.content > div#image > ul > li:eq(2)
二、包引入方法
https://mvnrepository.com/artifact/org.jsoup/jsoup/1.12.2
可以通过maven在pom.xml添加
<!-- https://mvnrepository.com/artifact/org.jsoup/jsoup -->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.12.2</version>
</dependency>
还可以下载jar包直接添加到项目里面
三、Jsoup解析方法
Jsoup支持四种方式解析Document,即可以输入四种内容得到一个Document:
解析字符串
解析body片段
从一个URL解析
从一个文件解析
字符串解析
字符串中必须包含head和body元素。
String html = "<html><head><title>First parse</title></head>"
+ "<body><p>Parsed HTML into a doc.</p></body></html>";
Document doc = Jsoup.parse(html);
HTML片段解析
String html = "<div><p>Lorem ipsum.</p>";
Document doc = Jsoup.parseBodyFragment(html);
Element body = doc.body();
URL解析
Document doc = Jsoup.connect("http://example.com/").get();
String title = doc.title();
还可以携带cookie等参数:(和Python的爬虫类似)
Document doc = Jsoup.connect("http://example.com")
.data("query", "Java")
.userAgent("Mozilla")
.cookie("auth", "token")
.timeout(3000)
.post();
本地文件解析
File input = new File("/tmp/input.html");
Document doc = Jsoup.parse(input, "UTF-8", "http://example.com/");
本次的项目需求是本地的文件解析
重新编写一下文件读取方法:
/**
* 提取文件里面的文本信息
*/
public static String openFile(String szFileName) {
try {
BufferedReader bis = new BufferedReader(new InputStreamReader(new FileInputStream(new File(szFileName)), ENCODE));
String szContent = "";
String szTemp;
while ((szTemp = bis.readLine()) != null) {
szContent += szTemp + "\n";
}
bis.close();
return szContent;
} catch (Exception e) {
return "";
}
}
四、Jsoup遍历DOM树的方法
使用标准的DOM方法
根据id查找元素: getElementById(String id)
根据标签查找元素: getElementsByTag(String tag)
根据class查找元素: getElementsByClass(String className)
根据属性查找元素: getElementsByAttribute(String key)
兄弟遍历方法: siblingElements(), firstElementSibling(), lastElementSibling(); nextElementSibling(), previousElementSibling()
层级之间遍历: parent(), children(), child(int index)
些方法会返回Element或者Elements节点对象,这些对象可以使用下面的方法获取一些属性:
attr(String key): 获取某个属性值
attributes(): 获取节点的所有属性
id(): 获取节点的id
className(): 获取当前节点的class名称
classNames(): 获取当前节点的所有class名称
text(): 获取当前节点的textNode内容
html(): 获取当前节点的 inner HTML
outerHtml(): 获取当前节点的 outer HTML
data(): 获取当前节点的内容,用于script或者style标签等
tag(): 获取标签
tagName(): 获取当前节点的标签名称
有了这些API,就像JQuery一样很便利的操作DOM。
强大的CSS选择器支持
htmlparse支持xpath,可以很方便的定位某个元素,而不用一层一层地遍历DOM树。调用方法如下:document.select(String selector): 选择匹配选择器的元素,返回是Elements对象
document.selectFirst(String selector): 选择匹配选择器的第一个元素,返回是一个Element对象
element.select(String selector): 也可以直接在Element对象上执行选择方法
Jsoup能够完美的支持CSS的选择器语法,可以说对应有前端经验的开发者来说简直是福音,不用特意去学习XPath的语法。
下面列出一些常见的选择器:
标签选择(如div): tag
id选择(#logo): #id
class选择(.head): .class
属性选择([href]): [attribute]
属性值选择: [attr=value]
属性前缀匹配: [^attr]
属性简单正则匹配: [attr^=value], [attr$=value], [attr*=value], [attr~=regex]
另外还支持下面的组合选择器:
element#id: (div#logo: 选取id为logo的div元素)
element.class: (div.content: 选择class包括content的div元素)
element[attr]: (a[href]: 选择包含href的a元素)
ancestor child: (div p: 选择div元素的所有p后代元素)
parent > child: (p > span: 选择p元素的直接子元素中的span元素)
siblingA + siblingB: (div.head + div: 选取div.head的下一个兄弟div元素)
siblingA ~ siblingX: (h1 ~ p: 选取h1后面的所有p兄弟元素)
el, el, el: (div.content, div.footer: 同时选取div.content和div.footer)
还支持伪元素选择器:
:lt(n): (div#logo > li:lt(2): 选择id为logo的div元素的前3个li子元素)
:gt(n)
:eq(n)
:has(selector)
:not(selector)
:contains(text)
Jsoup修改DOM树结构
Jsoup还支持修改DOM树结构,真的很像JQuery。
// 设置属性
doc.select("div.comments a").attr("rel", "nofollow");
// 设置class
doc.select("div.masthead").attr("title", "jsoup").addClass("round-box");
下面的API可以直接操作DOM树结构:
text(String value): 设置内容
html(String value): 直接替换HTML结构
append(String html): 元素后面添加节点
prepend(String html): 元素前面添加节点
appendText(String text), prependText(String text)
appendElement(String tagName), prependElement(String tagName)
五、项目实战(绿盟扫描器结果html数据爬虫整理)
获取2.1 漏洞概况数据获取
//绿盟扫描结果
public static Listgetlist1(Document doc, String ip){
//获取2.1 漏洞概况
//System.out.println(doc+"---------");
Elements elList=doc.getElementsByAttributeValue("id","vuln_list");
if(!elList.isEmpty()) {
Element el=elList.first();
Elements trLists = el.select("tr");//获取第一个tr
//System.out.println(doc+"---------");
for (int i = 0; i < trLists.size(); i++) {
Elements tds = trLists.get(i).select("td");//获取td标签
for (int j = 0; j < tds.size() - 1; j++) {
String text = tds.get(j).text();
}
}
}
}
}
获取2.2 漏洞详情数据获取
public static Listgetlist2(Document doc){
//获取2.2 漏洞详情
ListcList2 = new ArrayList();//获取相关的漏洞表述和CVSS数值
ListcList3 = new ArrayList();//获取漏洞的名称
ListcList4 = new ArrayList();//最后获取完整的 漏洞名称、漏洞描述、整改方法、CVSS评分
Listlist3 = new ArrayList();//对应漏洞和漏洞描述和CVSS评分的数组
Elements elList1=doc.getElementsByAttributeValue("id","vul_detail");//获取 2.2大模块的全部代码
Element el1 = elList1.first();
Elements trLists1 = el1.select("tr");//抽取其中的tr标签
//根据tr和td的循环获取 漏洞描述、整改方法、CVSS评分
for (int i = 0; i < trLists1.size(); i++) {
Elements tds1 = trLists1.get(i).select("td");
for (int j = 0; j < tds1.size(); j++) {
//Elements trLists2 = el1.select("[data-port]");
for (int u = 0; u < trLists2.size(); u++) {
rsas_scan2 = new RSAS_scan2();//防止list类方法添加数据出现最后一行
rsas_scan2.setAsset_name(trLists2.get(u).text());
}
}
}
}
最后数据汇总关联处理:
public Object Scan(String path1) throws Excel4JException, IOException {
List<String> list = new ArrayList<>();
String path = path1+"/";
File file = new File(path);
File[] files = file.listFiles();
List<RSAS_total> rsas_total = new ArrayList<>();
for (File hosts : files) {
String szContent = openFile(path + hosts.getName());
try {
Document doc = Jsoup.parse(szContent);
//获取本IP地址
String ip = hosts.getName().replace(".html", "");
System.out.println("\n"+ip);
//获取2.1 漏洞概况输出
List<RSAS_scan> data1 = getlist1(doc, ip);//调用函数
//获取2.2 漏洞详情
List<RSAS_scan2> data2 = getlist2(doc);//调用函数
//data1和data2通过漏洞名称关联 遍历匹配输出
for (int da1 = 0; da1 < data1.size(); da1++) {
for (int da2 = 0; da2 < data2.size(); da2++) {
rsas_total.setIp(data1.get(da1).getIp());
rsas_total.setAsset_point(data1.get(da1).getAsset_point());
rsas_total.setAsset_name(data1.get(da1).getAsset_name());
rsas_total.setAsset_description(data2.get(da2).getAsset_description());
}
}
} catch(Exception e){
e.printStackTrace();
}
}
}
System.out.println("------------------Finished-详情查看-绿盟扫描结果.xlsx------------------");
//Excel无模版导出
Excel.getInstance().exportObjects(rsas_totals, RSAS_total.class, true, null, true, "绿盟扫描结果.xlsx");
}
运行效果:
GUI界面化:
Console打印输出:
同时导出到excel
六、总结:
1、数据获取过程中的问题:
A、Java的Jsoup爬去html的选择器在选择的时候,选取的是一个集合,需要设置不同的类去来存储这个集合的不同地方对应的值。有的时候需要多层的数据嵌套循环选择。
B、还有就是数据的父节点和子节点的承继关系,在不同的区域选取,怎么讲数据关联。(对比python爬虫,Java的爬虫比较冗余一些,倒是Java的代码复用率高,尤其是相同模块,可以拆分不同的模块,处理不同的数据,Java的不同的类封装,再嵌套实现,python一个页面写一个。)
2、感悟:
如果是爬虫还是python轻量便捷,如果设计到对模块重复使用,还有重复关联数据交叉较大,建议使用java来实现,Swing的Gui开发也比较便捷。
七、参考文献:
https://www.cnblogs.com/youyoui/p/11065923.html
https://blog.csdn.net/lxacdf/article/details/73200906
https://www.cnblogs.com/shaosks/p/9625878.html
https://blog.csdn.net/qq_39569480/article/details/93980313
公众号:
thelostworld:
个人知乎:https://www.zhihu.com/people/fu-wei-43-69/columns
个人简书:https://www.jianshu.com/u/bf0e38a8d400