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]'表示选择所有'

  • '节点中的第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代码中包含的是单引号,参数要用双引号括起来。

  • posted @ 2021-09-05 15:53  索匣  阅读(479)  评论(0编辑  收藏  举报