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