美文网首页程序员
python使用xpath(超详细)

python使用xpath(超详细)

作者: 蒸熟的土豆 | 来源:发表于2020-10-07 14:41 被阅读0次

    使用时先安装 lxml 包

    开始使用#

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

    把文本转换成一个文档树对象

    from lxml import etreeif __name__ == '__main__':doc='''

      first itemsecond itemthird itemfourth itemfifth item# 注意,此处缺少一个闭合标签
    '''html = etree.HTML(doc)result = etree.tostring(html)print(str(result,'utf-8'))

    把文件转换成一个文档树对象

    fromlxmlimportetree# 读取外部文件 index.htmlhtml = etree.parse('./index.html')result = etree.tostring(html, pretty_print=True)#pretty_print=True 会格式化输出print(result)

    均会打印出文档内容

    节点、元素、属性、内容#

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

    元素举例

    html --->...div --->

    ...a  --->...

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

    路径表达式#

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

    通配符#

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

    谓语#

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

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

    多个路径#

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

    //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()也是个函数,在前面我们在谓语中已经提到过了

    案例#

    定位元素#

    匹配多个元素,返回列表

    fromlxmlimportetreeif__name__ =='__main__':doc='''

    '''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'third item\n                'None    #因为第三个li下面没有直接text,Nonethird item  #['third item'][<Element ul at 0x19cd8c4c848>][<Element li at 0x15ea3c5b848>, <Element li at 0x15ea3c5b6c8>]

    使用函数#

    contains#

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

    from lxml import etreeif __name__ == '__main__':doc='''

      first itemsecond itemthird itemfourth itemfifth item# 注意,此处缺少一个闭合标签
    '''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 etreeif __name__ == '__main__':doc='''

    first itemsecond itemthird itemfourth itemfifth item# 注意,此处缺少一个闭合标签'''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",line18,inprint(html.xpath("//*[ends-with(@class,'ul')]"))File"src\lxml\etree.pyx",line1582,inlxml.etree._Element.xpathFile"src\lxml\xpath.pxi",line305,inlxml.etree.XPathElementEvaluator.__call__File"src\lxml\xpath.pxi",line225,inlxml.etree._XPathEvaluatorBase._handle_resultlxml.etree.XPathEvalError: Unregisteredfunction

    看来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: Unregisteredfunction

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

    node#

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

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

    【结果为】

    []['\n                ', , '\n                ', , '\n                ', , '\n                ', , '\n                ', , ' 闭合标签\n            ']

    获取内容#

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

    from lxml import etreeif __name__ == '__main__':doc='''

    first itemsecond itemthird itemfourth itemfifth item# 注意,此处缺少一个闭合标签'''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 item18['\n                ','\n                ','\n                ','\n                ','\n                ',' 闭合标签\n            ']

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

    获取属性#

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

    【结果为】

    ['link1.html', 'link2.html', 'link3.html', 'link4.html', 'link5.html']['item-0active', '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 XPath1.0, XSLT1.0andthe EXSLT extensions through libxml2andlibxsltina 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='''

    first itemsecond itemthird itemfourth itemfifth item# 注意,此处缺少一个闭合标签'''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

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

    相关文章

      网友评论

        本文标题:python使用xpath(超详细)

        本文链接:https://www.haomeiwen.com/subject/uqmfpktx.html