Skip to main content

第三课:XML文件处理

有时我们会爬取整个网页的内容,这样得到的会是一个 HTML格式 或者 XML格式 的文本或者文件,里面包含了能在页面上直接显示的所有信息。HTML 和 XML 的处理方式 有异同点,但大致相似。下面介绍什么是 XML 文件以及如何处理 XML 文件。

(一)XML 文件

1. XML 简介

XML是一种标记语言,用于描述数据。它允许设计者自定义标签(markup symbols),以便更好地描述和结构化数据。 XML 文档必须包含根元素。该元素是所有其他元素的父元素。父元素拥有子元素。相同层级上的子元素成为同胞元素(节点)。

示例:

<person>                                          这里是父节点
    <name>John Doe</name>                         这三行都是子节点,他们互为同胞节点(元素)
    <email>john@example.com</email>               一行的前后两个<email>分别为打开标签和关闭标签(开始标签和结束标签)
    <age>30</age>
</person>

再来一个一个真实XML示例:

<?xml version="1.0" encoding="UTF-8"?>            第一行是 XML 声明。它定义 XML 的版本(1.0)和所使用的编码
<note>                                            描述文档的根元素(像在说:"本文档是一个便签")
  <to>Tove</to>                                   描述根的 4 个子元素
  <from>Jani</from>
  <heading>Reminder</heading>
  <body>Don't forget me this weekend!</body>
</note>                                           最后一行定义根元素的结尾

2. 多层 XML 文件

<bookstore>
    <book category="COOKING">                      注意XML中的属性(比如这里book元素的category属性)要加引号
        <title lang="en">Everyday Italian</title>
        <author>Giada De Laurentiis</author>
        <year>2005</year>
        <price>30.00</price>
    </book>
</bookstore>

XML 必须包含根元素,它是所有其他元素的父元素,比如以下实例中 root 就是根元素:

<root>
  <child>
    <subchild>.....</subchild>
  </child>
</root>

3. XML中的实体引用

如果想要在内容中打出如 > 这样的字符,需要做一些特定的替代

  &lt;    	<
&gt; >
&amp; &
&apos; '
&quot; "

4. 命名规则

名称不能以数字或者标点符号开始

名称不能以字母 xml(或者 XML、Xml 等等)开始

名称不能包含空格

5. HTML和XML的区别

XML与HTML 的语法格式非常像,但是有区别:

区别点HTMLXML
标签预定义的,如 <p>, <div>, <table>用户可自定义,如 <customer>, <order>
标签嵌套可不正确嵌套,如 <b><i>text</b></i>必须正确嵌套
关闭标签可省略关闭标签,如 <p>text必须显式关闭标签,如 <p>text</p>
作用用于格式化并显示数据用于传输和存储数据
空格处理连续空格会被合并为一个空格空格被原样保留
缩进处理不影响解析不影响解析


(二) Xpath 路径表达式

类似于 正则表达式来查找txt文本,Xpath 路径表达式用于查找XML文本

1. 基本介绍

XPath 使用路径表达式来选择 XML 文档中的节点或节点集。(比如刚刚的父节点到子节点)
由于XML文档是树形结构,这些表达式看起来很像文件系统的路径,非常适合处理 XML 数据的复杂查询。
简单说,xpath就是选择XML文件中节点的方法。

所谓节点(node),就是XML文件的最小构成单位,一共分成7种

  • element(元素节点)
  • attribute(属性节点)
  • text (文本节点)
  • namespace (名称空间节点)
  • processing-instruction (处理命令节点)
  • comment (注释节点)
  • root (根节点)

下面介绍常用的Xpath 表达式


2. 选取节点

符号含义说明
/如果在开头,表示绝对路径(从根节点开始选);如果在中间,表示元素之间的层级过渡
//当前节点之下选择所有满足条件的节点,不考虑它们的具体位置(即:任意深度匹配
.表示当前节点
..表示当前节点的父节点
*匹配任何元素节点
@*匹配任何属性节点
node()选择当前节点的所有子节点,包括元素、文本节点、注释、处理指令等
from lxml import etree

xml = '''  
<bookstore>
    <book>
          <title lang="eng">Harry Potter</title>
          <price>29.99</price>
    </book>
    <book>
          <title lang="eng">Learning XML</title>
          <price>39.95</price>
    </book>
</bookstore>
'''
result= etree.fromstring(xml)
print(result.xpath('bookstore'))        
# 为什么是空列表呢?
# 原因在于是否开头只要不是/,就是相对路径。result 实际上已经是指向 <bookstore> 元素的。
# 因此,实际上是在 <bookstore> 下寻找另一个名为 bookstore 的子元素,而这显然是不存在的

print(result.xpath('/bookstore'))    #选取根节点bookstore,这是绝对路径写法。
#[<Element bookstore at 0x28ed8200900>]  

print(result.xpath('/book'))        #绝对路径下直接找book是找不到的 

print(result.xpath('//book'))       #选择所有 book 子元素,而不管它们在文档中在什么位置。
# [<Element book at 0x2827fbd5400>, <Element book at 0x2827fbd4e40>]  

print(result.xpath('/bookstore//book'))  #选择所有属于bookstore 元素的后代的 book 元素,而不管它们是否是直接的子节点还是孙子节点等等
# [<Element book at 0x16b38394b80>, <Element book at 0x16b38395240>]

print(result.xpath('/bookstore/book'))    #选取所有直接属于bookstore 子元素的 book元素,不包括孙节点
# [<Element book at 0x16b38394b80>, <Element book at 0x16b38395240>]

print(result.xpath('//@lang'))     #选取所有名为 lang 的属性的值
# ['eng', 'eng']

print(result.xpath('//book/title/@lang'))  #选择所有的book下面的title中的lang属性的值
# ['eng', 'eng'] 

print(result.xpath('//book/title/text()')) #选择所有的book下面的title的文本
# ['Harry Potter', 'Learning XML']

print(result.xpath('/bookstore/*'))  #选取 bookstore 元素的所有子元素
# [<Element book at 0x21927875780>, <Element book at 0x21927875700>]

print(result.xpath('//*'))  #选取文档中的所有元素节点
# [<Element bookstore at 0x133f72c0b00>, <Element book at 0x133f73157c0>, 
# <Element title at 0x133f7315200>, <Element price at 0x133f7315b80>, 
# <Element book at 0x133f73158c0>, <Element title at 0x133f7315a00>, <Element price at 0x133f7315d00>]

print(result.xpath('//title[@*]'))  #选取所有带有属性的 title 元素     
# [<Element title at 0x2b53cb84cc0>, <Element title at 0x2b53cb85640>]          

print(result.xpath('//book/node()'))
# ['\n          ', <Element title at 0x2082f405bc0>, '\n          ', 
# <Element price at 0x2082f405800>, '\n    ', '\n          ', 
# <Element title at 0x2082f405c40>, '\n          ', <Element price at 0x2082f405d80>, '\n    ']
print(result.xpath('//title|//price'))   #选择所有 book 和 cd 节点
# [<Element title at 0x2001a285ac0>, <Element price at 0x2001a285500>,
#  <Element title at 0x2001a285e80>, <Element price at 0x2001a285bc0>]

注意:相同的代码每次获得的内存地址是不一样的!


3. 根据谓词筛选节点

谓词(Predicates):在方括号 [ ] 中,表示对节点进行进一步的筛选。

所谓"谓语条件",就是对路径表达式的附加条件。

from lxml import etree

xml = '''  
<bookstore>
    <book>
          <title lang="eng">Harry Potter</title>
          <price>29.99</price>
    </book>
    <book>
          <title lang="eng">Learning XML</title>
          <price>39.95</price>
    </book>
</bookstore>
'''
result= etree.fromstring(xml)
print(result.xpath('/bookstore/book[1]'))  #表示选择bookstore的第一个book子元素。
# [<Element book at 0x1f854a75700>]
print(result.xpath('/bookstore/book[last()]'))        #表示选择bookstore的最后一个book子元素。
print(result.xpath('/bookstore/book[last()-1]'))      #表示选择bookstore的倒数第二个book子元素。
print(result.xpath('/bookstore/*[last()]'))
print(result.xpath('/bookstore/book[position()<3]'))  #表示选择bookstore的前两个book子元素。

print(result.xpath('//title[@lang]'))  #表示选择所有具有lang属性的title节点。
#[<Element title at 0x1cc18574d00>, <Element title at 0x1cc18575680>]
print(result.xpath("//title[@lang='eng']"))  #表示选择所有lang属性的值等于"eng"的title节点
# [<Element title at 0x1db611f5740>, <Element title at 0x1db611f5480>]
# 注意不能内外都用单引号或双引号!
# 注意和'//book/title/@lang' 之间的区别!上面那个是选择title,而这个是选择lang! 

print(result.xpath('/bookstore/book[price]'))  #表示选择bookstore的book子元素,且被选中的book元素必须带有price子元素。
# [<Element book at 0x133092b4d80>, <Element book at 0x133092b5700>]
print(result.xpath('/bookstore/book[price>35.00]'))  #表示选择bookstore的book子元素,且被选中的book元素的price子元素值必须大于35。
# [<Element book at 0x15e051c5d80>]
print(result.xpath('/bookstore/book[price>35.00]/title'))   #表示在上一题结果集中,选择title子元素。
# [<Element title at 0x25455de5380>]
print(result.xpath('/bookstore/book/price[.>35.00]'))  #表示选择值大于35的"/bookstore/book"的price子元素。
# [<Element price at 0x1b9d56c4e40>]

4. 轴选择器

轴选择器(Axis selectors)用于定义从当前节点(上下文节点)出发,沿着不同方向遍历 XML 文档结构的方式
常用的 XPath 轴选择器

XPath轴描述
self选择当前节点。
ancestor选择当前节点的所有祖先(父、祖父等)。
ancestor-or-self选择当前节点的所有祖先以及当前节点自身。
attribute@选择当前节点的所有属性。
child选择当前节点的所有直接子节点。
descendant选择当前节点的所有后代(子、孙等)。
descatchent-or-self选择当前节点的所有后代以及当前节点自身。
following选择文档中当前节点的结束标签之后的所有节点。
following-sibling选择当前节点之后的所有同级节点。
namespace选择当前节点的所有命名空间节点。
parent选择当前节点的父节点。
preceding选择文档中当前节点的开始标签之前的所有节点。
preceding-sibling选择当前节点之前的所有同级节点。

示例 : 在表达式 ancestor::book 中:
ancestor 是轴选择器,指定了选择当前节点的所有祖先节点。
:: 是轴选择器和节点测试之间的分隔符。
book 是节点测试,限定只选择那些名为 book 的祖先节点。


text='''
<bookstore>
  <book>
    <title>Harry Potter</title>
    <author>J.K. Rowling</author>
  </book>
  <book>
    <title>Learning XML</title>
    <author>Erik T. Ray</author>
  </book>
</bookstore>
'''
result=etree.fromstring(text)
print(result.xpath('//title/descendant::node()'))  #会选择 <title> 元素的所有后代节点。
print(result.xpath('//book/attribute::'))  #选择所有 <book> 元素的属性。
print(result.xpath('//author/following-sibling::node()'))  #选择所有在 <author> 之后的同级节点。
print(result.xpath('//title/parent::node()'))  #选择 <title> 元素的父节点,即 <book> 元素。

5. 模糊查询 与 逻辑运算


text='''
<bookstore>
    <book>
        <title>Harry Potter and the Sorcerer's Stone</title>
        <author>J.K. Rowling</author>
        <price>24.99</price>
    </book>
    <book>
        <title>Learning XML</title>
        <author>Erik T. Ray</author>
        <price>39.95</price>
    </book>
    <book>
        <title>Advanced XML Programming</title>
        <author>John Doe</author>
        <price>59.99</price>
    </book>
    <book>
        <title>XML for Dummies</title>
        <author>Jane Doe</author>
        <price>29.99</price>
    </book>
</bookstore>
'''
result=etree.fromstring(text)

# 1.text()函数
print(result.xpath("//title[text()='Advanced XML Programming']"))
# [<Element title at 0x1e8b9af0d80>]


# 2.contains ()函数
print(result.xpath("//book[contains(title, 'XML')]"))  #选择 <book> 元素,其 <title> 子元素的文本包含字符串 "XML"
# [<Element book at 0x21dc6f24e00>, <Element book at 0x21dc6f25780>, <Element book at 0x21dc6f254c0>]

# 3.starts-with()函数
print(result.xpath("//book[starts-with(title, 'Harry')]")) #选择 <book> 元素,其 <title> 子元素的文本以 "Harry" 开头。
# [<Element book at 0x1e99c019480>]

# 4.name()函数(获取当前节点的名称)
print(result.xpath("name(//book[1])"))  #返回第一个 <book> 元素的节点名称,即 "book"
# book

# 5.translate()
print(result.xpath("//book[contains(translate(title, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), 'xml')]"))
# 首先将 <title> 元素的文本中的所有大写字母转换为小写,然后检查转换后的标题是否包含小写的 "xml"
# [<Element book at 0x19bfca887c0>, <Element book at 0x19bfcae5800>, <Element book at 0x19bfcae6180>]

# 6.逻辑运算符
print(result.xpath("//*[@id='count3' and contains(text(),'4')]"))
#找出全部满足要求的元素节点,要求id属性为count3 并且包含数字4

注意:

  • result= etree.fromstring(xml) 这个函数通常用于解析 XML 数据。

  • result=etree.HTML(xml) 可以用来解析 HTML 以及格式不那么严格的 XML 数据。