Python爬虫解析库-XPath

除了使用正则表达式提取网页中数据之外, XPath也是一个强大的网页文本内容提取工具, XPath全称XML Path Language, 即XML路径语言, 最初用来在XML文档的, 但是同样适用于HTML文档的搜索.

1. 安装

1
pip install lxml

2. XPath常用规则

表达式 描述
nodename 选取此节点的所有子节点
/ 从当前节点选择直接子节点
// 从当前节点选取子孙节点
. 选取当前节点
.. 选取当前节点的父节点
@ 选取属性

3. html文本引入和预处理

文本引入可以在内存中和外部文件中获取.

tostring函数可以对一段html文本进行完善和补全(自动修正), 如果缺少某个封闭标记, 执行后可以添加上去.

从内存中获取
1
2
3
4
5
from lxml import etree
text = '待解析文本'
html = etree.HTML(text) #传入字符串文本,并生成XPath对象
result = etree.tostring(html) # 对文本进行完善处理, 自动修正
print(result.decode('utf8')) # tostring后的文本是bytes类型, 需要转换为str类型
从文件中获取
1
2
3
html = etree.parse('./test.html', etree.HTMLParser())
result = etree.tostring(html) # 对文本进行完善处理, 自动修正
print(result.decode('utf8')) # tostring后的文本是bytes类型, 需要转换为str类型

4. 具体内容

1. 所有节点

我们一般会用//开头的XPath规则来选取所有符合要求的节点.

例如:

(1) //*: 获取所有节点

(2) //li: 获取所有子孙节点中的li节点

使用方法
1
html.xpath("//*") #这里html是XPath对象, 返回的是列表对象

2. 子节点

通过//或/来获取所有子孙节点或直接子节点.

例如:

(1) //li/a: 获取所有li节点的直接子节点a

(2) //li//a: 获取所有li节点中的所有子孙节点a

3. 父节点

通过..来获取父节点, 这里需要注意/的作用

1
2
3
4
5
6
7
8
9
10
text = """
<a class="item" target="_blank" href="https://movie.douban.com/subject/34454003/?tag=热门&amp;from=gaia">

<div class="cover-wp" data-isnew="true" data-id="34454003">
<img src="https://img1.doubanio.com/view/photo/s_ratio_poster/public/p2561672299.jpg" alt="夺爱之夏" data-x="1648" data-y="928">
</div>
"""
html = etree.HTML(text)
res = html.xpath('//div[@data-id="26849758"]/../@href')
print(res)

上面使用//div[@data-id=”26849758”]来先找出所有属性为该属性的div节点, 再用..找出父节点, 最后使用@href来获取父节点的href属性

4. 属性匹配和属性获取

属性匹配中[]括起来的内容表示选取节点的条件, 使用@表示属性, 不用方括号使用@表示直接属性的获取

具体案例看上节父节点相关内容

5. 文本内容获取

我们使用text()来获取节点中的文本

例:

//a//text(): 获取所有li节点的内容

需要注意的是, 在获取一个节点的内容,其子孙节点的内容是不会被获取的, 并且经常加载\n等符号, 使得结果不纯净, 还需要在提取

6. 属性多值匹配

有时候一个属性有多个值, 这时需要使用contains来提取

1
2
3
4
5
6
7
from lxml import etree
text = '''
<li class="li li-first" name="item"><a href="link.html">firstitem</a></li>
'''
html = etree.HTML(text)
result = html.xpath('//li[contains(@class,"li")]/a/text()')
print(result)

上面例子中, li的class属性有两个值, li和li-first,如果直接用//li[@class=”li”]/a/text()就会获取不到了.

利用contains可以有效解决这一问题

PS: 在实际测试中,发现直接写@class=”li li-first”也能获取出来…

7. 多属性匹配

有时候一个节点有多个属性, 如果要精确定位该节点, 可以使用多个属性值来定位.

例:
1
2
3
4
5
6
7
from lxml import etree
text = '''
<li class="li li-first" name="item"><a href="link.html">firstitem</a></li>
'''
html = etree.HTML(text)
result = html.xpath('//li[contains(@class,"li") and name="item"]/a/text()')
print(result)

拓展: XPath运算符

8. 按序选择

有时候在选择某些属性的时候可能匹配了多个节点, 但是指向要其中的某个节点, 第一个节点等, 这时候可以使用中括号传入索引的方法获取特定次序的节点.

例:
1
2
3
4
html.xpath('//li[1]/a/text()') #获取第一个节点, 注意是序号从1开始
html.xpath('//li[last()]/a/text()') #获取最后一个节点
html.xpath('//li[last()-2]/a/text()') #获取倒数第二个
html.xpath('//li[position()<3]/a/text()') #获取第1和2个节点

拓展: XPath函数

具体看网址: https://www.w3school.com.cn/xpath/xpath_functions.asp

9. XPath节点轴

具体看网址: https://www.w3school.com.cn/xpath/xpath_axes.asp

参考

  1. 崔庆才: 《Python3网络爬虫开发实战》
  2. W3C_School: https://www.w3school.com.cn/xpath/xpath_syntax.asp