网络爬虫笔记—解析库Xpath
Xpath可以用来定位网页源码的节点,并可以返回节点的内容。
1、Xpath的常用规则
表达式 | 描述 |
---|---|
nodename | 选取此节点的所有子节点 |
/ | 从当前节点选取直接子节点 |
// | 从当前节点选取子孙节点,既选取子节点,又选取子节点的子节点(孙节点) |
. | 选取当前节点 |
… | 选取当前节点的父节点 |
@ | 选取属性 |
2、实例引入
为了方便大家理解Xpath内容,原书作者编写了一个简单的网页源码,内容如下:
<div>
<ul>
<li class="item-0"><a href="link1.html">first item</a></li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-inactive"><a href="link3.html">third item</a></li>
<li class="item-1"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a>
</ul>
</div>
我们可以将上面的网页源码,粘贴到文本文档中,然后将文本文档的格式修改为html,再用浏览器打开该文件,便会显示成下面的样式:
这便是一个简单的网页。然后单击右键选择检查(或点击F12)便会打开开发者选项,选择元素(element),便会出现我们上面编写的网页源码,关于网页源码的各个节点可参考下图。
from lxml import etree
text = """
<div>
<ul>
<li class="item-0"><a href="link1.html">first item</a></li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-inactive"><a href="link3.html">third item</a></li>
<li class="item-1"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a>
</ul>
</div>
"""
html = etree.HTML(text)#解析文本,构建一个Xpath解析对象
result = etree.tostring(html)#修正网页文本
print(result.decode("utf-8"))#tostring转化的格式为bytes类型,这里将其转化为字符串类型
输出结果:
<html><body><div>
<ul>
<li class="item-0"><a href="link1.html">first item</a></li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-inactive"><a href="link3.html">third item</a></li>
<li class="item-1"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a>
</li></ul>
</div>
</body></html>
从输出结果可以看出,网页源码多了<html><body>
等字符,这是因为使用etree系统会自动补全网页所需元素。
- 读取本地网页源码
from lxml import etree
html = etree.parse("实际案例.html",etree.HTMLParser())#实际案例.html为本地网页文件,其内容就是上面代码的text文本中的内容
result = etree.tostring(html)#修正网页文本
print(result.decode("utf-8"))#tostring转化的格式为bytes类型,这里将其转化为字符串类型
输出结果:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body><div>
<ul>
<li class="item-0"><a href="link1.html">first item</a></li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-inactive"><a href="link3.html">third item</a></li>
<li class="item-1"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a>
</li></ul>
</div></body></html>
从本地读取和直接写文本对比来看,从本地读取会多一些元素,但其不影响后续对文本的分析。
3、获取网页的所有节点
//
代表获取子节点或孙节点,所以一般用//
开头的Xpath规则来获取所有符合要求的节点。如果用/
的话,则只能获取直接子节点,而不能获取孙节点,会有遗漏的情况。
from lxml import etree
html = etree.parse("实际案例.html",etree.HTMLParser())#实际案例.html为本地网页文件,其内容就是上面代码的text文本中的内容
result = html.xpath("//*")#表示获取所有的节点
result_li = html.xpath("//li")#表示获取所有的li节点
print("获取的所有节点:",result)
print("获取的所有的li节点:",result_li)
输出结果:
获取的所有节点: [<Element html at 0xb093594940>, <Element body at 0xb0946cc3c0>, <Element div at 0xb0935a20c0>, <Element ul at 0xb0935a2080>, <Element li at 0xb0935a2040>, <Element a at 0xb0935a2140>, <Element li at 0xb0935a2180>, <Element a at 0xb0935a21c0>, <Element li at 0xb0935a2200>, <Element a at 0xb0935a2100>, <Element li at 0xb0935a2240>, <Element a at 0xb0935a2280>, <Element li at 0xb0935a22c0>, <Element a at 0xb0935a2300>]
获取的所有的li节点: [<Element li at 0xb0935a2040>, <Element li at 0xb0935a2180>, <Element li at 0xb0935a2200>, <Element li at 0xb0935a2240>, <Element li at 0xb0935a22c0>]
4、获取网页的子节点
from lxml import etree
html = etree.parse("实际案例.html",etree.HTMLParser())#实际案例.html为本地网页文件,其内容就是上面代码的text文本中的内容
result = html.xpath("//li/a")#表示获取所有的li节点下的直接a节点,如果要想获取li节点下的所有a节点可这样写//li//a
#因为//代表获取子孙节点,而/获取直接子节点
print(result)
输出结果:
[<Element a at 0xf08310cac0>, <Element a at 0xf08310c100>, <Element a at 0xf08310c200>, <Element a at 0xf08310c3c0>, <Element a at 0xf08310c400>]
5、获取网页的父节点
from lxml import etree
html = etree.parse("实际案例.html",etree.HTMLParser())#实际案例.html为本地网页文件,其内容就是上面代码的text文本中的内容
result = html.xpath('//a[@href="link4.html"]/../@class')
print(result)
#输出结果为:['item-1']
'//a[@href=“link4.html”]/…/@class’表示的是:先获取href属性等于“link4.html”的a节点,再获取其父节点的class属性的值。
6、属性匹配
在选择网页节点时,根据某个属性值来筛选具有该属性的节点。
from lxml import etree
html = etree.parse("实际案例.html",etree.HTMLParser())
result = html.xpath('//li[@class="item-0"]')#获取class="item-0"的li节点
print(result)
输出结果:
[<Element li at 0xb0935a2400>, <Element li at 0xb0935a2580>]
7、文本获取
获取网页节点中的文本。
from lxml import etree
html = etree.parse("实际案例.html",etree.HTMLParser())
result_1 = html.xpath('//li[@class="item-0"]/text()')#获取class="item-0"的li节点的直接文本
result_2 = html.xpath('//li[@class="item-0"]//text()')#获取class="item-0"的li节点的其本身节点和子孙节点的文本
print("单斜杠的输出结果:",result_1)
print("双斜杠的输出结果:",result_2)
输出结果:
单斜杠的输出结果: ['\r\n ']
双斜杠的输出结果: ['first item', 'fifth item', '\r\n ']
如果想获取网页节点内的文本,可以在节点后面使用text()来获取,当text()前面是单斜杠时,表示获取该节点的文本;当text()前面是双斜杠时,表示获取该节点和其子孙节点的文本。
8、属性获取
网页节点除了有文本外,还会有许多属性,例如class属性、href属性等,这些属性的值同样也可以获取。
from lxml import etree
html = etree.parse("实际案例.html",etree.HTMLParser())
result = html.xpath('//li/a/@href')#li节点的直接子节点a的href属性值
print("li节点的直接子节点a的href属性值为:",result)
输出结果:
li节点的直接子节点a的href属性值为: ['link1.html', 'link2.html', 'link3.html', 'link4.html', 'link5.html']
一般节点的属性前面会加@符号
9、属性多值匹配
有时候,某些节点的属性值会是两个字符,这时候就需要使用contains来选取属性值。
from lxml import etree
text = '''
<li class="li li-first"><a href="link.html">first item</a></li>
<li class="li li-secend"><a href="link.html">secend item</a></li>
'''
html = etree.HTML(text)
result_1 = html.xpath('//li[@class="li li-first"]/a/text()')#根据属性值直接取值
result_2 = html.xpath('//li[contains(@class, "li")]/a/text()')#使用contains获取结果
print("按照属性直接取值的结果:",result_1)
print("使用contains取值的结果:",result_2)
输出结果:
按照属性直接取值的结果: ['first item']
使用contains取值的结果: ['first item', 'secend item']
当属性具有多个值得时候,也可以直接根据属性值获取文本。从上面的例子可以看出,使用contains获取了两个字,这是因为只要class属性值中含有li都会被提取出来,适合多值的提取。
10、多属性匹配
有时根据单个属性值无法锁定某个节点,这是就需要根据多个属性来锁定这一个节点。这时可以使用and函数来链接多个属性。如果是或的关系的话可以用or。
from lxml import etree
text = '''
<li class="li li-first" name="item"><a href="link.html">first item</a></li>
'''
html = etree.HTML(text)
result = html.xpath('//li[@class="li li-first" and @name="item"]/a/text()')#使用and函数来链接两个属性
print(result)
#输出结果:['first item']
11、按序选择
有时根据节点值会获取多个节点,而我们只需要第一个或第二个节点,这时候就需要按照序列选择节点。
from lxml import etree
text = '''
<div>
<ul>
<li class="item-0"><a href="link1.html">first item</a></li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-inactive"><a href="link3.html">third item</a></li>
<li class="item-1"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a>
</ul>
</div>
'''
html = etree.HTML(text)
result = html.xpath('//li[1]/a/text()')
print("第一个li节点的直接子节点a的文本值:",result)
result = html.xpath('//li[last()]/a/text()')
print("最后一个li节点的直接子节点a的文本值:",result)
result = html.xpath('//li[position()<3]/a/text()')
print("位置序号小于3的li节点的直接子节点a的文本值:",result)
result = html.xpath('//li[last()-2]/a/text()')
print("倒数第三个的li节点的直接子节点a的文本值:",result)
输出结果:
第一个li节点的直接子节点a的文本值: ['first item']
最后一个li节点的直接子节点a的文本值: ['fifth item']
位置序号小于3的li节点的直接子节点a的文本值: ['first item', 'second item']
倒数第三个的li节点的直接子节点a的文本值: ['third item']
- 本文章,首发于微信公众号:宏蜘蛛,原文链接:网络爬虫笔记—解析库Xpath
—End—
转载内容: