zoukankan      html  css  js  c++  java
  • python使用xpath(超详细)

    使用时先安装 lxml 包

    开始使用

    和beautifulsoup类似,首先我们需要得到一个文档树

    • 把文本转换成一个文档树对象
    from lxml import etree
    if __name__ == '__main__':
        doc='''
            <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(doc)
        result = etree.tostring(html)
        print(str(result,'utf-8'))
    
    • 把文件转换成一个文档树对象
    from lxml import etree
    
    # 读取外部文件 index.html
    html = etree.parse('./index.html')
    result = etree.tostring(html, pretty_print=True)    #pretty_print=True 会格式化输出
    print(result)
    

    均会打印出文档内容

    节点、元素、属性、内容

    xpath 的思想是通过 路径表达 去寻找节点。节点包括元素属性,和内容

    • 元素举例
    html ---> <html> ...</html>
    div ---> <div> ...</div>
    a  ---> <a> ...</a>
    

    这里我们可以看到,这里的元素和html中的标签一个意思。单独的元素是无法表达一个路径的,所以单独的元素不能独立使用

    路径表达式

        /   根节点,节点分隔符,
        //  任意位置
        .   当前节点
        ..  父级节点
        @   属性
    

    通配符

        *   任意元素
        @*  任意属性
        node()  任意子节点(元素,属性,内容)
    

    谓语

    使用中括号来限定元素,称为谓语

        //a[n] n为大于零的整数,代表子元素排在第n个位置的<a>元素
        //a[last()]   last()  代表子元素排在最后个位置的<a>元素
        //a[last()-]  和上面同理,代表倒数第二个
        //a[position()<3] 位置序号小于3,也就是前两个,这里我们可以看出xpath中的序列是从1开始
        //a[@href]    拥有href的<a>元素
        //a[@href='www.baidu.com']    href属性值为'www.baidu.com'的<a>元素
        //book[@price>2]   price值大于2的<book>元素
    

    多个路径

    | 连接两个表达式,可以进行 匹配

    //book/title | //book/price  
    

    函数

    xpath内置很多函数。更多函数查看https://www.w3school.com.cn/xpath/xpath_functions.asp

    • contains(string1,string2)
    • starts-with(string1,string2)
    • ends-with(string1,string2) #不支持
    • upper-case(string) #不支持
    • text()
    • last()
    • position()
    • node()

    可以看到last()也是个函数,在前面我们在谓语中已经提到过了

    案例

    定位元素

    匹配多个元素,返回列表

    from lxml import etree
    
    if __name__ == '__main__':
        doc='''
            <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(doc)
        print(html.xpath("//li"))
        print(html.xpath("//p")) 
    

    【结果为】

    [<Element li at 0x2b41b749848>, <Element li at 0x2b41b749808>, <Element li at 0x2b41b749908>, <Element li at 0x2b41b749948>, <Element li at 0x2b41b749988>]
    []  #没找到p元素
    
    html = etree.HTML(doc)
    print(etree.tostring(html.xpath("//li[@class='item-inactive']")[0]))
    print(html.xpath("//li[@class='item-inactive']")[0].text)
    print(html.xpath("//li[@class='item-inactive']/a")[0].text)
    print(html.xpath("//li[@class='item-inactive']/a/text()"))
    print(html.xpath("//li[@class='item-inactive']/.."))
    print(html.xpath("//li[@class='item-inactive']/../li[@class='item-0']"))
    

    【结果为】

    b'<li class="item-inactive"><a href="link3.html">third item</a></li>
                     '
    None    #因为第三个li下面没有直接text,None
    third item  # 
    ['third item']
    [<Element ul at 0x19cd8c4c848>]
    [<Element li at 0x15ea3c5b848>, <Element li at 0x15ea3c5b6c8>]
    

    使用函数

    contains

    有的时候,class作为选择条件的时候不合适@class='....' 这个是完全匹配,当网页样式发生变化时,class或许会增加或减少像activeclass。用contains就能很方便

    from lxml import etree
    if __name__ == '__main__':
        doc='''
            <div>
                <ul>
                     <p class="item-0 active"><a href="link1.html">first item</a></p>
                     <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(doc)
        print(html.xpath("//*[contains(@class,'item')]"))
    

    【结果为】

    [<Element p at 0x23f4a9d12c8>, <Element li at 0x23f4a9d13c8>, <Element li at 0x23f4a9d1408>, <Element li at 0x23f4a9d1448>, <Element li at 0x23f4a9d1488>]
    

    starts-with

    
    from lxml import etree
    if __name__ == '__main__':
        doc='''
            <div>
                <ul class='ul items'>
                     <p class="item-0 active"><a href="link1.html">first item</a></p>
                     <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(doc)
        print(html.xpath("//*[contains(@class,'item')]"))
        print(html.xpath("//*[starts-with(@class,'ul')]"))
    

    【结果为】

    [<Element ul at 0x23384e51148>, <Element p at 0x23384e51248>, <Element li at 0x23384e51288>, <Element li at 0x23384e512c8>, <Element li at 0x23384e51308>, <Element li at 0x23384e51388>]
    [<Element ul at 0x23384e51148>]
    

    ends-with

    print(html.xpath("//*[ends-with(@class,'ul')]"))
    

    【结果为】

    Traceback (most recent call last):
      File "F:/OneDrive/pprojects/shoes-show-spider/test/xp5_test.py", line 18, in <module>
        print(html.xpath("//*[ends-with(@class,'ul')]"))
      File "srclxmletree.pyx", line 1582, in lxml.etree._Element.xpath
      File "srclxmlxpath.pxi", line 305, in lxml.etree.XPathElementEvaluator.__call__
      File "srclxmlxpath.pxi", line 225, in lxml.etree._XPathEvaluatorBase._handle_result
    lxml.etree.XPathEvalError: Unregistered function
    

    看来python的lxml并不支持有的xpath函数列表

    upper-case

    和ends-with函数一样,也不支持。同样报错lxml.etree.XPathEvalError: Unregistered function

    print(html.xpath("//a[contains(upper-case(@class),'ITEM-INACTIVE')]"))
    

    text、last

    #最后一个li被限定了
    print(html.xpath("//li[last()]/a/text()"))
    
    #会得到所有的`<a>`元素的内容,因为每个<a>标签都是各自父元素的最后一个元素。
    #本来每个li就只有一个<a>子元素,所以都是最后一个
    print(html.xpath("//li/a[last()]/text()"))  
    
    print(html.xpath("//li/a[contains(text(),'third')]"))
    

    【结果为】

    ['fifth item']
    ['second item', 'third item', 'fourth item', 'fifth item']
    [<Element a at 0x26ab7bd1308>]
    

    position

    print(html.xpath("//li[position()=2]/a/text()"))
    #结果为['third item']
    

    上面这个例子我们之前以及讲解过了
    *这里有个疑问,就是position()函数能不能像text()那样用呢

    print(html.xpath("//li[last()]/a/position()"))
    #结果  lxml.etree.XPathEvalError: Unregistered function
    

    这里我们得到一个结论,函数不是随意放在哪里都能得到自己想要的结果

    node

    返回所有子节点,不管这个子节点是什么类型(熟悉,元素,内容)

    print(html.xpath("//ul/li[@class='item-inactive']/node()"))
    print(html.xpath("//ul/node()"))
    

    【结果为】

    [<Element a at 0x239a0d197c8>]
    ['
                     ', <Element li at 0x239a0d19788>, '
                     ', <Element li at 0x239a0d19888>, '
                     ', <Element li at 0x239a0d19908>, '
                     ', <Element li at 0x239a0d19948>, '
                     ', <Element li at 0x239a0d198c8>, ' 闭合标签
                 ']
    

    获取内容

    刚刚已经提到过,可以使用.texttext()的方式来获取元素的内容

    
    
    from lxml import etree
    if __name__ == '__main__':
        doc='''
            <div>
                <ul class='ul items'>
                     <li class="item-0 active"><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.XML(doc)
        print(html.xpath("//a/text()"))
        print(html.xpath("//a")[0].text)
        print(html.xpath("//ul")[0].text)
        print(len(html.xpath("//ul")[0].text))
        print(html.xpath("//ul/text()"))
    

    【结果为】

    ['first item', 'second item', 'third item', 'fourth item', 'fifth item']
    first item
    
                     
    18
    ['
                     ', '
                     ', '
                     ', '
                     ', '
                     ', ' 闭合标签
                 ']
    

    看到这里,我们观察到text().text的区别。自己总结吧。不太好表达,就不表达了

    获取属性

    print(html.xpath("//a/@href"))
    print(html.xpath("//li/@class"))
    

    【结果为】

    ['link1.html', 'link2.html', 'link3.html', 'link4.html', 'link5.html']
    ['item-0 active', 'item-1', 'item-inactive', 'item-1', 'item-0']
    

    自定义函数

    我们从使用函数的过程中得到结论,就是有的函数不支持,有的支持,那问题来了,到底哪些函数支持呢。我们在lxml官网找到了答案。https://lxml.de/xpathxslt.html。lxml 支持XPath 1.0 ,想使用其他扩展,使用libxml2,和libxslt的标准兼容的方式。XPath 1.0官方文档 以及其他版本的XPath文档 https://www.w3.org/TR/xpath/

    lxml supports XPath 1.0, XSLT 1.0 and the EXSLT extensions through libxml2 and libxslt in a standards compliant way.  
    

    除此之外,lxml还提供了自定义函数的方式来扩展xpath的支持度 https://lxml.de/extensions.html

    
    from lxml import etree
    
    #定义函数
    def ends_with(context,s1,s2):
        return s1[0].endswith(s2)
    if __name__ == '__main__':
        doc='''
            <div>
                <ul class='ul items'>
                     <li class="item-0 active"><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.XML(doc)
        ns = etree.FunctionNamespace(None)  
        ns['ends-with'] = ends_with #将ends_with方法注册到方法命名空间中
        print(html.xpath("//li[ends-with(@class,'active')]"))
        print(html.xpath("//li[ends-with(@class,'active')]/a/text()"))
    

    【结果为】

    [<Element li at 0x2816ed30548>, <Element li at 0x2816ed30508>]
    ['first item', 'third item']
    
    • 形参s1会传入xpath中的第一个参数@class,但这里注意@class是个列表
    • 形参s2会传入xpath中的第二个参数'active''active'是个字符串

    官网例子https://lxml.de/extensions.html

    def hello(context, a):
        return "Hello %s" % a
    
    from lxml import etree
    ns = etree.FunctionNamespace(None)
    ns['hello'] = hello
    root = etree.XML('<a><b>Haegar</b></a>')
    print(root.xpath("hello('Dr. Falken')"))
    # 结果为 Hello Dr. Falken
    

    xpath1.0支持的函数列表

    • number last() 谓语中返回在兄弟元素中排在最末尾的
    • number position() 谓语中,返回该元素在兄弟中的排名
    • number count(node-set) 返回子节点的个数 ,node-set代表子节点的表达式
    //*[count(@*)=1]    属性只有一个的元素(因为元素的属性本身就是子节点)
    //*[count(*)=1]    任意一种属性如果为1,就满足
    //*[count(a)=1] 子元素中有一个`<a>`的元素
    
    • node-set id("foo") 返回唯一id为foo的元素 。尝试了多次,未尝试出来,这里的唯一id是基于xml的,有下面这样一句话。所以确实没测试出来
    If a document does not have a DTD, then no element in the document will have a unique ID.
    
    • string local-name(node-set?)

    • string name(node-set?)

    • string namespace-uri(node-set?)

    • string concat(string, string, string*)

    • boolean starts-with(string, string)

    • boolean contains(string, string)

    • string substring-before(string, string)

    • string substring-after(string, string)

    • string substring(string, number, number?)

    • number string-length(string?)

    • string normalize-space(string?)

    • number sum(node-set)

    • number floor(number)

    • number ceiling(number)

    • number round(number)

    xpath使用工具

    chome生成xpath表达式

    经常使用chome的小伙伴的都应该知道这个功能,在 审查 状态下(快捷键ctrl+shift+i,F12),定位到元素(快捷键ctrl+shift+c) ,在Elements选项卡中,右键元素 Copy->Copy xpath,就能得到该元素的xpath了

    XPath Helper插件

    为chome装上XPath Helper就可以很轻松的检验自己的xpath是否正确了。安装插件需要kxsw(使用lanternFQ,或者Astar VPN),安装好插件后,在chrome右上角点插件的图标,调出插件的黑色界面,编辑好xpath表达式,表达式选中的元素被标记为黄色

  • 相关阅读:
    js去除字符串空格(空白符)
    jq以固定开头的class属性的名称
    day--38 mysql表的完整性约束总结
    day--39-MySQL的多表查询
    day--40 mysql-视图,触发器,存储过程,函数总结
    day--41 mysql索引原理与慢查询优化
    day--42 前端基础小结
    day--43 HTML标签和CSS基本小结
    day46----JavaScript的函数及对象小结
    JavaScript正则表达式
  • 原文地址:https://www.cnblogs.com/mxjhaima/p/13775844.html
Copyright © 2011-2022 走看看