Python Xpath 的使用

Xpath 的使用


正則表達式 筆記整理html

Python requests 模塊node

在用 Python 實現爬蟲時,可使用 requests 庫訪問資源,而後用正則表達式提取信息。python

可是,這裏會有一些繁瑣,由於正則表達式的書寫是比較嚴格的,萬一有一個地方寫錯了,可能會致使匹配失敗沒法提取須要的信息。正則表達式

對於網頁的節點來講,能夠定義 id、class 或其餘屬性。節點之間有層次關係,在網頁中,其實能夠經過 Xpath 定位一個或多個節點。微信

那麼相應的,在頁面解析的時候,利用 Xpath 定位節點,調用相應的方法獲取正文或者屬性,那麼徹底能夠獲取須要的信息。函數

在 Python 中,這個解析庫叫 lxml,下面來介紹這個解析庫的用法。測試

lxml 庫


lxml 是 Python 的一個解析庫,支持 HTML 和 XML 的解析,支持 XPath 解析方式,效率很是高。編碼

使用 lxml 以前,須要先安裝,可使用以下命令:.net

$ pip install lxml

Xpath 簡介


Xpath,全稱 XML Path Language,便是 XML 路徑語言。Xpath 是一門在 XML 文檔中查找信息的語言,用於在 XML 文檔中經過元素和屬性進行導航,但一樣適用於 HTML 文檔的搜索。設計

在實現爬蟲時,徹底能夠經過 Xpath 進行信息提取。

Xpath 的功能強大,使用路徑表達式來選取 XML 或 HTML 文檔中的節點或者節點集。Xpath 有超過 100 個內建的函數。這些函數可用於字符串、數值、日期和時間比較、節點、序列處理和邏輯值等等。

Xpath 於 1999 年 11 月 16 日成爲 W3C 標準,被設計爲供 XSLT、XPointer 以及其餘 XML 解析軟件使用。

Xpath 語法


前面說起了,Xpath 使用路徑表達式選取文檔中的節點或節點集。

下面羅列經常使用的路徑表達式:

表達式 描述說明
nodename 選取此節點的全部子節點
/ 從根節點選取
// 從當前節點選擇子孫節點(不考慮它們的位置)
. 選取當前節點
.. 選取當前節點的父節點
@ 選取屬性

上面羅列的內容屬於經常使用部分,用示例來講明下具體的用法:

//div[@class="document"]

這就是一個 Xpath 路徑表達式,表明的是選擇名稱爲 div,屬性 class 的值爲 document 的節點。

在 Python 中,會經過 lxml 庫,利用 Xpath 進行解析。

實例應用


經過實例瞭解使用 Xpath 對網頁進行解析的過程,代碼以下(下面 HTML 內容節選自豆瓣,稍做更改):

# 先導入 lxml 庫
from lxml import etree

text = """
<div>
    <ul>
        <li class="pl2"><a href="https://book.douban.com/subject/1007305/">紅樓夢</a>
        <li class="pl2"><a href="https://book.douban.com/subject/4913064/">活着</a></li>
        <li class="pl2"><a href="https://book.douban.com/subject/6082808/">百年孤獨</a></li>
        <li class="pl1"><a href="https://book.douban.com/subject/4820710/">1984</a></li>
    </ul>
</div>
"""

html = etree.HTML(text)
result = etree.tostring(html)
print(result.decode('utf-8'))

在上面的實例中,先導入 lxml 庫中的 etree 模塊,聲明一段 HTML 文本,而後使用 etree 的 HTML 類進行初始化,構造一個 Xpath 解析對象。在這裏須要注意一點,實例中,聲明的 HTML 文本第 1 個節點沒有閉合,可是 etree 模塊會自動修正。

etree.toString() 方法用於輸出修正後的 HTML 內容,不過該方法返回的是 byte 類型,輸出的時候須要進行解碼轉換爲 str 類型。

上面的輸出結果以下:

<html><body><div>
    <ul>
        <li class="pl2"><a href="https://book.douban.com/subject/1007305/">&#32418;&#27004;&#26790;</a>
        </li><li class="pl2"><a href="https://book.douban.com/subject/4913064/">&#27963;&#30528;</a></li>
        <li class="pl2"><a href="https://book.douban.com/subject/6082808/">&#30334;&#24180;&#23396;&#29420;</a></li>
        <li class="pl1"><a href="https://book.douban.com/subject/4820710/">1984</a></li>
    </ul>
</div>
</body></html>

在這裏能夠看到 li 節點標籤已經補全,同時自動添加了 body、html 節點。

上面的代碼中,中文沒有正常顯示。這裏屬於編碼的問題,能夠將上面的代碼稍微修改一下:

result = etree.tostring(html, encoding='gbk')
print(result.decode('gbk'))

再看輸出結果:

<?xml version='1.0' encoding='gbk'?>
<html><body><div>
    <ul>
        <li class="pl2"><a href="https://book.douban.com/subject/1007305/">紅樓夢</a>
        </li><li class="pl2"><a href="https://book.douban.com/subject/4913064/">活着
</a></li>
        <li class="pl2"><a href="https://book.douban.com/subject/6082808/">百年孤獨<
/a></li>
        <li class="pl1"><a href="https://book.douban.com/subject/4820710/">1984</a><
/li>
    </ul>
</div>
</body></html>

這裏有所不一樣,前面多了個聲明,同時標記編碼方式爲 GBK。

另外, lxml 庫也能夠直接讀取文件進行解析,示例以下(先將上面的未修正的 HTML 內容放到 example.html 文件中):

from lxml import etree

html = etree.parse('./example.html', etree.HTMLParser())
result = etree.tostring(html)
print(result.decode('utf-8'))

這個時候輸出的結果會多一個 DOCTYPE 的聲明。

Xpath 節點


全部節點

// 開頭的 Xpath 表達式爲選取全部符合要求的節點,沿用上面的例子:

...
result = html.xpath('//*')
print(result)

運行結果:

[<Element html at 0x4b34fc8>, <Element body at 0x4b3b108>, <Element div at 0x4b3b088>,
 <Element ul at 0x4b3b148>, <Element li at 0x4b3b188>, <Element a at 0x4b3b208>,
 <Element li at 0x4b3b248>, <Element a at 0x4b3b288>, <Element li at 0x4b3b2c8>,
 <Element a at 0x4b3b1c8>, <Element li at 0x4b3b308>, <Element a at 0x4b3b588>]

在這裏, * 表示匹配全部的節點,由運行結果能夠看出,返回的列表中,包括了 html, body, div, ul, li, a 全部節點。

固然 // 後面能夠跟特定的節點,例如:

...
result = html.xpath('//a')
print(result)

運行結果:

[<Element a at 0x2d1d688>, <Element a at 0x2d1d648>, <Element a at 0x2d1d748>, <Element a at 0x2d1d788>]

子節點

/ 或者 // 能夠用來定位子節點或者子孫節點,例如定位 li 節點的全部 a 節點:

...
result = html.xpath('//li/a')
print(result)

運行結果:

[<Element a at 0x2cfd688>, <Element a at 0x2cfd648>, <Element a at 0x2cfd748>, <Element a at 0x2cfd788>]

在這裏能夠看到,與上面直接用 //a 表達式獲取的結果相同,但這裏有所區別,//a 表達式找的全部的 a 節點,//li/a 這裏找的是全部 li 節點的全部直接 a 子節點。

好比,有以下標籤內容:

<title><a href="link.html">Title</a></title>

用這個示例來區分,根據上面的區分解釋,在這裏用 //a 是能夠匹配到這項內容,可是 //li/a 則匹配不到,由於示例中 a 節點並不是 li 節點的直接子節點。

在原來的 HTML 文檔內容中,a 是 li 的直接節點,也是 ul 的子孫節點,那麼要定位 a 節點,也能夠按照以下的表達式來寫:

...
result = html.xpath('//ul//a')
print(result)

這裏獲得的結果跟上面是一致的:

[<Element a at 0x2cfd688>, <Element a at 0x2cfd648>, <Element a at 0x2cfd748>, <Element a at 0x2cfd788>]

可是要注意,不可以寫成 //ul/a,由於 a 並不是 ul 的直接子節點,若是這樣寫則沒法匹配,返回空列表。

因此要對 /// 加以區分,/ 用於獲取直接子節點,//用於獲取子孫節點。

父節點

獲取父節點的信息,用 .. 來實現,例如:

<li class="p12"><a href="https://book.douban.com/subject/1007305/"></a>紅樓夢</li>

想要獲取 href 屬性爲 "https://book.douban.com/subject/1007305/" 的 a 節點的父節點屬性。

代碼以下:

...
result = html.xpath('//a[@href="https://book.douban.com/subject/1007305/"]/../@class')
print(result)

運行結果:

['pl2']

這個結果正是父節點的屬性。

屬性

節點中,屬性可存在單值或多值的狀況,一個節點也能夠有多個屬性,當出現這些狀況時,使用的表達式每每不可以一成不變,須要針對性進行書寫。

單值匹配

在上面的例子中,其實已經使用屬性匹配,@ 符號用於屬性過濾。在上面的例子當中,有一個屬性跟其餘的不一樣,如今將其定位,代碼實現:

...
result = html.xpath('//li[@class="pl1"]')
print(result)

運行結果:

[<Element li at 0x2cfd688>]

[@class="pl1"] 這部分對定位進行了限制,找的是 class 屬性值爲 pl1 的節點。

多值匹配

屬性有時候可能不止 1 個,以下示例:

<li class="pl1 pl2"><a href="https://book.douban.com/subject/4820710/">1984</a></li>

將 li 的屬性值改成 pl1 pl2,若是仍是用原來的表達式的話:

...
result = html.xpath('//li[@class="pl1"]')
print(result)

獲得的是空列表:

[]

這個時候,要考慮使用 contains() 方法,這個方法須要的參數有:第一個參數是屬性名稱,第二個參數是屬性值。該方法的實現過程是,若第一個參數屬性包含第二個參數中的屬性值,則能夠匹配成功。例如:

...
result = html.xpath('//li[contains(@class, "pl1")]')
print(result)

運行結果:

[<Element li at 0x2d1d648>]

這個方法在屬性值不止 1 個的狀況下,很是有用。

多屬性匹配

在節點中,除了單個屬性能夠有多個值以外,也能夠有多個屬性。假設有以下節點:

<li class="pl1 pl2" name="item"><a href="https://book.douban.com/subject/4820710/">1984</a></li>

這種狀況要用到 Xpath 運算符,下面羅列經常使用的運算符:

運算符 描述 實例 返回值
計算兩個節點集 //book 丨 //cd 返回擁有 book 和 cd 元素的節點
+ 加法 6 + 4 10
- 減法 6 - 4 2
* 乘法 6 * 4 24
div 除法 9 div 3 3
= 等於 stature=178 當 stature 爲 178 時,返回 true;不然,返回 false.
!= 不等於 stature!=178 當 stature 不是 178 時,返回 true;不然,返回 false
< 小於 stature<178 當 stature 爲 177 時,返回 true;當 stature 爲 179 時,返回 false
<= 小於或等於 stature<=178 當 stature 爲 177 時,返回 true;當 stature 爲 179 時,返回 false
> 大於 stature>178 當 stature 爲 179 時,返回 true;當 stature 爲 177 時,返回 false
>= 大於 stature>=178 當 stature 爲 179 時,返回 true;當 stature 爲 177 時,返回 false
or stature=178 or stature=179 當 stature=178 時,返回 true;當 stature=175 時,返回 false
and stature>175 and stature<178 當 stature=178 時,返回 true;當 stature=165 時,返回 false
mod 取餘 5 mod 2 1

在這裏,使用 and 運算符將多個屬性鏈接:

...
result = html.xpath('//li[contains(@class, "pl1") and @name="item"]')
print(result)

運算結果:

[<Element li at 0x2cfd688>]

獲取屬性

這裏要與上面區分開,上面都是根據屬性去定位節點。如今是想查找某個節點的確切屬性。例如查找 li 下 a 節點的 href 屬性:

...
result = html.xpath('//li/a/@href')
print(result)

返回結果:

['https://book.douban.com/subject/1007305/', 'https://book.douban.com/subject/4913064/', 'https://book.douban.com/subject/6082808/', 'https://book.douban.com/subject/4820710/']

這裏 /@href 是爲了獲取節點屬性,上面 [@class="pl1"] 是爲了限定屬性查找節點,要加以區分。

文本獲取

Xpath 用 text() 方法獲取文本,如今嘗試獲取上面屬性所演示的示例,獲取節點中的文本,同時驗證上面定位的是不是屬性值爲 pl1 的節點:

...
result = html.xpath('//li[@class="pl1"]/a/text()')
print(result)

運行結果:

['1984']

從結果來看,上面屬性示例中返回的節點,的確是屬性值爲 pl1 的節點。這裏須要注意,由於文本是被 a 節點包裹着的,若是直接在 li 節點下使用 /text() 是獲取不到想要的信息的。若是改爲 //text() 表達式,則能夠獲取全部子孫節點的文本,但這裏可能獲取的內容會有些誤差,有可能會獲取到換行符,這個並非想要的信息。以下示例:

result = html.xpath('//li[@class="pl1"]//text()')
print(result)

# 輸出結果:
# ['\n        ', '1984', '\n        ']

這裏就是須要注意的地方,若是要想獲取特定子節點的文本,首先建議先找到特定的子節點,而後在子節點下使用 text() 方法,這樣確保獲取的信息是整潔的。

Xpath 軸


軸可定義相對當前節點的節點集。

先羅列一些簡單的軸及其含義:

軸名稱 含義
ancestor 選取當前節點的全部祖先節點
attribute 選取當前節點的全部屬性
child 選取當前節點的全部直接子節點
descendant 選取當前節點的全部子孫節點
following 選取當前節點以後的全部節點

更多軸的詳細用法可參考:https://www.w3school.com.cn/xpath/xpath_axes.asp

使用軸的語法:

軸名稱::節點測試[謂語]

沿用上面的例子,關於軸的簡單實例:

例子 結果
//li/ancestor:: * 選取 li 節點的全部祖先節點
//li/ancestor::div 這裏加了 div 加以限定,因此僅返回 div 節點
//li/attribute:: * 獲取 li 節點的全部屬性
//li/child::a[@href="#"] 這裏加了限定條件,因此僅返回 href 屬性爲 # 的 a 節點
//li/descentdant:: * 獲取 li 節點的全部子孫節點
//li/following:: * 獲取 li 節點後續的全部節點

小結

以上就是關於 Xpath 的內容, Xpath 還有一些函數,文章未說起,若是有興趣的話,能夠參考:https://www.w3school.com.cn/xpath/xpath_functions.asp


歡迎關注微信公衆號《書所集錄》
相關文章
相關標籤/搜索