Class 14 - 1 解析庫 -- XPath

對於網頁的節點來講,它能夠定義 id 、class 或其餘屬性。 並且節點之間還有層次關係,在網頁 中能夠經過XPath 或 css 選擇器來定位一個或多個節點。因此在頁面解析時,利用 XPath 或 css 選擇器來提取某個節點,而後再調用相應方法獲取它的正文內容或者屬性,也能夠提取咱們想要的任意信息。css

XPath的使用html

  • 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的節點node

  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 節點。      spa

    另外,也能夠直接讀取文本文件進行解析,示例以下:code

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

    其中 test.html 的內容就是上面例子中的 HTML代碼,輸出結果會多一個DOCTYPE聲明,對解析無任何影響。xml

  4. 全部節點
    • 咱們通常會用 // 開頭的 XPath 規則來選取全部符合要求的節點。 之前面的 HTML 文本爲例,若是要選取全部節點:htm

      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 節點,例:blog

      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)
      輸出:
      ['\r\n']

      沒有獲取到任何文本,只獲取到了一個換行符。由於 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', '\r\n']

        這裏是選取全部子孫節點的文本,其中前兩個就 是 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/
相關文章
相關標籤/搜索