目录
需求5:找到无序列表(ul)中每一项(li)包裹的链接(a)内容
需求7:先获取列表项,再递归每个列表项取出包裹文本和a标签的href属性值
前言
XPath是一门在 XML 文档中查找信息的语言。XPath可用来在 XML 文档中对元素和属性进行遍历。而我们熟知的HTML恰巧属于XML的⼀个子集,所以完全可以用XPath去查找html中的内容。
模块安装
老样子,直接pip install lxml
XPath涉及的基本概念
下面是一段xml文档:
<book>
<id>1</id>
<name>野花遍地香</name>
<price>1.23</price>
<author>
<nick>周大强</nick>
<nick>周芷若</nick>
</author>
</book>
首先,先了解几个概念:
1. book, id, name, price....都被称为节点.
2. Id, name, price, author被称为book的子节点
3. book被称为id, name, price, author的父节点
4. id, name, price,author被称为同胞节点
OK~ 有了这些基础知识后, 我们就可以开始了解xpath的基本语法了
XPath基本语法
还是以一段xml为例:
<book>
<id>1</id>
<name>野花遍地香</name>
<price>1.23</price>
<nick>臭豆腐</nick>
<author>
<nick id="10086">周大强</nick>
<nick id="10010">周芷若</nick>
<nick class="jay">周杰伦</nick>
<nick class="jolin">蔡依林</nick>
<div>
<nick>热热热热热1</nick>
</div>
<span>
<nick>热热热热热2</nick>
<div>
<nick>热热热热热3</nick>
</div>
</span>
</author>
<partner>
<nick id="ppc">胖胖陈</nick>
<nick id="ppbc">胖胖不陈</nick>
</partner>
</book>
要想在Python中使用XPath解析方式,我们要先学会怎么导包。
from lxml import etree
从我们刚刚下载好的lxml库中导入etree模块,这样就能进行XPath解析了。
我们把上面的xml范例写入文件,用etree.XML()来处理这段信息:
xml = """
<book>
<id>1</id>
<name>野花遍地香</name>
<price>1.23</price>
<nick>臭豆腐</nick>
<author>
<nick id="10086">周大强</nick>
<nick id="10010">周芷若</nick>
<nick class="jay">周杰伦</nick>
<nick class="jolin">蔡依林</nick>
<div>
<nick>热热热热热1</nick>
</div>
<span>
<nick>热热热热热2</nick>
<div>
<nick>热热热热热3</nick>
</div>
</span>
</author>
<partner>
<nick id="ppc">胖胖陈</nick>
<nick id="ppbc">胖胖不陈</nick>
</partner>
</book>
"""
tree = etree.XML(xml)
不仅如此,etree下除了处理xml的api还可以用etree.HTML()来处理html类型文件,还可以用etree.parse()来处理资源文件,我们后续会用到。
我们XPath是基于类似文件路径来解析的。我们可以看到标签都是互相嵌套的,所以这种算法学习了文件路径。
比如我们想请求name标签,它的XPath路径就是:/book/name。写为代码:
result = tree.xpath("/book/name")
‘/’表示层级关系。熟悉Linux的或者用Mac的应该知道第一个‘/’是根节点,Windows文件系统不会这么写,是以盘符开头的(C:/等)。其实Windows系统这么写是省略了根节点,它也存在一个根节点,它的子节点就是许多的盘符,但是出于便于阅读等各种原因就把根节点省略了。
如果想请求author标签包裹的所有nick标签,我们仍可以使用XPath:/book/author/nick就可以请求到这个路径上的所有nick标签了。这和文件系统也许有所不同,文件系统在同一目录下是不允许出现重名的,而网页中可以出现很多同样的标签。
result = tree.xpath("/book/author/nick")
这时可以打印我们的result发现它只是一个类似迭代器的结果,返回结果是找到xxx标签在xxx地址。但是我们实际想要的是它所包裹的文本内容或者属性值等有效信息,这怎么获取的?直接/text的话拿到的是它下面text标签所在位置。所以这时要text()来出马了,它的作用就是获取查找到标签所包裹的文本内容。
result = tree.xpath("/book/name/text()") # text() 拿文本
# 返回结果:['野花遍地香']
类似地,我们可以获取/book/author/nick下的所有人名:
result = tree.xpath("/book/author/nick/text()")
# 返回结果:['周大强', '周芷若', '周杰伦', '蔡依林']
但是问题来了,如果我想拿到author标签里面所有nick标签包裹的所有文字怎么办?包括热热热热热1,热热热热热2,热热热热热3?先看代码再解释:
result = tree.xpath("/book/author//nick/text()") # // 后代
# 返回结果:['周大强', '周芷若', '周杰伦', '蔡依林', '热热热热热1', '热热热热热2', '热热热热热3']
可以看到我们在author后加了双斜杠'//',它的含义是获取所有后代的nick标签,也就是这俩斜杠中间可以有0层,也可以有1,2,3,...,n层,只要是在author下的nick标签就全部被抓出来了。那么再获取text()就可以把/book/author/div/nick和/book/author/span/nick和/book/author/span/div/nick三个路径下的文本都拿到了。
实际操作中我们可能也不需要这么多层都拿到。可能只需要一层的结果,又该怎么处理?
result = tree.xpath("/book/author/*/nick/text()") # * 是任意的节点. 通配符
# 返回结果:['热热热热热1', '热热热热热2']
可以看到,它获取了/book/author/div/nick和/book/author/span/nick的内容,author和nick中间的 * 星号表示任意符号(即通配符),可以代替各种标签名,但是只能替代一层,所以没有把下一层的/book/author/span/div/nick所包裹的热热热热热3拿出来。
再练习一下,把这段xml里面所有nick包裹的文本全给我拿出来!
result = tree.xpath("/book//nick/text()")
返回结果:['臭豆腐', '周大强', '周芷若', '周杰伦', '蔡依林', '热热热热热1', '热热热热热2', '热热热热热3', '胖胖陈', '胖胖不陈']
简单得很,只需要在book下面加双斜杠就好。
测试完整代码
测试的时候要把不必要的代码加注释,这样可以方便的查看运行结果,也可以通过debug看结果。
给代码行加注释在PyCharm中有个快捷键:ctrl+/,可以给选中的代码行加注释,很方便。
# Created at UESTC
# Author: Vector Kun
# Time: 2023/1/2 14:16
# xpath 是在XML文档中搜索内容的一门语言
# html是xml的一个子集
"""
<book>
<id>1</id>
<name>野花遍地香</name>
<price>1.23</price>
<author>
<nick>周大强</nick>
<nick>周芷若</nick>
</author>
</book>
"""
# 安装lxml模块
# pip install lxml -i xxxxxx
# xpath解析
from lxml import etree
xml = """
<book>
<id>1</id>
<name>野花遍地香</name>
<price>1.23</price>
<nick>臭豆腐</nick>
<author>
<nick id="10086">周大强</nick>
<nick id="10010">周芷若</nick>
<nick class="jay">周杰伦</nick>
<nick class="jolin">蔡依林</nick>
<div>
<nick>热热热热热1</nick>
</div>
<span>
<nick>热热热热热2</nick>
<div>
<nick>热热热热热3</nick>
</div>
</span>
</author>
<partner>
<nick id="ppc">胖胖陈</nick>
<nick id="ppbc">胖胖不陈</nick>
</partner>
</book>
"""
tree = etree.XML(xml)
# result = tree.xpath("/book") # /表示层级关系. 第一个/是根节点
# result = tree.xpath("/book/name")
# result = tree.xpath("/book/name/text()") # text() 拿文本
# result = tree.xpath("/book/author//nick/text()") # // 后代
# result = tree.xpath("/book/author/*/nick/text()") # * 是任意的节点. 通配符
result = tree.xpath("/book//nick/text()")
print(result)
XPath进阶用法
我们用一个简单的html文件来讲XPath还可以用来干什么:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Title</title>
</head>
<body>
<ul>
<li><a href="http://www.baidu.com">百度</a></li>
<li><a href="http://www.google.com">谷歌</a></li>
<li><a href="http://www.sogou.com">搜狗</a></li>
</ul>
<ol>
<li><a href="feiji">飞机</a></li>
<li><a href="dapao">大炮</a></li>
<li><a href="huoche">火车</a></li>
</ol>
<div class="job">李嘉诚</div>
<div class="common">胡辣汤</div>
</body>
</html>
放在py文件同级文件夹,命名为7_xpath_eg.html,当然可以自己修改文件名和代码部分。
需求1:让xpath处理这个资源文件
from lxml import etree
tree = etree.parse("7_xpath_eg.html")
这个时候我们就用到了parse这个函数,来处理我们本地的资源文件。
需求2:找到标签位置,如html标签
result = tree.xpath('/html')
# 运行结果:[<Element html at 0x15d667f6580>]
可以看到找到了标签位置,并指出所在地址。
需求3:找到无序列表(ul)中每一项(li)包裹的文本内容
result = tree.xpath("/html/body/ul/li/a/text()")
# 运行结果:['百度', '谷歌', '搜狗']
需求4:找到无序列表(ul)中第一项(li)包裹的文本内容
result = tree.xpath("/html/body/ul/li[1]/a/text()") # xpath的顺序是从1开始数的, []表示索引
# 运行结果:['百度']
这里有点“大逆不道”了,没有从0开始数,而是1就是第一项,这一点要注意一下。
需求5:找到无序列表(ul)中每一项(li)包裹的链接(a)内容
result = tree.xpath("/html/body/ul/li/a/@href")
# 运行结果:['http://www.baidu.com', 'http://www.google.com', 'http://www.sogou.com']
我们可以用“@属性”的方法来获取属性对应的属性值
需求6:获取href为‘dapao’包裹的文字(属性筛选)
result = tree.xpath("/html/body/ol/li/a[@href='dapao']/text()") # [@xxx=xxx] 属性的筛选
# 返回结果['大炮']
筛选属性可以在所属标签后加[@xxx属性=xxx属性值]筛选出含对应属性值的标签位置,这一点很像bs4的思路。
需求7:先获取列表项,再递归每个列表项取出包裹文本和a标签的href属性值
ol_li_list = tree.xpath("/html/body/ol/li")
for li in ol_li_list:
# 从每一个li中提取到文字信息
result = li.xpath("./a/text()") # 在li中继续去寻找. 相对查找
print(result)
result2 = li.xpath("./a/@href") # 拿到属性值: @属性
print(result2)
./相信大家比较熟悉,它的意义就是以当前路径进行相对查找,'.'就代表/html/body/ol/li。
需求8:练习:打印第一个div的文本信息
print(tree.xpath('/html/body/div[1]/text()'))
# 返回结果:['李嘉诚']
XPath进阶用法完整代码
from lxml import etree
tree = etree.parse("7_xpath_eg.html")
# result = tree.xpath('/html')
# result = tree.xpath("/html/body/ul/li/a/text()")
# result = tree.xpath("/html/body/ul/li/a/@href")
# result = tree.xpath("/html/body/ul/li[1]/a/text()") # xpath的顺序是从1开始数的, []表示索引
# result = tree.xpath("/html/body/ol/li/a[@href='dapao']/text()") # [@xxx=xxx] 属性的筛选
# print(result)
# ol_li_list = tree.xpath("/html/body/ol/li")
#
# for li in ol_li_list:
# # 从每一个li中提取到文字信息
# result = li.xpath("./a/text()") # 在li中继续去寻找. 相对查找
# print(result)
# result2 = li.xpath("./a/@href") # 拿到属性值: @属性
# print(result2)
#
# print(tree.xpath("/html/body/ul/li/a/@href"))
# print(tree.xpath('/html/body/div[1]/text()'))
# print(tree.xpath('/html/body/ol/li/a/text()'))
还是请大家自行注释对应代码段进行测试。
总结
本节我们进行了XPath的入门,学会了怎么处理xml文件和html文件,学习了多种情境和需求下XPath的用法,下一节我们将进行实战。
当前我们也已经学习了三种解析方式,大家可以自由搭配多种解析形式,快速高效获取自己想要的信息。