jsoup 是一个用于处理真实 HTML 的 Java 库。它提供了一个非常方便的 API,用于提取和操作数据,使用最好的 DOM,CSS 和类似 jquery 的方法。
目录#
- jsoup 概述
- 使用场景
- DOM 解析
- CSS 选择器
- HTML 过滤
- 逻辑分析
- 总结
jousp 概述#
官方解释:
jsoup 是一个用于处理真实 HTML 的 Java 库。它提供了一个非常方便的 API,用于提取和操作数据,使用最好的 DOM,CSS 和类似 jquery 的方法。
个人接触到 Jsoup 是在用 java 写爬虫时,苦恼于大量使用正则匹配不仅降低了代码的可读性,相对也比较费时费力。这时候,一款爬虫框架突然映入眼帘,那就是 jsoup。
作为一款轻量,功能强大的爬虫框架,jsoup 让简单抓取网页信息变得优雅,便捷。 虽然是一个 java 库,但是它的使用逻辑却无比接近于 jQuery,以至于只要是熟悉或是了解 JQuery 的人可以轻而易举地上手这款框架。
使用场景#
DOM 解析#
jsoup 的 dom 解析异常简单吗,只需要 new 一个 ducumnet 对象即可实现获取这个网页元素,接下来以解析一个网页为例。可以看到,将网页转化成 ducument 类,之后的 Element 类以及其子类都可以看成是一个个节点,通过调用相关方法实现整个文件节点的遍历。同时,Element 类的 getElementByTag 让人很容易联想到 js 中的相关方法,因此只要有点 JS 基础和 java 基础的人看这段代码都不会觉得陌生。 这里以查询学生成绩信息为例:
public class jsoupTest {
public String getGrade(String stu_num, String id_num) throws IOException {
String testURL = "http://jwc.cqupt.edu.cn/showS tuQmcj.php"; //目标网页
Connection con = Jsoup.connect(testURL); //获取连接
con.data("xh", stu_num); //填写参数
con.data("sfzh", id_num);
Document document = con.post(); //选择发送方式,获取整个网页信息,存在documnet类里
Element pTable = document.body().getElementsByClass("pTable").get(0); //通过class属性 ,获取子类元素
Elements trs = pTable.getElementsByTag("tbody").get(0).children();
trs.forEach(tr -> { //遍历<tr>标签
if (!tr.children().isEmpty()) {
Element element = tr.getElementsByTag("td").get(0);
if (!element.text().equals("课程类型")) {
GradeInfo gradeInfo = new GradeInfo();
gradeInfo.setProperty(tr.getElementsByTag("td").get(0).text());
String term = tr.getElementsByTag("td").get(1).text();
System.out.println(term);
System.out.println(tr.getElementsByTag("td").get(2).text());
System.out.println(tr.getElementsByTag("td").get(5).text());
System.out.println(tr.getElementsByTag("td").get(6).text());
System.out.println(tr.getElementsByTag("td").get(7).text());
}
}
});
return "";
}
}
结果:
只需要几行就可以完成对 html 的基本解析,而且所有的操作都可以用 js 的逻辑解释。或多或少比原生正则匹配要实用的多。
CSS 选择器#
jsoup 决心是想向前端靠齐了,除了基本的 DOM 解析操作外,它同时加入了 CSS 选择器,这个操作乍一看似乎没什么用处,但是当你真正去学习如何使用后你的就会发现这是多少好用。在针对较复杂地语句匹配时,使用选择器可以轻而易举地筛选出你想要的元素,可以帮你节省大量代码。 使用方法:可以用 Element.select (String selector) 和 Element.select (String selector) 实现.
public void getBySelect() throws IOException {
String testURL = "<html>" +
"<head></head>"+
"<body>"+"<span id=\"grade\">成绩</span>"+"<span id = \"subject\">课程</span>"+"<span id = \"name\">姓名</span>"+"<span id = \"stunum\">学号</span>"+
"<span class = \"score\">85</span >"+"<span class = \"class\">语文</span>"+"<span class = \"stuname\">小明</span class = \"number\">"+"<span>201721001</span>"+
"<span id=\"grade\">80</span>"+"<span id = \"subject\">数学</span>"+"<span id = \"name\">小明</span>"+"<span id = \"stunum\">2017210001</span>"+
"</body></html>"; //利用字符串拼接出HTML标签
//获取连接
Document document = Jsoup.parse(testURL); //将HTML转化成可遍历地document类
Elements elements = document.select("span:matchesOwn(^8)");
for (Element element:
elements) {
System.out.println(elements.text());
}
}
结果:
CSS 选择器类似于 JQuery 和 CSS 中使用的选择器,可以通过特定的选择器语法将对指定元素进行筛选 对于选择器的筛选,这里推荐一篇文章:详解 JSOUP 的 Select 选择器语法
HTML 过滤#
这个功能也是偶然看见的,不过现在想来也理所当然,过滤网页信息本身就是 Jsoup 分内的事。当时正在看 XSS 攻击方面的知识,突然发现 jsoup 在安全方面已早有考虑,已本身优秀的 HTML 解析为基础,抵御 XSS 攻击自然也是十分优秀。
XSS 注入本质就是在 HTML 中插入特定的标签改变原来标签的含义,因此防止 XSS 攻击的本质是能分辨并及时过滤掉多余或是无效的 HTML 标签。对此,jsoup 有一个白名单机制,通过 clean 方法可以一步通过白名单设置的过滤规则清理所有的标签,同时也会保留适当标签和禁止图片显示的功能。
/**
* xss过滤
*
*/
public class JsoupUtil {
/**
* 使用自带的basicWithImages 白名单
* 允许的便签有a,b,blockquote,br,cite,code,dd,dl,dt,em,i,li,ol,p,pre,q,small,span,
* strike,strong,sub,sup,u,ul,img
* 以及a标签的href,img标签的src,align,alt,height,width,title属性
*/
private static final Whitelist whitelist = Whitelist.basicWithImages();
/** 配置过滤化参数,不对代码进行格式化 */
private static final Document.OutputSettings outputSettings = new Document.OutputSettings().prettyPrint(false);
static {
// 富文本编辑时一些样式是使用style来进行实现的
// 比如红色字体 style="color:red;"
// 所以需要给所有标签添加style属性
whitelist.addAttributes(":all", "style");
}
public static String clean(String content) {
return Jsoup.clean(content, "", whitelist, outputSettings);
}
public static void main(String[] args) throws FileNotFoundException, IOException {
String text = "<a href=\"http://www.baidu.com/a\" onclick=\"alert(1);\">sss</a><script>alert(0);</script>sss";
System.out.println(clean(text));
}
}
逻辑分析 jsoup#
作为一款轻便的爬虫框架,全部由 Jonathan Hedley 独立写出,因此代码相比其他一些笨重的框架要简洁很多,我通过网上一些解析 jsoup 源码的博客,加深对 jsoup 的理解。废话不多说,让我们看看 jsoup 的魅力吧!
借用别人博客里整理的图片,可以看到,让 java 能像 js 那样使用类似标签的嵌套存储的方法就是在这里就是利用自定义的 node 抽象类,将属性存储在类似树状的结构中这,这样做不仅有利于之后的 DOM 树解析,也容易遍历,有利于性能的提高。 我们再看看 CSS 选择器的实现逻辑。这是 selector 的源码列表
jsoup 在关于 selector 的实现大致是利用 Evaluator 抽象类,Selector 选择的表达式都会通过 QueryParser 最终编译到对应的 Evaluator 类上,然后此类又有很多派生子类,从而分别实现不同功能。逻辑思路还算简单,但是具体代码我还不曾仔细研读,因此在此也不再赘述。不过其中进行嵌套实现对象的思路还是值得借鉴的。
在 HTML 过滤方面,jsoup 防止 XSS 攻击的大致策略是
- 将 HTMl 字符串解析成 document 对象,这样保证了无法通过注入一段无用的脚本和字符串拼接导致网页的功能发生了改变
- 将一些高频出现的危险系数较高的标签加入白名单进行提前过滤
总结#
jsoup 在操作便捷度上已经展现了它的实力,但是在性能上,考虑到它的底层还是通过正则进行匹配,因此对于一些一些简单的 HTML 解析或许直接正则的最快的;但是,当 HTML 页面比较复杂,这便是 jsoup 大显身手的时候了。
但是 jsoup 还是有很多不足,例如
- 只能处理静态页面,对于动态显示或者后端渲染后后的页面无法正常进行爬取,这时就需要利用其他的工具例如 httpunit 进行模拟的 ajax 请求。
- jsoup 总归还只是个人项目,在后期的维护方面还是存在一定的不确定性,如果需要用应用在一些大型的长久性的项目中还需三思。
- jsoup 的底层实现还是正则匹配,尽管 jsoup 本身够轻量,但它依然需要解析整个 HTML,再进行进一步的搜索,因此在一些简单的网页解析中,肯定还是直接上正则来的直接来的方便。但是如果网页的结构足够复杂,使用正则的的代码量巨大,那么 jsoup 不失为一个不错的选择。
总而言之,jsoup 作为一款轻量的爬虫框架,在 HTML 解析方面的表现还是很不错的,如果平时希望偷点懒,节省点时间和代码量,完全推荐大家使用。