解析XML


一、解析XML

1、简介

在日常开发中常见的XML解析方式有如下两种:

DOM解析

DOM解析要求解析器将整个XML文件全部加载到内存中,生成一个Document对象。

1.优点:元素和元素之间保留结构,关系,可以针对元素进行增删改查操作。

2.缺点:如果XML文件过大,可能会导致内存溢出。

SAX解析

SAX解析是一种更加高效的解析方式。它是逐行扫描,边扫描边解析,并且以时间驱动的方式进行具体的解析,每解析一行都会触发一个事件。

1.优点:不会出现内存溢出的问题,可以处理大文件。

2.缺点:只能读,不能写。

2、常见的解析XML类库

解析器就是根据不同的解析方式提供具体的实现,为了方便开发人员来解析XML,有一些方便操作的类库。具体如下所示:

1.dom4j:比较简单的XML解析类库;

2.Jsoup:功能强大的DOM方式解析的类库,尤其对HTML的解析更加方便,所以可以使用Jsoup来爬取网页的数据。

3、命名空间

XML 命名空间提供避免元素命名冲突的方法。

(1)命名冲突

在 XML 中,元素名称是由开发者定义的,当两个不同的文档使用相同的元素名时,就会发生命名冲突。

这个 XML 携带 HTML 表格的信息:

<table>
    <tr>
        <td>Apples</td>
        <td>Bananas</td>
    </tr>
</table>

这个 XML 文档携带有关桌子的信息(一件家具):

<table>
    <name>African Coffee Table</name>
    <width>80</width>
    <length>120</length>
</table>

假如这两个 XML 文档被一起使用,由于两个文档都包含带有不同内容和定义的 <table> 元素,就会发生命名冲突。

XML 解析器无法确定如何处理这类冲突。

(2)使用前缀来避免命名冲突

在 XML 中的命名冲突可以通过使用名称前缀从而容易地避免。

该 XML 携带某个 HTML 表格和某件家具的信息:

<h:table>
    <h:tr>
        <h:td>Apples</h:td>
        <h:td>Bananas</h:td>
    </h:tr>
</h:table>

<f:table>
    <f:name>African Coffee Table</f:name>
    <f:width>80</f:width>
    <f:length>120</f:length>
</f:table>

在上面的实例中,不会有冲突,因为两个 <table> 元素有不同的名称。

(3)XML 命名空间 - xmlns 属性

当在 XML 中使用前缀时,一个所谓的用于前缀的命名空间必须被定义。

命名空间是在元素的开始标签的 xmlns 属性中定义的。

命名空间声明的语法如下。xmlns:前缀=”URI“。

<root>

    <h:table xmlns:h="http://www.w3.org/TR/html4/">
        <h:tr>
            <h:td>Apples</h:td>
            <h:td>Bananas</h:td>
        </h:tr>
    </h:table>

    <f:table xmlns:f="http://www.w3cschool.cc/furniture">
        <f:name>African Coffee Table</f:name>
        <f:width>80</f:width>
        <f:length>120</f:length>
    </f:table>

</root>

在上面的实例中,<table> 标签的 xmlns 属性定义了 h: 和 f: 前缀的合格命名空间。

当命名空间被定义在元素的开始标签中时,所有带有相同前缀的子元素都会与同一个命名空间相关联。

命名空间,可以在他们被使用的元素中或者在 XML 根元素中声明:

<root xmlns:h="http://www.w3.org/TR/html4/"
      xmlns:f="http://www.w3cschool.cc/furniture">

    <h:table>
        <h:tr>
            <h:td>Apples</h:td>
            <h:td>Bananas</h:td>
        </h:tr>
    </h:table>

    <f:table>
        <f:name>African Coffee Table</f:name>
        <f:width>80</f:width>
        <f:length>120</f:length>
    </f:table>

</root>

注释:命名空间 URI 不会被解析器用于查找信息。

其目的是赋予命名空间一个惟一的名称。不过,很多公司常常会作为指针来使用命名空间指向实际存在的网页,这个网页包含关于命名空间的信息。

二、dom4j

1、使用步骤

(1)依赖

<dependency>
    <groupId>org.dom4j</groupId>
    <artifactId>dom4j</artifactId>
    <version>2.1.1</version>
</dependency>

(2)XML文件

在项目的 resource 目录下创建 user.xml 文件;

在下面 user.xml 文件中,users 是根标签,根标签是全局唯一的;

在根标签下有两个 user 子标签,每一个 user 子标签都有两个属性,一个是 country,另一个是 source ;在 user 标签下同样有三个子标签,分别是id,name以及password标签,具体如下所示:

<?xml version="1.0" encoding="UTF-8" ?>
<!--文档声明
    XML的文档声明是可选的,也就是可以不写,但是日常生活开发中大家都会写
    XML文档声明如果写了,它必须放在XML文档的第一行第一列,必须以<?xml开头 以?>结尾,而且必须包含两个属性
    一个是version,表示XML的版本
    一个是encoding,表示XML的编码
-->
<!--
    元素是XML的重要组成部分,元素也被称为标签
    每个XML文件必须要有一个根标签
    标签有开始标签和结束标签组成,开始标签和结束标签可以写标签,也可以是文本字符串
    标签可以嵌套使用,但是不能随便嵌套
    标签名必须准守命名规则和命名规范
-->
<!--
    属性是标签的组成部分,属性只能定义在开始标签中,不能定义在结束标签中
    属性定义的格式:属性名=属性值,属性值需要使用""包含起来
    开始标签中可以定义多个属性,但是多个属性的属性名不能相同
    属性名必须准守命名规则和命名规范
-->
<users>
    <user id="10001" country="Chinese" source="Android">
        <id>10001</id>
        <name>admin</name>
        <password>111111</password>
    </user>

    <user id="10002" country="Chinese" source="ios">
        <id>10002</id>
        <name>tony</name>
        <password>666666</password>
    </user>

</users>

(3)获取document对象

//创建解析器对象
SAXReader saxReader = new SAXReader();
//DOMReader domReader = new DOMReader();

//根据user.xml文档生成Document对象
Document document = saxReader.read(DomTest.class.getClassLoader().getResource("xml/user.xml"));

read() 方法有下面几种重载方式

public Document read(File file){}

public Document read(URL url){}

public Document read(String systemId){}

public Document read(InputStream in){}

public Document read(Reader reader){}

public Document read(InputStream in, String systemId){}

public Document read(Reader reader, String systemId){}

public Document read(InputSource in){}

systemId:是一个 URL 或文件名,如果systemId包含一个’:’字符,那么它被认为是一个URL,否则它被认为是一个文件名。如果你想要对这个机制进行更细粒度的控制,那么请显式地传递一个URL或者一个文件对象而不是一个String来表示文档的源。

(4)Document API

Dom4j的常用API说明:

方法 操作
Element getRootElement(); 获取XML文件的根节点
String getName(); 返回标签的名称
List<Element> elements(); 获取标签所有的子标签
String arrtributeVallue(String name); 获取指定属性名称的属性值
String getText(); 获取标签的文本
String elementText(String name); 获取指定名称的子标签的文本,返回子标签文本的值

示例代码

//创建解析器对象
SAXReader saxReader = new SAXReader();
//DOMReader domReader = new DOMReader();

//根据user.xml文档生成Document对象
Document document = saxReader.read(DomTest.class.getClassLoader().getResource("xml/user.xml"));

// 获取根标签
Element rootElement = document.getRootElement();

// 获取根标签的所有子标签
List<Element> elements = rootElement.elements();

for (Element element : elements) {
    System.out.println("标签名称: " + element.getName());
    System.out.println("标签内容: " + element.getText());
    System.out.println("标签id属性值: " + element.attributeValue("id"));
}

// 获取根标签下的第一个user标签
Element userElement = rootElement.element("user");
System.out.println(userElement);

2、结合 XPath

XPath 可以使用 路径表达式 来选取 XML 文档中的元素或者属性节点,节点是沿着路径来选取的

(1)依赖

<dependency>
    <groupId>jaxen</groupId>
    <artifactId>jaxen</artifactId>
    <version>1.2.0</version>
</dependency>

(2)Dom4j提供基于XPath的API

方法 操作
Node selectSingleNode(String xpathExpression); 根据XPath表达式获取单个标签(元素/节点)
List<Node> selectNodes(String xpathExpression) 根据XPath表达式获取多个标签(元素/节点)

(3)示例代码

// 获取根标签下的第一个user标签
Element userElement = rootElement.element("user");
System.out.println(userElement);

// 绝对路径获取标签,如果有多个,默认返回第一个
Node node = rootElement.selectSingleNode("/users/user/name");
System.out.println(node.getText());

// 相对径获取标签
Node node2 = rootElement.selectSingleNode("./user/name");
System.out.println(node2.getText());

// 全文搜索获取标签
Node node3 = rootElement.selectSingleNode("/users//name");
System.out.println(node3.getText());

// 条件搜索获取标签
Node node4 = rootElement.selectSingleNode("//user[@id=10002]");
Node nameNode = node4.selectSingleNode("./name");
System.out.println(nameNode.getText());

(4)路径表达式

表达式 示例 描述
nodename users 选取users节点的所有子节点
/ /users/user 从根节点开始选取(绝对路径)每层只取子节点
// /users//user 选取users下的所有user节点,包括孙子节点
. 选取当前节点
.. 选取当前节点的父节点
@ //@lang 选取属性,选取所有拥有 lang 属性的元素

(5)谓语

谓语用来查找某个特定的节点或者包含某个指定的值的节点。

谓语被嵌在方括号中。

路径表达式 结果
/bookstore/book[1] 选取属于 bookstore 子元素的第一个 book 元素。
/bookstore/book[last()] 选取属于 bookstore 子元素的最后一个 book 元素。
/bookstore/book[last()-1] 选取属于 bookstore 子元素的倒数第二个 book 元素。
/bookstore/book[position()<3] 选取最前面的两个属于 bookstore 元素的子元素的 book 元素。
//title[@lang] 选取所有拥有名为 lang 的属性的 title 元素。
//title[@lang=’eng’] 选取所有 title 元素,要求这些元素拥有值为 eng 的 lang 属性。
//title[@lang!=’eng’] 选取所有 title 元素,要求这些元素拥有值为 eng 不为 lang 属性。
//title[not(@id)] 选取id属性不存在的title
/bookstore/book[price>35.00] 选取所有 bookstore 元素的 book 元素,要求book元素的子元素 price 元素的值须大于 35.00。
/bookstore/book[price>35.00]/title 选取所有 bookstore 元素中的 book 元素的 title 元素,要求book元素的子元素 price 元素的值须大于 35.00

(6)选取未知节点(通配符)

XPath 通配符可用来选取未知的 XML 元素。

通配符 描述
* 匹配任何元素节点
@* 匹配任何属性节点
node() 匹配任何类型的节点

实例

路径表达式 结果
/bookstore/* 选取 bookstore 元素的所有子节点
//* 选取文档中的所有元素
//title[@*] 选取所有带有属性的 title 元素。

(7)选取若干路径(或)

通过在路径表达式中使用 ‘|’ 运算符,您可以选取若干个路径。

实例

路径表达式 结果
//book/title | //book/price 选取所有 book 元素的 title 和 price 元素。
//title | //price 选取所有文档中的 title 和 price 元素。
/bookstore/book/title|//price 选取所有属于 bookstore 元素的 book 元素的title 元素,以及文档中所有的 price 元素。

(8)XPath 轴

轴可定义某个相对于当前节点的节点集。

轴名称 结果
ancestor 选取当前节点的所有先辈(父、祖父等)
ancestor-or-self 选取当前节点的所有先辈(父、祖父等)以及当前节点本身
attribute 选取当前节点的所有属性
child 选取当前节点的所有子元素。
descendant 选取当前节点的所有后代元素(子、孙等)。
descendant-or-self 选取当前节点的所有后代元素(子、孙等)以及当前节点本身。
following 选取文档中当前节点的结束标签之后的所有节点。
namespace 选取当前节点的所有命名空间节点
parent 选取当前节点的父节点。
preceding 选取文档中当前节点的开始标签之前的所有节点。
preceding-sibling 选取当前节点之前的所有同级节点。
self 选取当前节点。

(9)路径

位置路径可以是绝对的,也可以是相对的。

绝对路径起始于正斜杠( / ),而相对路径不会这样。在两种情况中,位置路径均包括一个或多个步,每个步均被斜杠分割:

/step/step/...
step/step/...

每个步均根据当前节点集之中的节点来进行计算。

轴(axis):定义所选节点与当前节点之间的树关系

节点测试(node-test):识别某个轴内部的节点

零个或者更多谓语(predicate):更深入地提炼所选的节点集

步的语法轴名称::节点测试[谓语]

实例

例子 结果
child::book 选取所有属于当前节点的子元素的 book 节点
attribute::lang 选取当前节点的 lang 属性
child::* 选取当前节点的所有子元素
attribute::* 选取当前节点的所有属性
child::text() 选取当前节点的所有文本子节点
child::node() 选取当前节点的所有子节点
descendant::book 选取当前节点的所有 book 后代
ancestor::book 选择当前节点的所有 book 先辈
ancestor-or-self::book 选取当前节点的所有book先辈以及当前节点(假如此节点是book节点的话)
child::*/child::price 选取当前节点的所有 price 孙。

(10)XPath 运算符

运算符 描述 实例 返回值
| 计算两个节点集 //book | //cd 返回所有带有 book 和 ck 元素的节点集
+ 加法 6 + 4 10
- 减法 6 - 4 2
* 乘法 6 * 4 24
div 除法 8 div 4 2
= 等于 price=9.80 如果 price 是 9.80,则返回 true。如果 price 是 9.90,则返回 fasle。
!= 不等于 price!=9.80 如果 price 是 9.90,则返回 true。如果 price 是 9.80,则返回 fasle。
< 小于 price<9.80 如果 price 是 9.00,则返回 true。如果 price 是 9.90,则返回 fasle。
<= 小于或等于 price<=9.80 如果 price 是 9.00,则返回 true。如果 price 是 9.90,则返回 fasle。
> 大于 price>9.80 如果 price 是 9.90,则返回 true。如果 price 是 9.80,则返回 fasle。
>= 大于或等于 price>=9.80 如果 price 是 9.90,则返回 true。如果 price 是 9.70,则返回 fasle。
or price=9.80 or price=9.70 如果 price 是 9.80,则返回 true。如果 price 是 9.50,则返回 fasle。
and price>9.00 and price<9.90 如果 price 是 9.80,则返回 true。如果 price 是 8.50,则返回 fasle。
mod 计算除法的余数 5 mod 2 1

3、生成 XML 文件

DocumentHelper :是用来生成生成 XML 文档的工厂类

// 新建 document 对象,来操作和生成 xml
Document document = DocumentHelper.createDocument();

// 创建 users 节点
Element userElement = document.addElement("user");

// 添加注释
userElement.addComment("users节点注释");

// 添加属性
userElement.addAttribute("id", "1");

// 添加子节点
Element nameElement = userElement.addElement("name");

// 设置节点内容
nameElement.setText("张三");

String fileName = "c:/user2.xml";

XMLWriter xmlWriter = null;
try {
    xmlWriter = new XMLWriter(new FileWriter(fileName));
    xmlWriter.write(document);
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (xmlWriter != null){
        try {
            xmlWriter.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

文件内容

<?xml version="1.0" encoding="UTF-8"?>
<user id="1"><!--users节点注释--><name>张三</name></user>

4、修改节点属性

  • Attribute::setValue 设置属性值
  • Element::setText 设置节点内容
  • Element::remove 删除节点
public static void modifyXMLFile() {  

    String oldStr = "c:/text.xml";  
    String newStr = "c:/text1.xml";  
    Document document = null;  

    //修改节点的属性  
    try {  
        // 用来读取xml文档  
        SAXReader saxReader = new SAXReader(); 
        // 读取xml文档  
        document = saxReader.read(new File(oldStr)); 
        // 用xpath查找节点book的属性  
        List list = document.selectNodes("/books/book/@show");

        Iterator iter = list.iterator();  
        while (iter.hasNext()) {  
            Attribute attribute = (Attribute) iter.next();  
            if (attribute.getValue().equals("yes"))   
                attribute.setValue("no");  
        }  
    } catch (Exception e) {  
        e.printStackTrace();  
    }  

    //修改节点的内容  
    try {  
        // 用来读取xml文档
        SAXReader saxReader = new SAXReader(); 
        // 读取xml文档
        document = saxReader.read(new File(oldStr)); 

        // 用xpath查找节点book的内容  
        List list = document.selectNodes("/books/book/title");

        Iterator iter = list.iterator();  
        while (iter.hasNext()) {  
            Element element = (Element) iter.next();  
            element.setText("xxx");// 设置相应的内容  
        }  
    } catch (Exception e) {  
        e.printStackTrace();  
    }  

    try {
        XMLWriter writer = new XMLWriter(new FileWriter(new File(newStr)));  
        writer.write(document);  
        writer.close();  
    } catch (Exception ex) {  
        ex.printStackTrace();  
    }  
}  

5、编码转换

  • 文档中全为英文,不设置编码,直接写入
XMLWriter writer = new XMLWriter(new FileWriter("output.xml"));      
writer.write(document);      
writer.close();     
  • 文档中含有中文,设置编码格式再写入
OutputFormat format = OutputFormat.createPrettyPrint();
format.setEncoding("GBK");    // 指定XML编码
XMLWriter writer = new XMLWriter(new FileWriter("output.xml"),format);
writer.write(document);
writer.close();

6、字符串与XML的转换

将字符串转化为XML

String text = "<members><member>sitinspring</member></members>";      
Document document = DocumentHelper.parseText(text);      

将XML转化为字符串.

SAXReader reader = new SAXReader();
Document document = reader.read(new File("input.xml"));
Element root = document.getRootElement();

String docXmlText = document.asXML();
String rootXmlText = root.asXML();

Element memberElm = root.element("member");
String memberXmlText = memberElm.asXML();

7、事件模型

(1)SAXReader类

只有 SAXReader 才有事件驱动,是逐行扫描,边扫描边解析,并且以时间驱动的方式进行具体的解析,每解析一行都会触发一个事件。

当解析到 path 指定的路径时,将调用参数handler指定的处理器。针对不同的节点可以添加多个handler实例。或者调用默认的 Handler setDefaultHandler(ElementHandler handler);

public class SAXReader {

    // 为指定路径的标签添加处理器
    public void addHandler(String path, ElementHandler handler){}

    // 删除指定路径标签的处理器
    public void removeHandler(String path){}

    // 清空所有处理器
    public void resetHandlers(){}
}

示例代码

//创建解析器对象
SAXReader saxReader = new SAXReader();

saxReader.removeHandler("/users/user/name");

saxReader.resetHandlers();

(2)ElementHandler接口

  • onStart() 该方法在解析到元素的开始标签时被调用。

  • onEnd() 该方法在解析到元素的结束标签时被调用

//创建解析器对象
SAXReader saxReader = new SAXReader();

saxReader.addHandler("/users/user/name", new ElementHandler() {

    /**
    * 解析到元素的开始标签时被调用
    * 注意:开始标签中无法读取到标签内容
    */
    @Override
    public void onStart(ElementPath elementPath) {
        System.out.println("start ...");
        // 输出为空
        System.out.println(elementPath.getCurrent().getText());
    }

    /**
    * 解析到元素的结束标签时被调用
    */
    @Override
    public void onEnd(ElementPath elementPath) {
        System.out.println("end ...");
        System.out.println(elementPath.getCurrent().getText());

        // 获取当前元素
        Element current = elementPath.getCurrent();

        // 指定深度索引处的元素,0=根元素,users
        Element rootElement = elementPath.getElement(0);

        // 获取当前节点的路径 /users/user/name
        String path = elementPath.getPath();

        // 同 SAXReader
        //elementPath.removeHandler();
        //elementPath.addHandler();
    }
});

//根据user.xml文档生成Document对象
Document document = saxReader.read(Test1.class.getClassLoader().getResource("xml/users.xml"));

(3)Element API

方法 说明
getQName() 元素的QName对象
getNamespace() 元素所属的Namespace对象
getNamespacePrefix() 元素所属的Namespace对象的prefix
getNamespaceURI() 元素所属的Namespace对象的URI
getName() 元素的local name
getQualifiedName() 元素的qualified name
getText() 元素所含有的text内容,如果内容为空则返回一个空字符串而不是null
getTextTrim() 元素所含有的text内容,其中连续的空格被转化为单个空格,该方法不会返回null
attributeIterator() 元素属性的iterator,其中每个元素都是Attribute对象
attributeValue() 元素的某个指定属性所含的值
elementIterator() 元素的子元素的iterator,其中每个元素都是Element对象
element() 元素的某个指定(qualified name或者local name)的子元素
elementText() 元素的某个指定(qualified name或者local name)的子元素中的text信息
getParent() 元素的父元素
getPath() 元素的XPath表达式,其中父元素的qualified name和子元素的qualified name之间使用”/“分隔
isTextOnly() 是否该元素只含有text或是空元素
isRootElement() 是否该元素是XML树的根节点

三、jsoup

1、使用步骤

(1)依赖

<!--解析网页-->
<dependency>
    <groupId>org.jsoup</groupId>
    <artifactId>jsoup</artifactId>
    <version>1.10.2</version>
</dependency>

(2)获取Document对象

// 参数1: html 文本
// 参数2: 文档基础 uri,用于组合解析html文本中的相对路径
public static Document parse(String html, String baseUri){}

public static Document parse(String html){}

public static Document parse(File in, String charsetName, String baseUri){}

public static Document parse(File in, String charsetName){}

public static Document parse(InputStream in, String charsetName, String baseUri){}

(3)API

/**
* 1.    根据id查询元素getElementById
* 2.    根据标签获取元素getElementsByTag
* 3.    根据class获取元素getElementsByClass
* 4.    根据属性获取元素getElementsByAttribute
*
*  Elements skuEles = spuEle.select("li.ps-item");
*  String picUrl ="https:"+ skuEle.select("img[data-sku]").first().attr("data-lazy-img");
*/
String path = ItCastSpidder.class.getClassLoader().getResource("index.html").getPath();

Document document = Jsoup.parse(new File(path), "UTF-8");

//1. 获取title的内容
Element element = document.getElementById("city_bj");

//2.   根据标签获取元素getElementsByTag
element = document.getElementsByTag("title").first();

//3.   根据class获取元素getElementsByClass
element = document.getElementsByClass("s_name").last();

//4.   根据属性获取元素getElementsByAttribute
element = document.getElementsByAttribute("abc").first();
element = document.getElementsByAttributeValue("class", "city_con").first();

//tagname: 通过标签查找元素,比如:span
Elements span = document.select("span");
for (Element element1 : span) {
    System.out.println(element1.text());
}

//#id: 通过ID查找元素,比如:#city_bjj
String str = document.select("#city_bj").text();

//.class: 通过class名称查找元素,比如:.class_a
str = document.select(".class_a").text();
System.out.println(".class_a" + str);

//[attribute]: 利用属性查找元素,比如:[abc]
str = document.select("[abc]").text();
System.out.println("[abc]" + str);

//[attr=value]: 利用属性值来查找元素,比如:[class=s_name]
str = document.select("[class=s_name]").text();
System.out.println("[class=s_name]" + str);

//el#id: 元素+ID,比如: h3#city_bj
str = document.select("h3#city_bj").text();
System.out.println("[h3#city_bj]" + str);

//el.class: 元素+class,比如: li.class_a
str = document.select("li.class_a").text();
System.out.println("[li.class_a]" + str);

//el[attr]: 元素+属性名,比如: span[abc]
str = document.select("span[abc]").text();
System.out.println("[span[abc]]" + str);

//任意组合,比如:span[abc].s_name
str = document.select("span[abc].s_name").text();
System.out.println("[span[abc].s_name]" + str);

//ancestor child: 查找某个元素下子元素,比如:.city_con li 查找"city_con"下的所有li
str = document.select(".city_con li").text();
System.out.println("[.city_con li]" + str);

//parent > child: 查找某个父元素下的直接子元素,
//比如:.city_con > ul > li 查找city_con第一级(直接子元素)的ul,再找所有ul下的第一级li
str = document.select(".city_con > ul > li").text();
System.out.println("[.city_con > ul > li]" + str);

//parent > * 查找某个父元素下所有直接子元素.city_con > *
str = document.select(".city_con > *").text();
System.out.println(".city_con > *" + str);

(4)示例代码

public List<Content> goods(String keyword) throws IOException {
    //获取请求
    String url= "https://search.jd.com/Search?keyword="+keyword+"&enc=utf-8";
    //解析网页
    Document document = Jsoup.parse(new URL(url), 30000);
    Element element = document.getElementById("J_goodsList");
    //获取所有的li元素
    Elements li = element.getElementsByTag("li");

    ArrayList<Content> goodsList = new ArrayList<>();

    for (Element el : li) {
        //获取每一个li标签下所有的img标签下的第一个元素,属性为src
        String img = el.getElementsByTag("img").eq(0).attr("data-lazy-img");
        //获取每一个li标签下所有的p-price标签下的第一个元素,并转换为文档
        String price = el.getElementsByClass("p-price").eq(0).text();

        String title = el.getElementsByClass("p-name").eq(0).text();

        // 封装获取的数据
        Content content = new Content();
        content.setTitle(title);
        content.setImg(img);
        content.setPrice(price);
        //将封装的好的对象放入list集合
        goodsList.add(content);
    }
    return goodsList;
}

2、结合XPath

(1)依赖

<dependency>
    <groupId>cn.wanghaomiao</groupId>
    <artifactId>JsoupXpath</artifactId>
    <version>2.2</version>
</dependency>

(2)示例代码

String path = JsoupDemo1.class.getClassLoader().getResource("student.xml").getPath();

Document document = Jsoup.parse(new File(path),"utf-8");

// 旧版 package cn.wanghaomiao.xpath.model;
// JXDocument jxDocument = new JXDocument(document);
// 新版 package org.seimicrawler.xpath;
JXDocument jxDocument = JXDocument.create(document);

//获取dom树种的student标签的内容
List<JXNode> jxNodes = jxDocument.selN("//student");
for (JXNode jxNode : jxNodes) {
    System.out.println(jxNode);
}
System.out.println("-----------------");
//获取number属性的值  @获取属性值
List<JXNode> nodes = jxDocument.selN("//@number");
for (JXNode node : nodes) {
    System.out.println(node);
}

  目录