在可狱可囚的爬虫系列课程 07:BeautifulSoup4(bs4)库的使用中讲到了使用 CSS 选择器进行数据的爬取,其实还有另一种方式可以更好更快的抓取数据,那就是使用 XPath(XML 路径语言)。至于 XPath 和 CSS 选择器的区别,请听我娓娓道来:
一、什么是lxml
lxml 是 Python 语言中用于处理 XML 和 HTML 的功能最丰富且易于使用的库。它利用XPath就可以实现对XML和HTML进行数据解析。
二、什么是XML
XML 指可扩展标记语言,XML 是一种很像HTML的标记语言。但是 XML 被设计用来传输和存储数据,HTML 被设计用来显示数据。
因为 XML 和 HTML 很像,所以只讲其中一种,另一种大家便可以很简单的融汇贯通。
XML长这个样子👇👇👇👇,这是我自己编写的一个 XML 用例。
xml
<supermarket>
<!-- 这是一条平平无奇的注释 -->
<name>沃尔玛</name>
<address>中国四川成都</address>
<staffs>
<name age="20" sex="男">张三</name>
<name age="30" sex="男">李四</name>
<name age="23" gender="女">王五</name>
</staffs>
<goods>
<name price="8">啤酒
<count>12</count>
</name>
<name price="3">饮料
<count>5</count>
</name>
<name price="1" count="10">矿泉水</name>
<name price="5">花生</name>
<name price="6">瓜子</name>
<name price="2">火腿肠</name>
</goods>
</supermarket>
三、什么是XPath
XPath是一门在 XML 文档中查找信息的语言,用 XPath 写出的路径表达式可以在 XML 文档中进行导航。
使用 XPath 编写的路径表达式,和我们常使用的相对路径、绝对路径非常相似。那么XPath的语法是怎样的呢,我们一起来看。
四、XPath语法
首先先来说下XPath需要理解的概念,有树、节点、根节点、元素节点、属性节点、注释节点、文本节点。
- 树:整个html或者xml文档。
- 节点:树结构中的每个部分(标签、文本、属性、注释等)就是一个节点。
- 根节点:树结构中的第一个节点就是根节点(网页对应的根节点是html标签)。
- 元素节点:一个标签就是一个元素节点,例如:
<name>沃尔玛</name>
。 - 属性节点:标签内的一个属性就是一个属性节点,例如:
age="20"
。 - 注释节点:树结构中的一个注释就是一个注释节点,例如:
<!-- 这是一条平平无奇的注释 -->
。 - 文本节点:标签内的文字内容就是一个文本节点,例如:
沃尔玛
。
记住这些概念,就能很好的去寻找节点提取内容了。
五、解析
我们需要使用 lxml 模块中的 etree 包提供的 XML 方法或者 HTML 方法针对于字符串类型的 XML 或者 HTML 进行解析,返回一个 _Element 对象,针对 _Element 对象,可以使用 xpath 方法根据路径表达式进行导航。
(1)先看一下如何进行 XML 或 HTML 的解析。
python
from lxml import etree
xmlStr = """
<supermarket>
<!-- 这是一条平平无奇的注释 -->
<name>沃尔玛</name>
<address>中国四川成都</address>
<staffs>
<name age="20" sex="男">张三</name>
<name age="30" sex="男">李四</name>
<name age="23" gender="女">王五</name>
</staffs>
<goods>
<name price="8">啤酒
<count>12</count>
</name>
<name price="3">饮料
<count>5</count>
</name>
<name price="1" count="10">矿泉水</name>
<name price="5">花生</name>
<name price="6">瓜子</name>
<name price="2">火腿肠</name>
</goods>
</supermarket>
"""
root = etree.XML(xmlStr)
print(root)
# <Element supermarket at 0x1047401c0>
上述代码通过 etree 中的 XML方法将 XML 文档转化成了一个节点对象,且已经告知我们此处的节点为 supermarket。
(2)继续向下看,我们学习 xpath 语法提取具体内容。
先来看xpath方法的语法:节点对象.xpath(路径表达式)
路径表达式分为绝对路径、相对路径。但是,一般我们都是使用相对路径,所以只需要精通相对路径即可。
- 绝对路径:不管是哪个节点对象在调用xpath方法,都是以
/
开头,然后从根节点开始一层一层写起,节点和节点之间使用。 - 相对路径:哪个节点对象在调用xpath方法,这个节点就使用
.
表示,然后从此处一层一层开始写。
注意:节点和节点之间使用/
间隔,跨越节点要使用//
。
python
# 问题一:使用相对路径获取staffs节点下的三个name节点
nameList = root.xpath('./staffs/name')
print(nameList)
# [<Element name at 0x105551940>, <Element name at 0x105551980>, <Element name at 0x1055519c0>]
# 结果为上述注释,注意,因为此处的root为supermarket节点,下一级便是staffs节点,此处使用的相对路径,所以.表示的便是supermarket节点
# 问题二:使用绝对路径获取staffs节点下的三个name节点
nameList = root.xpath('/supermarket/staffs/name')
print(nameList)
# [<Element name at 0x105551940>, <Element name at 0x105551980>, <Element name at 0x1055519c0>]
# 问题三:获取所有的name节点
nameList = root.xpath('//name')
print(nameList)
# [<Element name at 0x105551a00>, <Element name at 0x105551940>, <Element name at 0x105551980>, <Element name at 0x1055519c0>, <Element name at 0x105551a40>, <Element name at 0x105551ac0>, <Element name at 0x105551b00>, <Element name at 0x105551b40>, <Element name at 0x105551b80>, <Element name at 0x105551a80>]
在路径的最后添加/text()
,可以得到此节点里面的内容;添加/@属性名
,可以得到此节点中属性对应的值。
python
nameList = root.xpath('./staffs/name/text()')
print(nameList)
# ['张三', '李四', '王五']
sexList = root.xpath('./staffs/name/@sex')
print(sexList)
# ['男', '男']
谓语,在路径中需要添加条件的节点后面添加[],在[]中写条件。
- [N]:表示获取第N个节点,从数字1开始。
python
# 获取第二个员工的姓名
print(root.xpath('./staffs/name[2]/text()'))
# ['李四']
- [last()]:表示获取最后一个节点。
python
# 获取倒数第二个员工的姓名
print(root.xpath('./staffs/name[last()-1]/text()'))
# ['李四']
- position():获取某些位置的标签
python
# 获取前两个员工的年龄
print(root.xpath('./staffs/name[position()<=2]/@age'))
print(root.xpath('./staffs/name[position()<3]/@age'))
# ['20', '30']
# 获取后两个员工的年龄
print(root.xpath('./staffs/name[position()>=last()-1]/@age'))
print(root.xpath('./staffs/name[position()>last()-2]/@age'))
# ['30', '23']
- [@属性名]
python
# 获取有年龄信息的员工姓名
print(root.xpath('./staffs/name[@age]/text()'))
# ['张三', '李四', '王五']
- [@属性名=属性值]
python
# 获取属性sex="男"的员工的姓名
print(root.xpath('./staffs/name[@sex="男"]/text()'))
# ['张三', '李四']
- [子标签名=值]、[子标签名>值]等
python
# 获取有count子标签且标签内容等于12的父标签的内容
print(root.xpath('./goods/name[count=12]/text()'))
# ['啤酒\n ', '\n ']
分支(| ):将多个路径选择器使用|
间隔,同时获取多个元素。
python
print(root.xpath('./staffs/name/text()|./staffs/name/@age'))
# ['20', '张三', '30', '李四', '23', '王五']
print(root.xpath('./staffs/name/text()|./staffs/name/@sex'))
# ['男', '张三', '男', '李四', '王五']
总结,与CSS选择器一样,XPath路径选择器也需要灵活运用。