第三课: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中的实体引用
如果想要在内容中打出如 > 这样的字符,需要做一些特定的替代
< <
> >
& &
' '
" "
4. 命名规则
名称不能以数字或者标点符号开始
名称不能以字母 xml(或者 XML、Xml 等等)开始
名称不能包含空格
5. HTML和XML的区别
XML与HTML 的语法格式非常像,但是有区别:
| 区别点 | HTML | XML |
|---|---|---|
| 标签 | 预定义的,如 <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 数据。