lxml库使用
我们可以利用python中的lxml库来使用Xpath对HTML文档进行搜索。
选取节点:
nodename 选取此节点的所有子节点。
/ 从根节点选取。
// 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。
. 选取当前节点。
… 选取当前节点的父节点。
@ 选取属性。
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>
'''
# 利用lxml库的etree模块调用HTML类将html文本进行初始化,构造一个Xpath解析对象。
html=etree.HTML(text)
result=etree.tostring(html) # 调用tostring()的方法可以输出修正后的HTML代码
print(type(result))
print(result.decode('utf-8')) # 结果是bytes类型,利用decode()将其转化为str类型
#以下输出结果中,残缺的html文本被补全,并且自动添加了body、html节点
<class 'bytes'>
<html><body><div>
<ul>
<li class="item-0"><a href="link1.html">first item</a><a/></li>
<li class="item-1"><a href="link2.html">second item</a><a/></li>
<li class="item-inactive"><a href="link3.html">third item</a><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>
# 也可以直接读取文件进行解析
from lxml import etree
html=etree.parse('./test.html',etree.HTMLParser())
result=etree.tostring(html)
print(result.decode('utf-8'))
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body><p>from lxml import etree
text = '''
</p><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>
'''
html = etree.HTML(text)
result = etree.tostring(html)
print(result)</body></html>
所有节点
一般用 // 开头的XPath规则来选择所有符合要求的节点。
from lxml import etree
'''这里使用*来匹配所有节点,整个html文本的所有节点都会被获取。
返回一个列表,每个元素是Element类型,其后跟了节点的名称。'''
html=etree.parse('./test.html',etree.HTMLParser())
result=html.xpath('//*')
print(result)
[<Element html at 0x280b7673808>, <Element body at 0x280b764be48>, <Element p at 0x280b764bb88>, <Element div at 0x280b75d54c8>, <Element ul at 0x280b76736c8>, <Element li at 0x280b76738c8>, <Element a at 0x280b76735c8>, <Element li at 0x280b7673308>, <Element a at 0x280b7673848>, <Element li at 0x280b7673908>, <Element a at 0x280b7673508>, <Element li at 0x280b7673448>, <Element a at 0x280b7673408>, <Element li at 0x280b7673a48>, <Element a at 0x280b7673cc8>]
from lxml import etree
html=etree.parse('./test.html',etree.HTMLParser())
# 选取所有li节点,可以使用//,直接加上节点名称即可,调用时直接使用xpath()方法
result=html.xpath('//li')
print(result)
[<Element li at 0x280b7626d08>, <Element li at 0x280b7673a08>, <Element li at 0x280b7673f48>, <Element li at 0x280b7673f88>, <Element li at 0x280b7673fc8>]
子节点
通过 / 或 // 来查找元素的节点或子孙节点
from lxml import etree
html=etree.parse('./test.html',etree.HTMLParser())
result=html.xpath('//li/a') # 查找li节点中的所有直接a子节点
print(result)
[<Element a at 0x280b75d5408>, <Element a at 0x280b7673f08>, <Element a at 0x280b7673dc8>, <Element a at 0x280b76735c8>, <Element a at 0x280b7673308>]
from lxml import etree
html=etree.parse('./test.html',etree.HTMLParser())
result=html.xpath('//ul//a') # 查找ul节点下的所有a节点。
print(result)
# 如果用//ul/a就不会返回结果,因为a不是ul的直接节点
[<Element a at 0x280b764b448>, <Element a at 0x280b7673e48>, <Element a at 0x280b7673d08>, <Element a at 0x280b7673e08>, <Element a at 0x280b7673408>]
父节点
当我们知道了子节点后,我们可以用 … 来查找父节点
from lxml import etree
html=etree.parse('./test.html',etree.HTMLParser())
# 查找顺序:<a href='link4.html'>-->(直接父节点<li>)-->直接属性class的内容
result=html.xpath('//a[@href="link4.html"]/../@class')
print(result)
['item-1']
from lxml import etree
html=etree.parse('./test.html',etree.HTMLParser())
result=html.xpath('//a[@href="link4.html"]/parent::*/@class') #也可以用parent::来获取
print(result)
['item-1']
属性匹配
我们可以通过@符号进行属性过滤
from lxml import etree
html=etree.parse('./test.html',etree.HTMLParser())
result=html.xpath('//li[@class="item-0"]') # 选取属性class为'item-0'的li节点
print(result)
[<Element li at 0x280b7671bc8>, <Element li at 0x280b7673f08>]
文本获取
利用text()方法获取节点中文本
from lxml import etree
# 获取所有<li class='item-0'>节点的文本,法一:
html=etree.parse('./test.html',etree.HTMLParser())
# 因为文本内容实际在a节点内,所以先要进入a节点,再用text()方法进行提取文本
result=html.xpath('//li[@class="item-0"]/a/text()')
print(result)
['first item', 'fifth item']
from lxml import etree
# 获取所有<li class='item-0'>节点的文本,法二:
html=etree.parse('./test.html',etree.HTMLParser())
#使用//获取其节点下所有文本内容,其中\r\n是补全后li节点内的内容
result=html.xpath('//li[@class="item-0"]//text()')
print(result)
['first item', 'fifth item', '\r\n ']
属性获取
同样用@可以用来获取属性
from lxml import etree
html=etree.parse('./test.html',etree.HTMLParser())
result=html.xpath('//li/a/@href') # 匹配所有li节点下a节点的href属性
print(result)
['link1.html', 'link2.html', 'link3.html', 'link4.html', 'link5.html']
属性多值匹配
当某些节点的属性包含多个值时,就无法单纯的用xpath()方法匹配,此时需要用contains()方法,第一个参数传入属性名称,第二个参数传入属性值。
from lxml import etree
text='''
<li class="li li-first"><a href="link.html">first item</a></li>
'''
html=etree.HTML(text)
result=html.xpath('//li[contains(@class,"li")]/a/text()')
print(result)
['first item']
多属性匹配
当多个属性确定一个节点时,我们需要同时匹配多个属性,此时可以用运算符 and 来连接
from lxml import etree
text='''
<li class="li li-first" name="item"><a href="link.html">first item</a></i>
'''
html=etree.HTML(text)
result=html.xpath('//li[contains(@class,li) and @name="item"]/a/text()')
print(result)
['first item']
按序选择
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"><span class="bold">third item</span></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>
'''
html=etree.HTML(text)
result=html.xpath('//li[1]/a/text()') # 查找第一个li节点内的文本
print(result)
result=html.xpath('//li[last()]/a/text()') # 查找最后一个节点内的文本
print(result)
result=html.xpath('//li[position()<3]/a/text()') # 查找位置小于3的文本
print(result)
result=html.xpath('//li[last()-2]/a/text()') # 查找倒数第三位的节点
print(result)
['first item']
['fifth item']
['first item', 'second item']
[]
节点轴选择
ancestor:匹配当前节点的所有祖先节点
attribute:匹配当前节点的所有属性值
child:匹配当前节点的所有子节点
decendant:匹配当前节点的所有子孙节点
following:匹配当前节点之后的所有节点
following-sibling:匹配当前节点之后的所有同级节点
from lxml import etree
text='''
<div>
<ul>
<li class="item-0"><a href="link1.html"><span>first item</span></a></li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-inactive"><a href="link3.html"><span class="bold">third item</span></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>
'''
html=etree.HTML(text)
result=html.xpath('//li[1]/ancestor::*') # 调用ancestor轴,匹配第一个li节点的所有祖先节点
print(result)
result=html.xpath('//li[1]/ancestor::div') # 调用ancestor轴,匹配第一个li节点的祖先节点中的div节点
print(result)
result=html.xpath('//li[1]/attribute::*') # 调用attribute轴,匹配第一个li节点的所有属性值
print(result)
result=html.xpath('//li[1]/child::a[@href="link1.html"]') # 调用child轴,匹配第一个li节点的中特定属性的a节点
print(result)
result=html.xpath('//li[1]/descendant::span') # 调用descendant轴,匹配第一个节点的所有子孙节点中的span节点
print(result)
result=html.xpath('//li[1]/following::*[2]') # 调用following轴,匹配第一个节点的后续节点的第二个节点
print(result)
result=html.xpath('//li[1]/following-sibling::*') # 调用following-sibling轴,获取第一个li节点的所有同级节点
print(result)
[<Element html at 0x280b765ee08>, <Element body at 0x280b7673408>, <Element div at 0x280b767c708>, <Element ul at 0x280b767cdc8>]
[<Element div at 0x280b767c708>]
['item-0']
[<Element a at 0x280b7687cc8>]
[<Element span at 0x280b7680208>]
[<Element a at 0x280b767c708>]
[<Element li at 0x280b7673408>, <Element li at 0x280b7680208>, <Element li at 0x280b7680248>, <Element li at 0x280b7680348>]
参考:崔庆才《python3网络爬虫开发实战》