lxml与xpath
lxml是一个Python的一个解析库,用于解析HTML和XML,支持Xpaxth解析。由于lxml底层是使用C语言编写的,所以解析效率非常高。
一.安装lxml
主要是介绍windows下的安装
1.使用pip安装
pip install lxml
如果安装出错,表明缺少依赖库,如libxm12。这时候可以采用wheel方式安装
2.本地安装
这里给出个网址
python包Windows 二进制文件
在该页面下搜索lxml的whl文件即可。
下载完成之后,cmd切换运行到下载解压后的目录,执行如下命令
pip install lxml-4.6.3-cp310-cp310-win_amd64.whl
3。操作XML
lxml可以读取xml文件,也可以使用字符串形式的xml文档。如果要读取xml文件,需要使用parse函数,该函数需要传入一个XML文件名。如果要解析字符串形式的XML文档,需要使用fromstring函数,该函数的参数就是XML字符串。
from lxml import etree
# 读取xml文件
tree = etree.parse('products.xml')
print(type(tree))
# 将tree重新转化为字符串形式的xml文档,并输出
print(str(etree.tostring(tree,encoding = "utf-8"),'utf-8'))
# 获得根节点
root = tree.getroot()
print(type(root))
# 输出根节点的名称
print('root:',root.tag)
# 获得根节点的所有子节点
children = root.getchildren()
print('--------------输出产品信息--------------')
# 迭代这些子节点,并输出对应的属性和节点文本
for child in children:
print('product id = ', child.get('id'))
print('child[0].name = ',child[0].text)
print('child[1].price = ', child[1].text)
# 分析字符串形式的XML文档
root = etree.fromstring('''
<products>
<product1 name="iPhone"/>
<product2 name="iPad"/>
</products>
''')
print('------------新产品------------')
# 输出根节点的节点名
print('root =',root.tag)
children = root.getchildren()
# 迭代这些子节点,并输出节点的名称和name属性名
for child in children:
print(child.tag,'name = ',child.get('name'))
4.操作HTML文档
html本质上与xml类似,都是由若干类似<tag></tag>
的节点组成的。他们之间的不同之处在于XML除了这些节点,不能再有其他东西,而HTML的语法比较自由,不仅可以有节点,还可以有其他任何文本。
用lxml库操作html与操作XML类似,同样可以通过getroot方法获得根节点,通过get方法获得节点属性值,通过text属性获取节点内容,通过索引的方式引用了子节点。
from lxml import etree
parser = etree.HTMLParser()
print(type(parser))
tree = etree.parse('test.html', parser)
root = tree.getroot()
result = etree.tostring(root,encoding='utf-8',
pretty_print=True, method="html")
print(str(result,'utf-8'))
print(root.tag)
print('lang =',root.get('lang'))
print('charset =',root[0][0].get('charset'))
print('charset =',root[0][1].text)
二.XPath
XPath的英文名称是XML path Language ,中文是XML路径语言,他是一种在XML文档中查找信息的语言,最初是用于在XML文档中搜索节点的,但同样可以用于HTML文档的搜索,因为XML与HTML是同源的;
绝大多数HTML分析库都支持Xpath。这里介绍lxml库中使用Xpath过滤掉HTML代码中的节点
lxml装载HTML代码有如下两种方式
1.从文件装载,通过parse函数指定的HTML文件名
2.从代码装载,通过HTML函数指定HTML代码
2.1提取test.html文件中的标题,以及一段HTML代码中特定a节点的herf属性值和节点文本
from lxml import etree
parser = etree.HTMLParser()
tree = etree.parse('test.html', parser)
# 使用xpath定位title节点,返回一个节点集合
titles = tree.xpath('/html/head/title')
if len(titles) > 0:
# 输出title节点的文本
print(titles[0].text)
# 定义一段html代码
html = '''
<div>
<ul>
<li class="item1"><a href="https://geekori.com"> geekori.com</a></li>
<li class="item2"><a href="https://www.jd.com"> 京东商城</a></li>
<li class="item3"><a href="https://www.taobao.com">淘宝</a></li>
</ul>
</div>
'''
# 分析HTML代码
tree = etree.HTML(html)
# 使用xpath定位class属性值为item2的<li>节点
aTags = tree.xpath("//li[@class='item2']")
if len(aTags) > 0:
# 得到该<li>节点中<a>节点的href属性值和文本
print(aTags[0][0].get('href'),aTags[0][0].text)
# https://www.jianshu.com/p/2ae6d51522c3
注意:
1.通过XPath定位节点返回的是节点集合,即使只有一个节点,返回的也是一个节点集合
2.使用XPath分析的HTML文档并不一定是标准的,可以么有像<html>、<head>、<body>
这些的、节点。任何一段符合HTML语法标准的代码都可以使用XPath进行定位
2.2选取所有节点
‘//’:以两个斜杠(//)开头的XPath规则会选取所有符合要求的节点。表示任意节点,所以//*表示的是选取所有节点
这里展示一个取出所有的<a>
节点,并输出节点的名称
from lxml import etree
parser = etree.HTMLParser()
html = etree.parse('demo.html', parser)
# 选取demo.html文件中所有的节点
nodes = html.xpath('//*')
print('共',len(nodes),'个节点')
# 输出所有节点的节点名
print(nodes)
for i in range(0,len(nodes)):
print(nodes[i].tag,end=' ')
# 按层次输出节点,indent是缩进
def printNodeTree(node, indent):
print(indent + node.tag)
indent += " "
children = node.getchildren()
if len(children) > 0:
for i in range(0,len(children)):
# 递归调用
printNodeTree(children[i],indent)
print()
# 按层次输出节点的节点,nodes[0]是根节点(html节点)
printNodeTree(nodes[0],"")
nodes = html.xpath('//a')
print()
print('共',len(nodes),'个<a>节点')
print(nodes)
# 输出所有<a>节点的文本
for i in range(0,len(nodes)):
print(nodes[i].text,end=' ')
2.3选取子节点
“/”后是直接子节点。“//”是选取所有该子节点(在该父节点之下的所有)
from lxml import etree
parser = etree.HTMLParser()
html = etree.parse('demo.html', parser)
nodes = html.xpath('//li/a')
print('共',len(nodes),'个节点')
print(nodes)
for i in range(0,len(nodes)):
print(nodes[i].text,end=' ')
print()
nodes = html.xpath('//ul//a')
print('共',len(nodes),'个节点')
print(nodes)
for i in range(0,len(nodes)):
print(nodes[i].text,end=' ')
print()
nodes = html.xpath('//ul/a')
print('共',len(nodes),'个节点')
print(nodes)
2.4选取父节点
如果知道子节点,想得到父节点,可以使用..
;parent::*
与..
等效
from lxml import etree
# 如果知道子节点,想得到父节点,可以使用"..";“parent::*”与"..“等效
parser = etree.HTMLParser()
html = etree.parse('demo.html', parser)
# 选取属性值为href="https://www.jd.com"<a>标签的父节点,并输出父节点的属性值
result = html.xpath('//a[@href="https://www.jd.com"]/../@class')
print('class属性 =',result)
#
result = html.xpath('//a[@href="https://www.jd.com"]/parent::*/@class')
print('class属性 =',result)
2.5属性匹配与获取
引用属性值需要在属性名前面加@,如@class表示class属性,XPath的过滤条件需要放到一对中括号([..])中,就是过滤(提取)出该属性值为什么的标签;如果不将属性值放在[..]中,就是获取属性值,如'//a/@herf'表示获取所有<a>
节点的href的属性值
from lxml import etree
parser = etree.HTMLParser()
html = etree.parse('demo.html', parser)
nodes = html.xpath('//a[@href="https://geekori.com"]')
print('共',len(nodes),'个节点')
for i in range(0,len(nodes)):
print(nodes[i].text)
nodes = html.xpath('//a[contains(@href,"www")]')
print('共',len(nodes),'个节点')
for i in range(0,len(nodes)):
print(nodes[i].text)
urls = html.xpath('//a[contains(@href,"www")]/@href')
for i in range(0,len(urls)):
print(urls[i])
2.6多属性匹配
使用and,or关键字和contains函数来匹配多属性,注意XPath对大小写敏感,这里全部小写
from lxml import etree
parser = etree.HTMLParser()
html = etree.parse('demo.html', parser)
aList = html.xpath('//a[@href="https://www.jd.com" or @href="https://geekori.com"]')
for a in aList:
print(a.text,a.get('href'))
# <li class="item4" value="1234"><a href="https://www.microsoft.com">微软</a></li>
print('----------------')
# 选择herf属性值包含www,并且父节点中value属性值等于1234的<a>节点
aList = html.xpath('//a[contains(@href,"www") and ../@value="1234"]')
for a in aList:
print(a.text,a.get('href'))
除了and和or外,Xpath中还有很多运算符
2.7按序选择节点
在XPath使用索引的方式与Python中引用列表中元素的方式相似,都是在中括号中使用索引。
如'//li[1]'表示选择所有<li>
节点中的第一个<li>
节点。XPath中的索引是从1开始的,而Python列表的索引是从0开始的。
XPath中的索引还可以使用XPath的内置函数,如position()表示当前位置,list()表示最后的位置。例如'//li[position() = 3]'表示选择所有'
<li>
节点,与'//li[3]'的效果相同
from lxml import etree
parser = etree.HTMLParser()
text = '''
<div>
<a href="https://geekori.com"> geekori.com</a>
<a href="https://www.jd.com"> 京东商城</a>
<a href="https://www.taobao.com">淘宝</a>
<a href="https://www.microsoft.com">微软</a>
<a href="https://www.google.com">谷歌</a>
</div>
'''
html = etree.HTML(text)
a1 = html.xpath('//a[1]/text()')
a1 = html.xpath('//a[1]/text()')
a2 = html.xpath('//a[2]/text()')
print(a1,a2)
lasta = html.xpath('//a[last()]/text()')
print(lasta)
aList = html.xpath('//a[position() > 3]/text()')
print(aList)
aList = html.xpath('//a[position() = 2 or position() = last() - 1]/text()')
print(aList)
2.8节点轴选择
XPath提供了许多节点轴选择方法,包括获取祖先节点、兄弟节点、子孙节点等。
ancestor轴
attribute轴
child轴
descendant轴
following轴
following-sibling轴
from lxml import etree
'''
ancestor轴
attribute轴
child轴
descendant轴
following轴
following-sibling轴
'''
parser = etree.HTMLParser()
text = '''
<html>
<head>
<meta charset="UTF-8">
<title>XPath演示</title>
</head>
<body class="item">
<div>
<ul class="item" >
<li class="item1"><a href="https://geekori.com"> geekori.com</a></li>
<li class="item2"><a href="https://www.jd.com">京东商城</a>
<value url="https://geekori.com"/>
<value url="https://www.google.com"/>
</li>
<li class="item3"><a href="https://www.taobao.com">淘宝</a>
<a href="https://www.tmall.com/">天猫</a></li>
<li class="item4" value="1234"><a href="https://www.microsoft.com">微软</a></li>
<li class="item5"><a href="https://www.google.com">谷歌</a></li>
</ul>
</div>
</body>
</html>
'''
html = etree.HTML(text)
# ancestor轴,用于获取所有的祖先节点,后面必须跟两个冒号(::),然后是节点选择器
# 这里的*表示匹配所有的节点
result = html.xpath('//li[1]/ancestor::*')
# 输出结果:html body div ul
for value in result:
print(value.tag, end= ' ')
print()
# attribute轴匹配所有class属性值为item的祖先节点
result = html.xpath('//li[1]/ancestor::*[@class="item"]')
# 输出结果:body ul
for value in result:
print(value.tag, end= ' ')
print()
# 使用attribute轴获取第4个<li>节点的所有属性值
result = html.xpath('//li[4]/attribute::*')
# 输出结果:['item4','1234']
print(result)
# child轴获取第三个<li>节点的所有子节点
result = html.xpath('//li[3]/child::*')
for value in result:
print(value.get('href'), value.text,end= ' ')
print()
# descendant轴获取第二个<li>节点的所有名为value的子孙节点
result = html.xpath('//li[2]/descendant::value')
for value in result:
print(value.get('url'),end= ' ')
print()
# following轴获取第一个<li>节点后的所有子节点(包括子孙节点)
result = html.xpath('//li[1]/following::*')
for value in result:
print(value.tag,end= ' ')
print()
# following轴获取第一个<li>节点后位置大于4的所有子节点
result = html.xpath('//li[1]/following::*[position() > 4]')
for value in result:
print(value.tag,end= ' ')
print()
# following-sibling轴获取第一个<li>节点后所有同级的节点
result = html.xpath('//li[1]/following-sibling::*')
for value in result:
print(value.tag,end= ' ')
print()
2.9在Chrome中自动获得XPath代码
在页面右键菜单单击"检查"命令显示开发者工具,然后定位到导航条的某一条连接(如"秒杀"),在Elements选项卡中定位到"秒杀",选择对应的HTML代码,然后右击,在弹出的菜单中单击Copy->Copy XPath命令。
一般这个获得的Xpath代码需要做些修改,比如,获取多个最后的标签,就要把'/'替换成'//'
2.10使用Chrome验证XPath
$x是用来运行XPath的函数
$x('//*[@id="1"]/h3') 注意:这里如果XPath代码中包含的是双引号,参数要用单引号括起来,如果XPath代码中包含的是单引号,参数要用双引号括起来。