XPath详解教程
目錄
????????1. XPath 概覽
????????2. XPath 常用規則
????????3. 準備工作
????????4.實例引入
????????5.所有節點
????????6. 子節點
????????7.父節點
????????8.屬性匹配
????????9.文本獲取
????????10.屬性獲取
????????11.屬性多值匹配
????????12. 多屬性匹配
????????13.按序選擇
????????14. 節點軸選擇
????????結語
1. XPath 概覽
????????XPath 全稱 XML Path Language ,即 XML 路徑語言,它是一門在 XML 文檔中查找信息的語言 它最初是用來搜尋 XML 文檔的,但是它同樣適用于 HTML 文檔的搜索 ????????XPath 的選擇功能十分強大,它提供了非常簡潔明了的路徑選擇表達式 另外,它還提供了超過 100 個內建函數,用于字符串、數值、時間的匹配以及節點、序列的處理 幾乎所有我們想要定 的節點,者阿以用 XPath 來選擇 ????????XPath于1999年11月16日成為 W3C 標準,它被設計為供 XSLT、XPointer 以及其他 XML 解析 軟件使用,更多的文檔可以訪問其官方網站 xpath cover page - W3C2. XPath 常用規則
常用的集中規則有:
- nodename ? 選擇此節點的所有子節點
- /? 從當前節點選擇直接子節點
- // 從當前節點選擇子孫節點
- . 選取當前節點
- ?..? 選取當前節點的父節點
- ?@? 選取屬性
????????舉個例子 : //title [@lang = 'ergou'] ? 這個就是選擇所有標簽名為title 并且 lang的屬性值為二狗的元素
????????//title [@lang = 'ergou'][1] ?這個就是選擇所有標簽名為title 并且 lang的屬性值為二狗的元素的第一個元素
????????//title [@lang = 'ergou'][1]/@href ??這個就是選擇所有標簽名為title 并且 lang的屬性值為二狗的元素的第一個元素的href 屬性
3. 準備工作
????????使用之前,首先要確保安裝好 lxml 庫,若沒有安裝 ,可以參考安裝過程4.實例引入
????????這里首先導人 lxml 庫的 etree 模塊,然后聲明了一段HTML文本,調用 HTML 類進行初始化,這樣就成功構造了一個XPath解析對 這里需要注意的是,HTML文本中的最后一個li節點是沒有 閉合的,但是etree模塊可以自動修正HTML文本 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')) ????????這里我們調用 tostring()方法即可輸出修正后的HTML代碼,但是結果是 bytes 類型 這里利用 decode()方法將其轉成 str 類型,結果如下:
????????可以看到,經過處理之后, li 節點標簽被補全,并且還向動添加了 body,html 節點
????????另外,也可以直接讀取文本文件進行解析,示例如下:
from lxml import etree html = etree.parse('./test.html',etree.HTMLParser()) ''' 其中test.html為上述的 <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> ''' result= etree.tostring(html) print(result.decode('utf-8'))返回:
????????至于有 
,應該是我手打,不知哪錯了,排除這個,結果與上面略有不同,就是多了一個聲明DOCTYPE ,不過對解析沒有任何影響
5.所有節點
????????我們一般會用刀 // 開頭的 XPath 規則來選取所有符合要求的節點 這里以上面的 HTML 文本為例, 如果要選取所有節點,可以這樣實現: from lxml import etree html = etree.parse('./test.html',etree.HTMLParser()) ''' 其中test.html為上述的 <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>''' result= html.xpath('//*') print(result)返回:
????????這里使用*代表匹配所有節點,也就是整個 HTML 文本中的所有節點都會被獲取 可以看到,返 回形式是一個列表,每個元素是 Element 類型,其后跟了節點的名稱,如 html、body、div、ul、li、a等,所有節點都包含在列表中了 ????????當然 ,此處匹配也可以指定節點名稱。如果想獲取所有 li 節點,示例如下: from lxml import etree html = etree.parse('./test.html',etree.HTMLParser()) ''' 其中test.html為上述的 <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>''' result= html.xpath('//li') print(result) print(result[0])
返回:
????????這里可以看到提取結果是一個列表形式,其中每個元素都是 Element 對象。如果要取出其中一個對象 ,可以直接用中括號加索引,如[0]。
6. 子節點
????????我們通過 / 或 // 即可查找元素的子節點或子孫節點。假如現在想選擇 li 節點的所有直接a子節點, 可以這樣實現: from lxml import etree html = etree.parse('./test.html',etree.HTMLParser()) ''' 其中test.html為上述的 <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>''' result= html.xpath('//li/a') print(result)返回:
????????此處的 /?用于選取直接子節點 ,如果要獲取所有子孫節點,就可以使用 //??例如,要獲取 ul 節點下的所有子孫a節點,可以這樣實現:
from lxml import etree html = etree.parse('./test.html',etree.HTMLParser()) ''' 其中test.html為上述的 <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>''' result= html.xpath('//ul//a') print(result)運行結果:
????????但是如果這里用 //ul/a?,就無法獲取任何結果了 因為 / 用于獲取直接子節點,而在 節點下沒 有直接 子節點,只有 li 節點,所以無法獲取任何匹配結果,代碼如下: from lxml import etree html = etree.parse('./test.html',etree.HTMLParser()) ''' 其中test.html為上述的 <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>''' result= html.xpath('//ul/a') print(result)運行結果為:
7.父節點
????????我們知道通過連續的 / 或 // 可以查找子節點或子孫節點,那么假如我們知道了子節點,怎樣來查找父節點呢?這可以用 ..?來實現 ????????比如,現在首先選中 href 屬性為 link4.html 節點,然后再獲取其父節點,然后再獲取其 class 屬性,相關代碼如下: from lxml import etree html = etree.parse('./test.html',etree.HTMLParser()) ''' 其中test.html為上述的 <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> ''' result= html.xpath('//a[@href="link4.html"]/../@class') print(result)返回:
?同時,我們也可以通過 parent:: 來獲取父節點:
from lxml import etree html = etree.parse('./test.html',etree.HTMLParser()) ''' 其中test.html為上述的 <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> ''' result= html.xpath('//a[@href="link4.html"]/parent::*/@class') print(result)8.屬性匹配
????????在選取的時候,我們還可以用@符號進行屬性過濾。比如,這里如果要選取 class為item-1的li 節點,可以這樣實現: from lxml import etree html = etree.parse('./test.html',etree.HTMLParser()) ''' 其中test.html為上述的 <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> ''' result= html.xpath('//li[@class="item-0"]') print(result) ????????這里我們通過加入[@class= item -0”],限制了節點的 class 屬性為 item-0 ,而 HTML 文本中符合 條件的 li 節點有兩個,所以結果應該返回兩個匹配到的元素 結果如下:?可見,匹配結果正是兩個,至于是不是那正確的兩個,后面再驗證 。
9.文本獲取
????????用 XPath 中的 text()? 方法獲取節點中的文本,接下來嘗試獲取前面 li 節點中的文本,相關
代碼如下: from lxml import etree html = etree.parse('./test.html',etree.HTMLParser()) ''' 其中test.html為上述的 <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> ''' result= html.xpath('//li[@class="item-0"]/text()') print(result)運行結果:
????????奇怪的是,我們并沒有獲取到任何文本,卻有WIN回車換行,這是為什么呢?因為 XPath中text()前面是/,而此處/的含義是選取直接子節點,很明顯li的直接子節點都是a節點,文本都是在a節點內部的,所以這里匹配到的結果就是被修正的li節點內部的符號,因為自動修正的 li 節點的尾標簽有WIN回車換行。
????????即選中的是這兩個節點: <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 節點的尾標簽和a節點的尾標簽之間的換行符。 ????????因此,如果想獲取 li 節點內部的文本,就有兩種方式,一種是先選取a節點再獲取文本,另一 種就是使用 //??接下來,我們來看下二者的區別: ????????首先 ,選取至a節點再獲取文本,代碼如下: from lxml import etree html = etree.parse('./test.html',etree.HTMLParser()) ''' 其中test.html為上述的 <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> ''' result= html.xpath('//li[@class="item-0"]/a/text()') print(result)輸出為:
????????可以看到,這里的返回值是兩個,內容都是屬性為 item-o的?li 節點的文本,這也印證了前面屬性匹配的結果是正確的。 ????????再來看下用另一種方式(即使用 // )選取的結果,代碼如下: from lxml import etree html = etree.parse('./test.html',etree.HTMLParser()) ''' 其中test.html為上述的 <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> ''' result= html.xpath('//li[@class="item-0"]//text()') print(result)輸出:
????????不出所料,這里的返回結果是3個,可想而知,這里是選取所有子孫節點的文本,其中前兩個就 li 的子節點a節點內部的文本,另外一個就是最后一個 li 節點內部的文本,即換行符。10.屬性獲取
????????我們知道用 text()?可以獲取節點內部文本,那么節點屬性該怎樣獲取呢?其實還是用@符號就可。例如,我們想獲取所有 li 節點下所有a節點的 href 屬性,代碼如下: from lxml import etree html = etree.parse('./test.html',etree.HTMLParser()) ''' 其中test.html為上述的 <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> ''' result= html.xpath('//li/a/@href') print(result)返回:
注意:此處和屬性匹配的方法不同,屬性匹配是中括號加屬性名和值來限定某個屬性,如[@href="linkl.html ”],而此處的@href 指的是獲取節點的某 個屬性,二者需要做好區分11.屬性多值匹配
有時候,某些節點的某個屬性可能有多個值,例如: 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) ????????這樣通過 contains()?方法,第一個參數傳入屬性?名稱,第二個參數傳入屬性值,只要此屬性包含所傳入的屬性值,就可以完成匹配了,運行結果如下:?注:此種方式在某個節點的某個屬性有多個值時經常用到,如某個節點的 class 屬性通常有多個。
12. 多屬性匹配
????????我們可能還遇到一種情況,那就是根據多個屬性確定一個節點,這時就需要同時匹配多個屬性。此時可以使用運算符 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) ????????這里的 li 節點又增加了1個屬性 name 要確定這個節點 需要同時根據 clas和name 屬性來選擇,一個條件是 class 屬性里面包含 li 字符串,另一個條件是 name 屬性為 item 字符串,二者需要同時滿足,需要用 and 操作符相連,相連之后置于中括號內進行條件篩選 運行結果如下: xpath運算符有哪些呢?
13.按序選擇
????????有時候,我們在選擇的時候某些屬性可能同時匹配了多個節點,但是只想要其中的某個節點,如第二個節點或者最后一個節點,這時該怎么辦呢? ????????這時可以利用中括號傳入索引的方法獲取特定次序的節點,示例如下: 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) #序號是以1開頭的,不是以0開頭 result= html.xpath('//li[1]/a/text()') #第一個 li 節點 print(result) result= html.xpath('//li[last()]/a/text()') #最后一個 li 節點 print(result) result= html.xpath('//li[position()<3]/a/text()') #選取位置小于3的 li 節點,也就是1和2的節點 print(result) result= html.xpath('//li[last()-2]/a/text()') #選取倒數第三個的節點,last()是最后一個,所有last()-2就是倒數第三個 print(result)運行結果為:
14. 節點軸選擇
????????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) #序號是以1開頭的,不是以0開頭 result= html.xpath('//li[1]/ancestor::*') '''第一次選擇時,調用了ancestor軸,可以獲取所有祖先節點 其后需要跟兩個冒號,然后是節點的選擇器,這里我們直接使用*,表示匹配所有節點,因此返回結果是第一個 li 節點的所有祖先節點,包括 html、body、div、ul''' print(result) result= html.xpath('//li[1]/ancestor::div') '''第二次選擇時,又加了限定條件,這次在冒號后面加了 div ,這樣得到的結果就只有 div這個祖先節點了''' print(result) result= html.xpath('//li[1]/attribute::*') '''第三次選擇時,調用了 attribute 軸,可以獲取所有屬性值,其后跟的選擇器還是*,這代獲取節點的所有屬性,返回值就是 li 節點的所有屬性值''' print(result) result= html.xpath('//li[1]/child::a[@href="link1.html"]') '''第四次選擇時,調用了 child 軸,可以獲取所有直接子節點 這里又加了限定條件,選 href 屬性為 linkl.html的a節點''' print(result) result= html.xpath('//li[1]/descendant::span') '''第五次選擇時,調用了 descendant 軸,可以獲取所有子孫節點 這里又加了限定條件獲取 span 節點,所以返回的結果只包含 span 節點而不包含a節點''' print(result) result= html.xpath('//li[1]/following::*[2]') '''第六次選擇時,調用 following 軸,可以獲取當前節點之后的所有節點 這里雖然使用的是*匹配,但又加了索引選擇,所以只獲取了第二個后續節點''' print(result) result= html.xpath('//li[1]/following-sibling::*') '''第七次選擇時,調用 fo llowing -s ibling ,可以獲取當前節點之后的所有同級節點,使用*匹配,所以獲取了所有后續同級節點''' print(result)運行結果:
結語
? ? ? ? 到這里,我們基本把會用到的xpath選擇器介紹完了。xpath功能非常強大,內置函數非常多,熟練使用后會大大提升HTML信息的提取效率。
如果想查詢更多 XPath 的用法,可以查看:XPath 教程 如果想查詢更多 .Pyt on xml 庫的用法,可以查看:lxml - Processing XML and HTML with Python總結
- 上一篇: leetcode combination
- 下一篇: MATLAB使用教程(三)——在文件中编