zoukankan      html  css  js  c++  java
  • Class 14

    对于网页的节点来说,它可以定义 id 、class 或其他属性。 而且节点之间还有层次关系,在网页 中可以通过XPath 或 css 选择器来定位一个或多个节点。所以在页面解析时,利用 XPath 或 css 选择器来提取某个节点,然后再调用相应方法获取它的正文内容或者属性,也可以提取我们想要的任意信息。

    XPath的使用

    • XPath, 全称 XML Path Language,即 XML 路径语言,它是一门在 XML 文档中查找信息的语言。 它最初是用来搜寻 XML 文档的,同样适用于 HTML 文档的搜索。
    1. 更多文档参考:https://www.w3.org/TR/xpath
    2. XPath常用规则
      • nodename:选取此节点的所有子节点
      • / :从当前节点选取直接子节点
      • //:从当前节点选取子孙节点
      • . :选取当前节点
      • ..:选取当前节点的父节点
      • @:选取属性
        • XPath的常用匹配规则,示例:
          //title[@lang='eng']

          此XPath代表选择所有名称为title,同时属性lang的值为eng的节点

    3. XPath对网页解析代码:
      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>
      '''
      html = etree.HTML(text)
      result =etree.tostring(html)
      print(result.decode('utf-8'))

      首先导人 lxml 库的 etree 模块,然后声明了一段 HTML 文本,调用 HTML 类进行初始化,构造了一个 XPath 解析对象。 注意: HTML 文本中的最后一个 li 节点是没有闭合的,但是 etree模块可以自动修正HTML 文本。
      调用 tostring()方法即可输出修正后的 HTML代码,但是结果是 bytes 类型。这里利用decode()方法将其转成 str 类型

      <html><body><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>
      </body></html>

      经过处理之后, li 节点标签被补全,并且还向动添加了 body、 html 节点。      

      另外,也可以直接读取文本文件进行解析,示例如下:

      from lxml import etree
      html = etree.parse('./test.html',etree.HTMLParser())
      result = etree.tostring(html)
      print(result.decode('utf-8'))

      其中 test.html 的内容就是上面例子中的 HTML代码,输出结果会多一个DOCTYPE声明,对解析无任何影响。

    4. 所有节点
      • 我们一般会用 // 开头的 XPath 规则来选取所有符合要求的节点。 以前面的 HTML 文本为例,如果要选取所有节点:

        from lxml import etree
        html = etree.parse('./test.html',etree.HTMLParser())
        result = html.xpath('//*')
        print(result)
        输出:
        [<Element html at 0x245a651e508>, <Element body at 0x245a651e608>, <Element p at 0x245a651e648>, <Element div at 0x245a651e688>, <Element ul at 0x245a651e6c8>, <Element li at 0x245a651e748>, <Element a at 0x245a651e788>, <Element li at 0x245a651e7c8>, <Element a at 0x245a651e808>, <Element li at 0x245a651e708>, <Element a at 0x245a651e848>, <Element li at 0x245a651e888>, <Element a at 0x245a651e8c8>, <Element li at 0x245a651e908>, <Element a at 0x245a651e948>]

        这里使用*代表匹配所有节点,整个 HTML 文本中的所有节点都会被获取。返回形式是一个列表,每个元素是 Element 类型,后面跟了节点名称,如 html、 body、 div 、 ul、 li 、a 等,所有节点都包含在列表中。

      • 此处匹配也可以指定节点名称。 如果想获取所有 li 节点,例:

        from lxml import etree
        html = etree.parse('./test.html', etree.HTMLParser())
        result = html.xpath('//li')
        print(result)
        print(result[0])
        输出:
        [<Element li at 0x1e80856c608>, <Element li at 0x1e80856c648>, <Element li at 0x1e80856c688>, <Element li at 0x1e80856c6c8>, <Element li at 0x1e80856c708>]
        <Element li at 0x1e80856c608>

        提取结果是一个列表形式,其中每个元素都是一个 Element 对象。 如果要取出其中 一个对象,可以直接用中括号加索引,如 [0].

    5. 子节点
      • 通过/或//即可查找元素的子节点或子孙节点。如果现在想选择 li 节点的所有直接 a 子节点,例:

        from lxml import etree
        html = etree.parse('./test.html', etree.HTMLParser())
        result = html.xpath('//li/a')
        print(result)
        输出:
        [<Element a at 0x2af0834e608>, <Element a at 0x2af0834e648>, <Element a at 0x2af0834e688>, <Element a at 0x2af0834e6c8>, <Element a at 0x2af0834e708>]

        通过追加/a 即选择了所有 li 节点的所有直接 a 子节点。因为//li 用于选中所有 li 节点,/a 用于选中 li 节点的所有直接子节点 a, 二者组合在一起即获取所有 li 节点的所有直接 a 子节点

        此处的/用于选取直接子节点 ,如果要获取所有子孙节点,就可以使用//。 例如,要获取 ul 节点 下的所有子孙 a 节点,可以这样实现:

        from lxml import etree
        html = etree.parse('./test.html', etree.HTMLParser())
        result = html.xpath('//ul//a')
        print(result)

        如果这里用//ul闭,就无法获取任何结果了。 因为/用于获取直接子节点,而在 ul 节点下没 有直接的 a 子节点,只有 li 节点,所以无法获取任何匹配结果。

        因此,这里我们要注意/和//的区别,其中/用于获取直接子节点,//用于获取子孙节点

    6. 父节点

      • 连续的/或//可以查找子节点或子孙节点,如果知道了子节点,查找父节点可以用..来实现。   

         在首先选中 href 属性为 link4.html 的 a 节点,然后再获取其父节点,然后再获取其 class 属性,示例:

        from lxml import etree
        html = etree.parse('./test.html', etree.HTMLParser())
        result = html.xpath('//a[@href="link4.html"]/../@class')
        print(result)
        输出:
        ['item-1']

        同时,我们也可以通过 parent ::来获取父节点,示例:

        from lxml import etree
        html = etree.parse('./test.html', etree.HTMLParser())
        result = html.xpath('//a[@href="link4.html"]/parent::*/@class')  #为何需要加*
        print(result)
    7. 属性匹配

      • 在选取的时候,还可以用@符号进行属性过滤。 比如,这里如果要选取 class 为 item-1 的 li 节点,例:

        from lxml import etree
        html = etree.parse('./test.html', etree.HTMLParser())
        result = html.xpath('//li[@class="item-0"]')
        print(result)
        输出:
        [<Element li at 0x2675c1f5608>, <Element li at 0x2675c1f5648>]

        通过加入[@class="item-0”],限制了节点的 class 属性为 item-0,而 HTML 文本中符合 条件的 li 节点有两个,所以结果应该返回两个匹配到的元素。

    8. 文本获取
      • XPath 中的 text()方法获取节点中的文本,接下来尝试获取前面 li 节点中的文本,示例如下:
        from lxml import etree
        html = etree.parse('./test.html', etree.HTMLParser())
        result = html.xpath('//li[@class="item-0"]/text()')
        print(result)
        输出:
        ['
        ']

        没有获取到任何文本,只获取到了一个换行符。因为 XPath中text()前面是/,而此处/的含义是选取直接子节点,很明显 li 的直接子节点都是 a 节点,文本都是在a节点内部的,所以这里匹配到的结果就是被修正的 li 点内部的换行符,因为自动修正的 li节点的尾标签换行了。

        即选中的是这两个节点:
        <li class="item-0"><a href=”link1.html”>first item</a></li>                                                                                                                                                                                                                  <li class="item-0"><a href="link5.html”>fifth item</a>                                                                                                                                                                                                                            <li>
        其中一个节点因为自动修正,li 节点的尾标签添加的时候换行了,所以提取文本得到的唯一结果就是 li 节点的尾标签和 a 节点的尾标签之间的换行符。

      • 如果想获取 li 节点内部的文本,就有两种方式,一种是先选取 a 节点再获取文本,另一 种就是使用//
        1. 首先,选取至u a 节点再获取文本,示例:
          from lxml import etree
          html = etree.parse('./test.html', etree.HTMLParser())
          result = html.xpath('//li[@class="item-0"]/a/text()')
          print(result)
          输出:
          ['first item', 'fifth item']

          这里的返回值是两个,内容都是属性为 item-0的 li 节点的文本。

          这里是逐层选取的,先选取了 li 节点,又利用/选取了其直接子节点 a 然后再选取其文本,得到的结果恰好是符合我们预期的两个结果。

        2. 即使用(//)选取的结果,示例:

          from lxml import etree
          html = etree.parse('./test.html', etree.HTMLParser())
          result = html.xpath('//li[@class="item-0"]//text()')
          print(result)
          输出:
          ['first item', 'fifth item', '
          ']

          这里是选取所有子孙节点的文本,其中前两个就 是 li 的子节点 a 节点内部的文本,另外一个就是最后一个 li 节点内部的文本,即换行符。

          如果要想获取子孙节点内部的所有文本,可以直接用//加 text()的方式,这样可以保证 获取到最全面的文本信息,但是可能会夹杂一些换行符等特殊字符。 如果想获取某些特定子孙节点下的所有文本,可以先选取到特定的子孙节点,然后再调用 text()方法获取其内部文本,这样可以保证 获取的结果是整洁的。

    9. 属性获取
      • 用@符号可以获取节点属性。

        例:想获取所有 li 节点下所有 a 节点的 href 属性,示例:

        from lxml import etree
        html = etree.parse('./test.html', etree.HTMLParser())
        result = html.xpath('//li/a/@href')
        print(result)
        输出:
        ['link1.html', 'link2.html', 'link3.html', 'link4.html', 'link5.html']

        这里通过@href 即可获取节点的 href 属性。

      • 注意,此处和属性匹配的方法不同,属性匹配是中括号加属性名和值来限定某个属性,如[@href="link1.html”],而此处的@href 指的是获取节点的某 个属性     

    10. 属性多值匹配 
      • 某些节点的某个属性可能有多个值.例:
        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[@class="li"]/a/text()')
        print(result)
        输出:
        []

        这里 HTML 文本中 li 节点的 class 属性有两个值 li 和 li-first,此时如果还想用之前的属性匹配获取,就无法匹配了。所以输出为空。 这时需要用 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']

        通过 contains()方法,第一个参数传人属性名称,第二个参数传人属性值,只要此属性包含 所传人的属性值,就可以完成匹配了。此方式在某个节点的某个属性有多个值时经常用到,如某个节点的 class 属性通常有多个。

    11. 多属性匹配
      • 根据多个属性确定一个节点,这时需要同时匹配多个 属性。 此时可以使用运算符 and 来连接,例:
        from lxml import etree
        text = '''
        <li class="li li-first" name ="item"><a href="link.html">first item</a></li>
        '''
        html = etree.HTML(text)
        result =html.xpath('//li[contains(@class,"li") and @name="item"]/a/text()')
        print(result)
        输出:
        ['first item']

        li 节点又增加了一个属性 name。 要确定这个节点 , 需要同时根据 class 和 name 属性来选 择,一个条件是 class 属性里面包含 li 字符串,另一个条件是 name 属性为 item 字符串,二者需要同 时满足,需要用 and 操作符相连,相连之后置于中括号内进行条件筛选。 

      • 此处 and 是 XPath 中的运算符。 还有很多运算符,如 or、 mod 等,如下:

    12. 按序选择

      • 有时候,在选择的时候某些属性可能同时匹配了多个节点,如果只想要其中的某个节点,可以利用中括号传入索引的方法获取特定次序的节点

        • 例:需要第二个节点或者最后一个节点:

          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>
          '''
          html = etree.HTML(text)
          result =html.xpath('//li[1]/a/text()')
          print(result)
          result = html.xpath('//li[last()]/a/text()')
          print(result)
          result = html.xpath('//li[position()<3]/a/text()')
          print(result)
          result = html.xpath('//li[last()-2]/a/text()')
          print(result)
          输出:
          ['first item']
          ['fifth item']
          ['first item', 'second item']
          ['third item'
          • 第一次选择时,我们选取了第一个 li 节点,中括号中传入数字 1 即可。 注意,这里和代码中不 同,序号是以 1 开头的,不是以 0 开头。
          • 第二次选择时,我们选取了最后一个 li 节点,中括号中传入 last()即可,返回的便是最后一个 li 节点。
          • 第三次选择时,我们选取了位置小于 3 的 li 节点,也就是位置序号为 1 和 2 的节点,得到的结果 就是前两个 li 节点。
          • 第四次选择时,我们选取了倒数第三个 li 节点,中括号中传入 last()-2 即可。 因为 last()是最 后一个,所以 last()-2 就是倒数第三个。
        •  这里使用了 last()、position()等函数。 在 XPath 中,提供了 100 多个函数,包括存取、 数值、字符串、逻辑、节点、序列等处理功能,具体作用可以参考 : http://www.w3school.com.cn/xpath/xpath _functions.asp。

    13. 节点轴选择

      • XPath 提供了很多节点轴选择方法,包括获取子元素、兄弟元素、父元素、祖先元素等,示例:

        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">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>
        '''
        html = etree.HTML(text)
        result =html.xpath('//li[1]/ancestor::*')
        print(result)
        result = html.xpath('//li[1]/ancestor::div')
        print(result)
        result = html.xpath('//li[1]/attribute::*')
        print(result)
        result = html.xpath('//li[1]/child::a[@href="link1.html"]')
        print(result)
        result = html.xpath('//li[1]/descendant::span')
        print(result)
        result = html.xpath('//li[1]/following::*[2]')
        print(result)
        result = html.xpath('//li[1]/following-sibling::*')
        print(result)
        输出:
        [<Element html at 0x2d9a83c5608>, <Element body at 0x2d9a83c5588>, <Element div at 0x2d9a83c5548>, <Element ul at 0x2d9a83c5648>]
        [<Element div at 0x2d9a83c5548>]
        ['item-0']
        [<Element a at 0x2d9a83c5648>]
        [<Element span at 0x2d9a83c5548>]
        [<Element a at 0x2d9a83c5648>]
        [<Element li at 0x2d9a83c5588>, <Element li at 0x2d9a83c5688>, <Element li at 0x2d9a83c56c8>, <Element li at 0x2d9a83c5708>]
        • 第一次选择时,调用了 ancestor 轴,可以获取所有祖先节点。 其后需要跟两个冒号,然后是 节点的选择器,这里我们直接使用*,表示匹配所有节点,因此返回结果是第一个 li 节点的所有祖先 节点,包括 html、 body、 div 和 ul。
        • 第二次选择时,我们又加了限定条件,这次在冒号后面加了 div,这样得到的结果就只有 div 这 个祖先节点了
        • 第三次选择时,我们调用了 attribute 轴,可以获取所有属性值,其后跟的选择器还是*,这代表 获取节点的所有属性,返回值就是 li 节点的所有属性值。
        • 第四次选择时,我们调用了 child 轴,可以获取所有直接子节点。 这里我们又加了限定条件,选 取 href 属性为 linkl.html 的 a 节点
        • 第五次选择时,我们调用了 descendant 轴,可以获取所有子孙节点。 这里我们又加了限定条件获 取 span 节点,所以返回的结果只包含 span 节点而不包含 a节点
        • 第六次选择时,我们调用了 following 轴,可以获取当前节点之后的所有节点。 这里我们虽然使 用的是*匹配,但又加了索引选择,所以只获取了第二个后续节点
        • 第七次选择时,我们调用了 following-sibling 轴,可以获取当前节点之后的所有同级节点。 这 里我们使用*匹配,所以获取了所有后续同级节点。 以上是 XPath 轴的简单用法
      • 更多轴的用法可以参考: http://www.w3school.com.cn/xpath/xpath_axes.asp
      • 更多 XPath 的用法,可以查看: http://www.w3school.com.cn/xpath/index.asp。
      • 更多 Python lxml 库的用法,可以查看 http://lxml.de/

      

  • 相关阅读:
    联想 Vibe Shot(Z90-3) 免recovery 获取ROOT权限 救砖 VIBEUI V3.1_1625
    联想 Z5S(L78071)免解锁BL 免rec 保留数据 ROOT Magisk Xposed 救砖 ZUI 10.5.370
    联想 Z5(L78011) 免解锁BL 免rec 保留数据 ROOT Magisk Xposed 救砖 ZUI 10.5.254
    联想 S5 Pro(L78041)免解锁BL 免rec 保留数据 ROOT Magisk Xposed 救砖 ZUI 5.0.123
    第二阶段 冲刺八
    第二阶段 冲刺七
    第二阶段 冲刺六
    第二阶段 冲刺五
    代码大全阅读笔记03
    学习进度十二
  • 原文地址:https://www.cnblogs.com/Mack-Yang/p/10069066.html
Copyright © 2011-2022 走看看