解析和遍歷一個(gè)HTML文檔
一個(gè)文檔的對象模型
- 文檔由多個(gè)Elements和TextNodes組成 (以及其它輔助nodes:詳細(xì)可查看:nodes package tree).
- 其繼承結(jié)構(gòu)如下:Document繼承Element繼承Node. TextNode繼承 Node.
- 一個(gè)Element包含一個(gè)子節(jié)點(diǎn)集合囤耳,并擁有一個(gè)父Element铆隘。他們還提供了一個(gè)唯一的子元素過濾列表属拾。
輸入
解析一個(gè)HTML字符串
使用靜態(tài)Jsoup.parse(String html)
方法或 Jsoup.parse(String html, String baseUri)
示例代碼:
String html = "<html><head><title>First parse</title></head>"
+ "<body><p>Parsed HTML into a doc.</p></body></html>";
Document doc = Jsoup.parse(html);
parse(String html, String baseUri)
這方法能夠?qū)⑤斎氲腍TML解析為一個(gè)新的文檔 (Document)蚣录,參數(shù) baseUri 是用來將相對 URL 轉(zhuǎn)成絕對URL,并指定從哪個(gè)網(wǎng)站獲取文檔。如這個(gè)方法不適用,你可以使用 parse(String html)
方法來解析成HTML字符串如上面的示例。.
只要解析的不是空字符串震叙,就能返回一個(gè)結(jié)構(gòu)合理的文檔,其中包含(至少) 一個(gè)head和一個(gè)body元素云头。
一旦擁有了一個(gè)Document捐友,你就可以使用Document中適當(dāng)?shù)姆椒ɑ蛩割?Element
和Node
中的方法來取得相關(guān)數(shù)據(jù)。
解析一個(gè)body片斷
使用Jsoup.parseBodyFragment(String html)
方法.
String html = "<div><p>Lorem ipsum.</p>";
Document doc = Jsoup.parseBodyFragment(html);
Element body = doc.body();
parseBodyFragment
方法創(chuàng)建一個(gè)空殼的文檔溃槐,并插入解析過的HTML到body
元素中匣砖。假如你使用正常的 Jsoup.parse(String html)
方法,通常你也可以得到相同的結(jié)果昏滴,但是明確將用戶輸入作為 body片段處理猴鲫,以確保用戶所提供的任何糟糕的HTML都將被解析成body元素。
Document.body()
方法能夠取得文檔body元素的所有子元素谣殊,與 doc.getElementsByTag("body")
相同拂共。
從一個(gè)URL加載一個(gè)Document
使用 Jsoup.connect(String url)
方法:
Document doc = Jsoup.connect("http://example.com/").get();
String title = doc.title();
connect(String url)
方法創(chuàng)建一個(gè)新的 Connection
, 和 get()
取得和解析一個(gè)HTML文件。如果從該URL獲取HTML時(shí)發(fā)生錯誤姻几,便會拋出 IOException宜狐,應(yīng)適當(dāng)處理。
Connection
接口還提供一個(gè)方法鏈來解決特殊請求蛇捌,具體如下:
Document doc = Jsoup.connect("http://example.com")
.data("query", "Java")
.userAgent("Mozilla")
.cookie("auth", "token")
.timeout(3000)
.post();
這個(gè)方法只支持Web URLs (http
和https
協(xié)議); 假如你需要從一個(gè)文件加載抚恒,可以使用 parse(File in, String charsetName)
代替。
從一個(gè)文件加載一個(gè)文檔
可以使用靜態(tài) Jsoup.parse(File in, String charsetName, String baseUri)
方法:
File input = new File("/tmp/input.html");
Document doc = Jsoup.parse(input, "UTF-8", "http://example.com/");
parse(File in, String charsetName, String baseUri)
這個(gè)方法用來加載和解析一個(gè)HTML文件络拌。如在加載文件的時(shí)候發(fā)生錯誤俭驮,將拋出IOException,應(yīng)作適當(dāng)處理春贸。
baseUri
參數(shù)用于解決文件中URLs是相對路徑的問題混萝。如果不需要可以傳入一個(gè)空的字符串。
另外還有一個(gè)方法parse(File in, String charsetName)
萍恕,它使用文件的路徑做為 baseUri
逸嘀。 這個(gè)方法適用于如果被解析文件位于網(wǎng)站的本地文件系統(tǒng),且相關(guān)鏈接也指向該文件系統(tǒng)允粤。
解析
使用DOM方法來遍歷一個(gè)文檔
將HTML解析成一個(gè)Document
之后厘熟,就可以使用類似于DOM的方法進(jìn)行操作屯蹦。示例代碼:
File input = new File("/tmp/input.html");
Document doc = Jsoup.parse(input, "UTF-8", "http://example.com/");
Element content = doc.getElementById("content");
Elements links = content.getElementsByTag("a");
for (Element link : links) {
String linkHref = link.attr("href");
String linkText = link.text();
}
說明
Elements這個(gè)對象提供了一系列類似于DOM的方法來查找元素,抽取并處理其中的數(shù)據(jù)绳姨。具體如下:
查找元素
getElementById(String id)
getElementsByTag(String tag)
getElementsByClass(String className)
-
getElementsByAttribute(String key)
(and related methods) - Element siblings:
siblingElements()
,firstElementSibling()
,lastElementSibling()
;nextElementSibling()
,previousElementSibling()
- Graph:
parent()
,children()
,child(int index)
元素?cái)?shù)據(jù)
-
attr(String key)
獲取屬性attr(String key, String value)
設(shè)置屬性 -
attributes()
獲取所有屬性 -
id()
,className()
andclassNames()
-
text()
獲取文本內(nèi)容text(String value)
設(shè)置文本內(nèi)容 -
html()
獲取元素內(nèi)HTMLhtml(String value)
設(shè)置元素內(nèi)的HTML內(nèi)容 -
outerHtml()
獲取元素外HTML內(nèi)容 -
data()
獲取數(shù)據(jù)內(nèi)容(例如:script和style標(biāo)簽) -
tag()
andtagName()
操作HTML和文本
-
append(String html)
,prepend(String html)
-
appendText(String text)
,prependText(String text)
-
appendElement(String tagName)
,prependElement(String tagName)
html(String value)
使用選擇器語法來查找元素
可以使用Element.select(String selector)
和 Elements.select(String selector)
方法實(shí)現(xiàn):
File input = new File("/tmp/input.html");
Document doc = Jsoup.parse(input, "UTF-8", "http://example.com/");
Elements links = doc.select("a[href]"); //帶有href屬性的a元素
Elements pngs = doc.select("img[src$=.png]");
//擴(kuò)展名為.png的圖片
Element masthead = doc.select("div.masthead").first();
//class等于masthead的div標(biāo)簽
Elements resultLinks = doc.select("h3.r > a"); //在h3元素之后的a元素
說明
jsoup elements對象支持類似于CSS (或jquery)的選擇器語法,來實(shí)現(xiàn)非常強(qiáng)大和靈活的查找功能阔挠。.
這個(gè)select
方法在Document
, Element
,或Elements
對象中都可以使用飘庄。且是上下文相關(guān)的,因此可實(shí)現(xiàn)指定元素的過濾购撼,或者鏈?zhǔn)竭x擇訪問跪削。
Select方法將返回一個(gè)Elements
集合,并提供一組方法來抽取和處理結(jié)果迂求。
Selector選擇器概述
-
tagname
: 通過標(biāo)簽查找元素碾盐,比如:a
-
ns|tag
: 通過標(biāo)簽在命名空間查找元素,比如:可以用fb|name
語法來查找<fb:name>
元素 -
#id
: 通過ID查找元素揩局,比如:#logo
-
.class
: 通過class名稱查找元素毫玖,比如:.masthead
-
[attribute]
: 利用屬性查找元素,比如:[href]
-
[^attr]
: 利用屬性名前綴來查找元素凌盯,比如:可以用[^data-]
來查找?guī)в蠬TML5 Dataset屬性的元素 -
[attr=value]
: 利用屬性值來查找元素付枫,比如:[width=500]
-
[attr^=value]
,[attr$=value]
,[attr*=value]
: 利用匹配屬性值開頭、結(jié)尾或包含屬性值來查找元素驰怎,比如:[href*=/path/]
-
[attr~=regex]
: 利用屬性值匹配正則表達(dá)式來查找元素阐滩,比如:img[src~=(?i)\.(png|jpe?g)]
-
*
: 這個(gè)符號將匹配所有元素
Selector選擇器組合使用
-
el#id
: 元素+ID,比如:div#logo
-
el.class
: 元素+class县忌,比如:div.masthead
-
el[attr]
: 元素+class掂榔,比如:a[href]
- 任意組合,比如:
a[href].highlight
-
ancestor child
: 查找某個(gè)元素下子元素症杏,比如:可以用.body p
查找在"body"元素下的所有p
元素 -
parent > child
: 查找某個(gè)父元素下的直接子元素装获,比如:可以用div.content > p
查找p
元素,也可以用body > *
查找body標(biāo)簽下所有直接子元素 -
siblingA + siblingB
: 查找在A元素之前第一個(gè)同級元素B鸳慈,比如:div.head + div
-
siblingA ~ siblingX
: 查找A元素之前的同級X元素饱溢,比如:h1 ~ p
-
el, el, el
:多個(gè)選擇器組合,查找匹配任一選擇器的唯一元素走芋,例如:div.masthead, div.logo
偽選擇器selectors
-
:lt(n)
: 查找哪些元素的同級索引值(它的位置在DOM樹中是相對于它的父節(jié)點(diǎn))小于n绩郎,比如:td:lt(3)
表示小于三列的元素 -
:gt(n)
:查找哪些元素的同級索引值大于n``,比如
:div p:gt(2)
表示哪些div中有包含2個(gè)以上的p元素 -
:eq(n)
: 查找哪些元素的同級索引值與n
相等翁逞,比如:form input:eq(1)
表示包含一個(gè)input標(biāo)簽的Form元素 -
:has(seletor)
: 查找匹配選擇器包含元素的元素肋杖,比如:div:has(p)
表示哪些div包含了p元素 -
:not(selector)
: 查找與選擇器不匹配的元素,比如:div:not(.logo)
表示不包含 class=logo 元素的所有 div 列表 -
:contains(text)
: 查找包含給定文本的元素挖函,搜索不區(qū)分大不寫状植,比如:p:contains(jsoup)
-
:containsOwn(text)
: 查找直接包含給定文本的元素 -
:matches(regex)
: 查找哪些元素的文本匹配指定的正則表達(dá)式,比如:div:matches((?i)login)
-
:matchesOwn(regex)
: 查找自身包含文本匹配指定正則表達(dá)式的元素 - 注意:上述偽選擇器索引是從0開始的,也就是說第一個(gè)元素索引值為0津畸,第二個(gè)元素index為1等
從元素抽取屬性振定,文本和HTML
方法
- 要取得一個(gè)屬性的值,可以使用
Node.attr(String key)
方法 - 對于一個(gè)元素中的文本肉拓,可以使用
Element.text()
方法 - 對于要取得元素或?qū)傩灾械腍TML內(nèi)容后频,可以使用
Element.html()
, 或Node.outerHtml()
方法
示例:
String html = "<p>An <a ><b>example</b></a> link.</p>";
Document doc = Jsoup.parse(html);//解析HTML字符串返回一個(gè)Document實(shí)現(xiàn)
Element link = doc.select("a").first();//查找第一個(gè)a元素
String text = doc.body().text(); // "An example link"http://取得字符串中的文本
String linkHref = link.attr("href"); // "http://example.com/"http://取得鏈接地址
String linkText = link.text(); // "example""http://取得鏈接地址中的文本
String linkOuterH = link.outerHtml();
// "<a
String linkInnerH = link.html(); // "<b>example</b>"http://取得鏈接內(nèi)的html內(nèi)容
說明
上述方法是元素?cái)?shù)據(jù)訪問的核心辦法。此外還其它一些方法可以使用:
Element.id()
Element.tagName()
-
Element.className()
andElement.hasClass(String className)
這些訪問器方法都有相應(yīng)的setter方法來更改數(shù)據(jù).
處理URLs
問題
你有一個(gè)包含相對URLs路徑的HTML文檔暖途,需要將這些相對路徑轉(zhuǎn)換成絕對路徑的URLs卑惜。
方法
- 在你解析文檔時(shí)確保有指定
base URI
,然后 - 使用
abs:
屬性前綴來取得包含base URI
的絕對路徑驻售。代碼如下:
Document doc = Jsoup.connect("http://www.open-open.com").get();
Element link = doc.select("a").first();
String relHref = link.attr("href"); // == "/"
String absHref = link.attr("abs:href"); // "http://www.open-open.com/"
說明
在HTML元素中露久,URLs經(jīng)常寫成相對于文檔位置的相對路徑: <a href="/download">...</a>
. 當(dāng)你使用 Node.attr(String key)
方法來取得a元素的href屬性時(shí),它將直接返回在HTML源碼中指定定的值欺栗。
假如你需要取得一個(gè)絕對路徑毫痕,需要在屬性名前加 abs:
前綴。這樣就可以返回包含根路徑的URL地址attr("abs:href")
因此纸巷,在解析HTML文檔時(shí)镇草,定義base URI非常重要。
如果你不想使用abs:
前綴瘤旨,還有一個(gè)方法能夠?qū)崿F(xiàn)同樣的功能 Node.absUrl(String key)
梯啤。
數(shù)據(jù)修改
設(shè)置屬性的值
問題
在你解析一個(gè)Document之后可能想修改其中的某些屬性值,然后再保存到磁盤或都輸出到前臺頁面存哲。
方法
可以使用屬性設(shè)置方法 Element.attr(String key, String value)
, 和 Elements.attr(String key, String value)
.
假如你需要修改一個(gè)元素的 class
屬性因宇,可以使用 Element.addClass(String className)
和 Element.removeClass(String className)
方法。
Elements
提供了批量操作元素屬性和class的方法祟偷,比如:要為div中的每一個(gè)a元素都添加一個(gè)rel="nofollow"
可以使用如下方法:
doc.select("div.comments a").attr("rel", "nofollow");
說明
與Element
中的其它方法一樣察滑,attr
方法也是返回當(dāng)前 Element
(或在使用選擇器是返回 Elements
集合)。這樣能夠很方便使用方法連用的書寫方式修肠。比如:
doc.select("div.masthead").attr("title", "jsoup").addClass("round-box");
設(shè)置一個(gè)元素的HTML內(nèi)容
方法
可以使用Element
中的HTML設(shè)置方法具體如下:
Element div = doc.select("div").first(); // <div></div>
div.html("<p>lorem ipsum</p>"); // <div><p>lorem ipsum</p></div>
div.prepend("<p>First</p>");//在div前添加html內(nèi)容
div.append("<p>Last</p>");//在div之后添加html內(nèi)容
// 添完后的結(jié)果: <div><p>First</p><p>lorem ipsum</p><p>Last</p></div>
Element span = doc.select("span").first(); // <span>One</span>
span.wrap("<li><a );
// 添完后的結(jié)果: <li><a ><span>One</span></a></li>
說明
-
Element.html(String html)
這個(gè)方法將先清除元素中的HTML內(nèi)容贺辰,然后用傳入的HTML代替。 -
Element.prepend(String first)
和Element.append(String last)
方法用于在分別在元素內(nèi)部HTML的前面和后面添加HTML內(nèi)容 -
Element.wrap(String around)
對元素包裹一個(gè)外部HTML內(nèi)容嵌施。
設(shè)置元素的文本內(nèi)容
方法
可以使用Element
的設(shè)置方法:
Element div = doc.select("div").first(); // <div></div>
div.text("five > four"); // <div>five > four</div>
div.prepend("First ");
div.append(" Last");
// now: <div>First five > four Last</div>
說明
文本設(shè)置方法與 HTML setter方法一樣:
-
Element.text(String text)
將清除一個(gè)元素中的內(nèi)部HTML內(nèi)容饲化,然后提供的文本進(jìn)行代替 -
Element.prepend(String first)
和Element.append(String last)
將分別在元素的內(nèi)部html前后添加文本節(jié)點(diǎn)晃洒。
對于傳入的文本如果含有像
<
,>
等這樣的字符木人,將以文本處理婚肆,而非HTML擎勘。
消除不受信任的HTML (來防止XSS攻擊)
問題
在做網(wǎng)站的時(shí)候,經(jīng)常會提供用戶評論的功能遇绞。有些不懷好意的用戶暇韧,會搞一些腳本到評論內(nèi)容中护桦,而這些腳本可能會破壞整個(gè)頁面的行為,更嚴(yán)重的是獲取一些機(jī)要信息族奢,此時(shí)需要清理該HTML姥闭,以避免跨站腳本cross-site scripting攻擊(XSS)。
方法
使用jsoup HTML Cleaner
方法進(jìn)行清除歹鱼,但需要指定一個(gè)可配置的 Whitelist
String unsafe =
"<p><a onclick='stealCookies()'>Link</a></p>";
String safe = Jsoup.clean(unsafe, Whitelist.basic());
// now: <p><a rel="nofollow">Link</a></p>
說明
XSS又叫CSS (Cross Site Script) 泣栈,跨站腳本攻擊。它指的是惡意攻擊者往Web頁面里插入惡意html代碼弥姻,當(dāng)用戶瀏覽該頁之時(shí),嵌入其中Web里面的html代碼會被執(zhí)行掺涛,從而達(dá)到惡意攻擊用戶的特殊目的庭敦。XSS屬于被動式的攻擊,因?yàn)槠浔粍忧也缓美眯嚼拢栽S多人常忽略其危害性秧廉。所以我們經(jīng)常只讓用戶輸入純文本的內(nèi)容,但這樣用戶體驗(yàn)就比較差了拣帽。
一個(gè)更好的解決方法就是使用一個(gè)富文本編輯器WYSIWYG如CKEditor 和 TinyMCE疼电。這些可以輸出HTML并能夠讓用戶可視化編輯。雖然他們可以在客戶端進(jìn)行校驗(yàn)减拭,但是這樣還不夠安全蔽豺,需要在服務(wù)器端進(jìn)行校驗(yàn)并清除有害的HTML代碼,這樣才能確保輸入到你網(wǎng)站的HTML是安全的拧粪。否則修陡,攻擊者能夠繞過客戶端的Javascript驗(yàn)證,并注入不安全的HMTL直接進(jìn)入您的網(wǎng)站可霎。
jsoup的whitelist清理器能夠在服務(wù)器端對用戶輸入的HTML進(jìn)行過濾魄鸦,只輸出一些安全的標(biāo)簽和屬性。
jsoup提供了一系列的Whitelist
基本配置癣朗,能夠滿足大多數(shù)要求拾因;但如有必要,也可以進(jìn)行修改旷余,不過要小心绢记。
這個(gè)cleaner非常好用不僅可以避免XSS攻擊,還可以限制用戶可以輸入的標(biāo)簽范圍荣暮。
參見
- 參閱XSS cheat sheet 庭惜,有一個(gè)例子可以了解為什么不能使用正則表達(dá)式,而采用安全的whitelist parser-based清理器才是正確的選擇穗酥。
- 參閱Cleaner护赊,了解如何返回一個(gè) Document 對象惠遏,而不是字符串
- 參閱Whitelist,了解如何創(chuàng)建一個(gè)自定義的whitelist
- nofollow 鏈接屬性了解