Python網路爬蟲開發實戰使用XPath,xpath的多種用法

2020-08-12 14:18:17

XPath,全稱XML Path Language,即XML路徑語言,它是一門在XML文件中查詢資訊的語言。它最初是用來搜尋XML文件的,但是它同樣適用於HTML文件的搜尋。

所以在做爬蟲時,我們完全可以使用XPath來做相應的資訊抽取。本節中,我們就來介紹XPath的基本用法。

  1. XPath概覽
    XPath的選擇功能十分強大,它提供了非常簡潔明瞭的路徑選擇表達式。另外,它還提供了超過100個內建函數,用於字串、數值、時間的匹配以及節點、序列的處理等。幾乎所有我們想要定位的節點,都可以用XPath來選擇。

XPath於1999年11月16日成爲W3C標準,它被設計爲供XSLT、XPointer以及其他XML解析軟體使用,更多的文件可以存取其官方網站:https://www.w3.org/TR/xpath/。

  1. XPath常用規則
    表4-1列舉了XPath的幾個常用規則。

表4-1 XPath常用規則

表達式

描述

nodename

選取此節點的所有子節點

/

從當前節點選取直接子節點

//

從當前節點選取子孫節點

.

選取當前節點

選取當前節點的父節點

@

選取屬性

這裏列出了XPath的常用匹配規則,範例如下:

//title[@lang=‘eng’]

這就是一個XPath規則,它代表選擇所有名稱爲title,同時屬性lang的值爲eng的節點。

後面會通過Python的lxml庫,利用XPath進行HTML的解析。

  1. 準備工作
    使用之前,首先要確保安裝好lxml庫,若沒有安裝,可以參考第1章的安裝過程。

  2. 範例引入
    現在通過範例來感受一下使用XPath來對網頁進行解析的過程,相關程式碼如下:

from lxml import etree
text = ‘’’

''' 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型別,結果如下:

可以看到,經過處理之後,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的宣告,不過對解析無任何影響,結果如下:

  1. 所有節點
    我們一般會用//開頭的XPath規則來選取所有符合要求的節點。這裏以前面的HTML文字爲例,如果要選取所有節點,可以這樣實現:

from lxml import etree
html = etree.parse(’./test.html’, etree.HTMLParser())
result = html.xpath(’//*’)
print(result)

執行結果如下:

[<Element html at 0x10510d9c8>, <Element body at 0x10510da08>, <Element div at 0x10510da48>, <Element ul at 0x10510da88>, <Element li at 0x10510dac8>, <Element a at 0x10510db48>, <Element li at 0x10510db88>, <Element a at 0x10510dbc8>, <Element li at 0x10510dc08>, <Element a at 0x10510db08>, <Element li at 0x10510dc48>, <Element a at 0x10510dc88>, <Element li at 0x10510dcc8>, <Element a at 0x10510dd08>]

這裏使用*代表匹配所有節點,也就是整個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])

這裏要選取所有li節點,可以使用//,然後直接加上節點名稱即可,呼叫時直接使用xpath()方法即可。

執行結果:

[<Element li at 0x105849208>, <Element li at 0x105849248>, <Element li at 0x105849288>, <Element li at 0x1058492c8>, <Element li at 0x105849308>]
<Element li at 0x105849208>

這裏可以看到提取結果是一個列表形式,其中每個元素都是一個 Element物件。如果要取出其中一個物件,可以直接用中括號加索引,如[0]。

  1. 子節點
    我們通過/或//即可查詢元素的子節點或子孫節點。假如現在想選擇li節點的所有直接a子節點,可以這樣實現:

from lxml import etree

html = etree.parse(’./test.html’, etree.HTMLParser())
result = html.xpath(’//li/a’)
print(result)

這裏通過追加/a即選擇了所有li節點的所有直接a子節點。因爲//li用於選中所有li節點,/a用於選中li節點的所有直接子節點a,二者組合在一起即獲取所有li節點的所有直接a子節點。

執行結果如下:

[<Element a at 0x106ee8688>, <Element a at 0x106ee86c8>, <Element a at 0x106ee8708>, <Element a at 0x106ee8748>, <Element a at 0x106ee8788>]

此處的/用於選取直接子節點,如果要獲取所有子孫節點,就可以使用//。例如,要獲取ul節點下的所有子孫a節點,可以這樣實現:

from lxml import etree

html = etree.parse(’./test.html’, etree.HTMLParser())
result = html.xpath(’//ul//a’)
print(result)

執行結果是相同的。

但是如果這裏用//ul/a,就無法獲取任何結果了。因爲/用於獲取直接子節點,而在ul節點下沒有直接的a子節點,只有li節點,所以無法獲取任何匹配結果,程式碼如下:

from lxml import etree

html = etree.parse(’./test.html’, etree.HTMLParser())
result = html.xpath(’//ul/a’)
print(result)

因此,這裏我們要注意/和//的區別,其中/用於獲取直接子節點,//用於獲取子孫節點。

  1. 父節點
    我們知道通過連續的/或//可以查詢子節點或子孫節點,那麼假如我們知道了子節點,怎樣來查詢父節點呢?這可以用…來實現。

比如,現在首先選中href屬性爲https://blog.csdn.net/weixin_48794920/article/details/link4.html的a節點,然後再獲取其父節點,然後再獲取其class屬性,相關程式碼如下:
from lxml import etree

html = etree.parse(’./test.html’, etree.HTMLParser())
result = html.xpath(’//a[@href=「https://blog.csdn.net/weixin_48794920/article/details/link4.html」]/…/@class’)
print(result)

執行結果如下:

1
[‘item-1’]

檢查一下結果發現,這正是我們獲取的目標li節點的class。

同時,我們也可以通過parent::來獲取父節點,程式碼如下:
from lxml import etree

html = etree.parse(’./test.html’, etree.HTMLParser())
result = html.xpath(’//a[@href=「https://blog.csdn.net/weixin_48794920/article/details/link4.html」]/parent: