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 解析方面的表現還是很不錯的,如果平時希望偷點懶,節省點時間和代碼量,完全推薦大家使用。