上一節,我們詳述了lxml.html的各種操作,接下來我們熟練掌握一下XPath,就可以熟練的提取網頁內容了。
XPath 是什麼?
XPath的全稱是 XML Path Language,即XML 路徑語言,是一種在XML(HTML)文件中查詢資訊的語言。它有4點特性:
- XPath 使用路徑表示式在 XML 文件中進行導航
- XPath 包含一個標準函式庫
- XPath 是 XSLT 中的主要元素
- XPath 是一個 W3C 標準
我們從網頁中提取資料,主要應用前兩點。
XPath 路徑表示式
使用XPath我們可以很容易定位到網頁中的節點,也就是找到我們關心的資料。這些路徑跟電腦目錄、網址的路徑很相似,透過/
來表示路徑的深度。
XPath 標註函式庫
頭內建了100多個函式,當然我們提取資料用到的有限,也就不用記住全部100多個函式了。
Xpath 的節點(Node)
XPath中的核心就是節點(Node),定義了7種不同型別的節點: 元素(Element)、屬性(Attribute)、文字(Text)、名稱空間(Namespace)、處理指令(processing-instruction)、註釋(Comment)和文件節點(Document nodes)
這些節點組成一棵節點樹,樹的根節點被稱為文件節點。
其中註釋就是html裡面的註釋:<!-- 註釋內容 -->
而名稱空間、處理指令和網頁資料提取基本沒關係,這裡就不再詳述。
下面我們以一個簡單的html文件為例,來解釋不同的節點及其關係。
<html lang="en-US">
<body>
<div>ABC</div>
<ul id="menu">
<li class="item">home</li>
<li class="item">python</li>
</ul>
</body>
</html>
這段html中的節點有:
- 文件節點:
<html>
- 元素節點:
<li class="item">python</li>
- 屬性節點:
id="menu"
XPath 節點的關係
節點間的關係完全照搬人類傳宗接代的輩分關係,但只是直系關係,沒有叔叔、大伯之類的旁系關係。
還是以上面的html文件為例來說明節點關係:
父(Parent)
每個元素節點(Element)及其屬性都有一個父節點。
比如,body的父是html,而body是div、ul 的父親。
子(Children)
每個元素節點可以有零個、一個或多個子。
比如,body有兩個子:div,ul,而ul也有兩個子:兩個li。
同輩(Sibling)
同輩有相同的父輩節點。
比如,div和ul是同輩。
先輩(Ancestor)
某節點的父輩及其以上輩分的節點。
比如,li的父輩有:ul、div、body、html
後代(Descendant)
某節點的子及其子孫節點。
比如,body的後代有:div、ul、li。
XPath節點的選取
選取節點,也就是透過路徑表達來實現。這是我們在網頁提取資料時的關鍵,要熟練掌握。
下表是比較有用的路徑表示式:
表示式 | 說明 |
---|---|
nodename | 選取當前節點的名為nodename的所有子節點。 |
/ | 從根節點選取,在路徑中間時表示一級路徑 |
// | 從當前節點開始選擇文件中的節點,可以是多級路徑 |
. | 從當前節點開始選取 |
.. | 從父節點開始選取 |
@ | 按屬性選取 |
接下來透過具體的示例來加深對路徑表達的理解:
路徑表示式 | 解釋 |
---|---|
/html/body/ul/li | 從根節點開始依照路徑選取li元素。返回多個。 |
//ul/li[1] | 還是選取li元素,但是路徑多級跳躍到ul/li。[1]表示只取第一個li。 |
//li[last()] | 還是選取li,但路徑更跳躍。[last()]表示取最後一個li元素。 |
//li[@class] | 選取根節點的名為li且有class屬性的所有後代。 |
//li[@class=”item”] | 選擇根節點的名為li且class屬性為item的所有後代。 |
//body/*/li | 選取body的名為li的孫子節點。* 是萬用字元,表示任何節點。 |
//li[@*] | 選取所有帶屬性的li元素。 |
//body/div | //body/ul |
選取body的所有div和ul元素。 |
body/div | 相對路徑,選取當前節點的body元素的子元素div。絕對路徑以 / 開始。 |
XPath函式
Xpath的函式很多,涉及到錯誤、數值、字串、時間等等,然而我們從網頁中提取資料的時候只會用到很少的一部分。其中最重要的就是字串相關的函式,比如contains()函式。
contains(a, b)
如果字串a包含字串b,則返回true,否則返回false。
比如: contains(‘猿人學Python’, ‘Python’),返回true
那麼它用在什麼時候呢?我們知道,一個html標籤的class是可以有多個屬性值的,比如:
<div class="post-item text-red text-center">
...
</div>
這段html中div有三個class值,第一個表面它是一條釋出的訊息,後面兩個是對格式做了更多的設定。如果我們想提取網頁中所有釋出的訊息,只需要匹配到post-item
即可,這時候就可以用上contains了:
doc.xpath('//div[contains(@class, "post-item")]')
跟contains()類似的字串匹配的函式還有:
- starts-with(string1, string2) 判斷string1是否以string2開頭
- ends-with(string1, string2) 判斷string1是否以string2結尾
- matches(string, pattern) 透過正規表示式匹配
然而,在lxml的xpath中使用ends-with(), matches() 會報錯
In [232]: doc.xpath('//ul[ends-with(@id, "u")]')
---------------------------------------------------------------------------
XPathEvalError Traceback (most recent call last)
<ipython-input-232-79a4afc46a75> in <module>()
----> 1 doc.xpath('//ul[ends-with(@id, "u")]')
src/lxml/etree.pyx in lxml.etree._Element.xpath()
src/lxml/xpath.pxi in lxml.etree.XPathElementEvaluator.__call__()
src/lxml/xpath.pxi in lxml.etree._XPathEvaluatorBase._handle_result()
XPathEvalError: Unregistered function
lxml 竟然不支援ends-with(), matches()函式
到lxml官方網站去看看,原來它說了只支援 XPath 1.0:
lxml supports XPath 1.0, XSLT 1.0 and the EXSLT extensions through libxml2 and libxslt in a standards compliant way.
接著又在Wikipedia上找到Xpath 2.0 和 1.0 的差異對比,果然ends-with(), matches() 只屬於2.0。下圖中,粗體部分是1.0包含的,其它是2.0也有的:
好了,Xpath在網頁內容提取中要用到的部分我們已經學完了。下一節,我們將以例項講解xpath具體提取資料的過程。
我的公眾號:猿人學 Python 上會分享更多心得體會,敬請關注。
***版權申明:若沒有特殊說明,文章皆是猿人學 yuanrenxue.com 原創,沒有猿人學授權,請勿以任何形式轉載。***