对HTML页面的解析,之前我一般使用HTMLParser,详细见HTMLParser的学习系列 - 学习总结,但是这个项目已经停止更新。现在比较好的解析HTML的控件是Jsoup。本文对Jsoup的用法做个总结 。
Jsoup的主要功能有三部分组成:
下方每节对以上三点进行逐一演示。
POM.xml 配置
<dependency>
<groupId>org.jsoupgroupId>
<artifactId>jsoupartifactId>
<version>1.10.2version>
dependency>
JSOUP通过不同方式生成Document,主要有以下三种:
1. 字符串
2. 网页
3. 本地文件
关键方法:Jsoup.parse
代码
/**
* 将字符串转化为Document
*
* html: https://jsoup.org/cookbook/input/parse-document-from-string
*/
public void parseDocumentFromString() {
String html = "First parse "
+ "Parsed HTML into a doc.
";
Document doc = Jsoup.parse(html);
logger.info("parseDocumentFromString content={}", doc);
}
输出结果
parseDocumentFromString content=<html>
<head>
<title>First parsetitle>
head>
<body>
<p>Parsed HTML into a doc.p>
body>
html>
关键方法: Jsoup.connect
代码
/**
* 从网络上加载网页并转化为Document
*
* html: https://jsoup.org/cookbook/input/load-document-from-url
*/
public void loadDocumentFromURL() {
Document doc;
try {
doc = Jsoup.connect("https://www.baidu.com/").get();
// 从document中获取title值
String title = doc.title();
logger.info("LoadDocumentFromURL title={}", title);
} catch (IOException e) {
e.printStackTrace();
}
}
输出:由于页面太大,这里没有打印页面内容,只是输出页面的title值
LoadDocumentFromURL title=百度一下,你就知道
本地测试文件: loadDocumentFromFile.html
<html>
<body>
.. body
body>
html>
代码
关键方法: Jsoup.parse
/**
* 从本地加载文件并转化为Document
*
* html: https://jsoup.org/cookbook/input/load-document-from-file
*/
public void loadDocumentFromFile() {
URL fileUrl = LoadParseDocument.class.getResource("/com/hry/tool/jsoup/doc/loadDocumentFromFile.html");
File input = new File(fileUrl.getFile());
try {
/* The baseUri parameter is used by the parser to resolve relative URLs in the document
* before a element is found. If that's not a concern for you,
* you can pass an empty string instead.
*/
Document doc = Jsoup.parse(input, "UTF-8", "https://www.baidu.com/");
logger.info("LoadDocumentFromFile content={}", doc);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
输出
LoadDocumentFromFile content=<html>
<head>head>
<body>
.. body
body>
html>
在上一节中已经生成Document,下面就可以对这个document进行操作,操作的主要单位是Element,下面介绍如何选取elment及获取elment的内容。
本地包含的主要内容获取节点信息:
1. 获取Element,获取的方式有二种: a DOM; b 通过css或类似jquery的selector语法
2. 获取节点属性、文本、html
a DOM
代码
/**
* 调用document的类似DOM的方法获取Element
*
* html: https://jsoup.org/cookbook/extracting-data/dom-navigation
* @throws IOException
*/
public void extractDataByDOM() throws IOException{
Document doc = Jsoup.connect("https://www.baidu.com/").get();
Element lg = doc.getElementById("lg");
logger.info("getElementById lg = {}", lg);
Elements links = doc.getElementsByTag("a");
for (Element link : links) {
String linkHref = link.attr("href");
String linkText = link.text();
logger.info("linkHref={}, linkText={}",linkHref, linkText);
}
}
输出
输出内容过多,略
b 通过css或类似jquery的selector语法
点击查看全部的selector语法
测试文件: extractDataByCSSOrJqueryLikeSelectorSyntax.html
<html>
<header>
<h3 class="r">h3<a href="/test/h3">a>b>h3>
header>
<body>
.. body
<div>
<a href="/test" >testa>
<a href="/test2" >test2a>
div>
<div>
<img alt="test" src="/image/a.png" />
<img alt="test2" src="/image/b.png" />
div>
<div>
<div class="masthead">mastheaddiv>
div>
body>
html>
代码
/**
* 通过css或类似jquery的selector语法
*
* html: https://jsoup.org/cookbook/extracting-data/selector-syntax
* @throws IOException
*/
public void extractDataByCSSOrJqueryLikeSelectorSyntax() throws IOException{
URL fileUrl = LoadParseDocument.class.getResource("/com/hry/tool/jsoup/extractingdata/extractDataByCSSOrJqueryLikeSelectorSyntax.html");
File input = new File(fileUrl.getFile());
Document doc = Jsoup.parse(input, "UTF-8", "http://example.com/");
// 获取所有的a节点
Elements links = doc.select("a[href]");
logger.info("links = {}", links);
// 获取img的src以.png结果结尾
Elements pngs = doc.select("img[src$=.png]");
logger.info("pngs = {}", pngs);
// 获取class=masthead的div节点
Element masthead = doc.select("div.masthead").first();
logger.info("masthead = {}", masthead);
// 获取class=r的h3节点下面的a节点
Elements resultLinks = doc.select("h3.r > a"); // direct a after h3
logger.info("resultLinks = {}", resultLinks);
}
输出: 省略部分日志内容,方便查看
links = <a href="/test/h3">a>
<a href="/test">testa>
<a href="/test2">test2a>
pngs = <img alt="test" src="/image/a.png">
<img alt="test2" src="/image/b.png">
masthead = <div class="masthead">mastheaddiv>
代码
/**
* 获取节点属性、文本、html
*
* html: https://jsoup.org/cookbook/extracting-data/attributes-text-html
*/
public void extractAttributesTextAndHTML(){
String html = "An example link.
";
// 指定baseUri的值,在使用abs:attr会使用到
Document doc = Jsoup.parse(html, "http://example.com/");
Element link = doc.select("a").first();
/**
* text获取所有子节点的文本并组合在一一起
* 如:HTML Hello there now!
, --》 调用p.text() --》输出: "Hello there now!"
*/
String text = doc.body().text(); // "An example link"
logger.info("text={}", text);
// 获取link的href属性值
String linkHref = link.attr("href"); // /abc
// 在href前面加上abs,会在现有的href(如/abc)的前面别上Jsoup.parse(html, "http://example.com/")里指定的baseUri值
String absLinkHref = link.attr("abs:href"); // http://example.com/abc
logger.info("linkHref={}, absLinkHref={}",linkHref, absLinkHref);
// 获取link里所有字节点的内容组合在一起
String linkText = link.text(); // "example""
logger.info("linkText={}", linkText);
// 获取本节点所有HTML文本信息
String linkOuterH = link.outerHtml(); // "example"
logger.info("linkOuterH={}", linkOuterH);
// 获取本节点字节点的html文本信息
String linkInnerH = link.html(); // "example"
logger.info("linkInnerH={}", linkInnerH);
}
}
输出:省略部分日志,方便阅读
text=An example link.
linkHref=/abc, absLinkHref=http://example.com/abc
linkText=example
linkOuterH=<a href="/abc"><b>exampleb>a>
linkInnerH=<b>exampleb>
设置节点值,主要有在以下方式:
1. 设置节点HTML的值
2. 设置节点内容的值
3. 设置节点属性的值
公共测试文件
<html>
<body>
.. body
<div>div>
<span>Onespan>
body>
html>
从本地文件造成Document文本
private Document getDoucment() throws IOException{
URL fileUrl = LoadParseDocument.class.getResource("/com/hry/tool/jsoup/modifyingdata/modifyingData.html");
File input = new File(fileUrl.getFile());
Document doc = Jsoup.parse(input, "UTF-8", "http://example.com/");
return doc;
}
设置节点的html内容:
Element.wrap:将指定节点封装到html最里面
代码:
/**
* 设置节点的html内容:
* 1. html:使用新的HTML替换旧的值
* 2. prepend:将新html添加到指定节点内部的最前面
* 3. append:将新html添加到指定节点内部的最后面
* 4. wrap:将指定节点封装到html最里面
*
* html: https://jsoup.org/cookbook/modifying-data/set-html
* @throws IOException
*/
public void setHTMLofAnElement() throws IOException{
Document doc = getDoucment();
// 获取div节点
Element div = doc.select("div").first(); // <div>div>
// 使用新的HTML替换div旧的html值
div.html("<p>lorem ipsump>"); // <div><p>lorem ipsump>div>
// 将新html添加到div内部的最前面
div.prepend("<p>Firstp>"); // <div><p>Firstp><p>lorem ipsump>div>
// 将新html添加到div内部的最后面
div.append("<p>Lastp>"); // now: <div><p>Firstp><p>lorem ipsump><p>Lastp>div>
logger.info("now div={}", div);
Element span = doc.select("span").first(); // <span>Onespan>
// 将span节点封装到html最里面 --》 <li><a href="http://example.com"><span>Onespan>a>li>
span.wrap("<li><a href='http://example.com/'>a>li>");
logger.info("doc={}", doc);
}
输出:
2017-06-07 22:35:48.271 INFO 6644 --- [ main] c.h.t.jsoup.modifyingdata.ModifyingData : now div=<div>
<p>Firstp>
<p>lorem ipsump>
<p>Lastp>
div>
2017-06-07 22:35:48.271 INFO 6644 --- [ main] c.h.t.jsoup.modifyingdata.ModifyingData : doc=<html>
<head>head>
<body>
.. body
<div>
<p>Firstp>
<p>lorem ipsump>
<p>Lastp>
div>
<li><a href="http://example.com/"><span>Onespan>a>li>
body>
html>
修改节点内容
1. Element.text: 完全替换内容
2. Element.prepend:在节点的内容最前面加内容
3. Element.append:在节点的内容最后面加内容
/**
* 修改节点内容
* 1. 完全替换内容
* 2. 在节点的内容最前面加内容
* 3. 在节点的内容最后面加内容
*
* html: https://jsoup.org/cookbook/modifying-data/set-text
* @throws IOException
*/
public void setTextContentofAnElement() throws IOException{
Document doc = getDoucment();
Element div = doc.select("div").first(); //
// 替换div里的内容 输出 --> five four
div.text("five four");
logger.info("text = {}", div);
// 在div的内容最前面加内容 输出 --> First five four
div.prepend("First ");
logger.info("prepend = {}", div);
// 在div的内容最后面加内容 输出 --> First five four Last
div.append(" Last");
logger.info("append = {}", div);
}
输出:
2017-06-07 22:39:20.289 INFO 11476 --- [ main] c.h.t.jsoup.modifyingdata.ModifyingData : text =
five four
2017-06-07 22:39:20.289 INFO 11476 --- [ main] c.h.t.jsoup.modifyingdata.ModifyingData : prepend =
First five four
2017-06-07 22:39:20.289 INFO 11476 --- [ main] c.h.t.jsoup.modifyingdata.ModifyingData : append =
First five four Last
设置节点的属性和class值
代码
/**
* 设置节点的属性和class值
* 1. 设置属性
* 2. 设置class
*
* html:https://jsoup.org/cookbook/modifying-data/set-attributes
* @throws IOException
*/
public void setAttributeValues() throws IOException{
Document doc = getDoucment();
Element div = doc.select("div").first();
// 在div节点上增加属性和其值:输出 -->
div.attr("title", "nofollow");
logger.info("div={}", div);
// 在div节点上增加class和其值:输出 -->
div.addClass("round-box");
logger.info("div={}", div);
}
输出
2017-06-07 22:39:44.466 INFO 11280 --- [ main] c.h.t.jsoup.modifyingdata.ModifyingData : div="nofollow">
2017-06-07 22:39:44.466 INFO 11280 --- [ main] c.h.t.jsoup.modifyingdata.ModifyingData : div="nofollow" class="round-box">
详细代码见 github代码