XPath,全稱XML Path Language,即XML路徑語言,它是一門在XML文檔中查找信息的語言,它最初是用來搜尋XML文檔的,但是它同樣適用於HTML文檔的搜索。
一、XPATH定位---常用歸納
定位 | 說明 |
//ul/* | ul的所有子元素 |
//input[2] | 第2個input元素 |
//input[last()] | 最后一個input元素 |
//input[last()-1] | 倒數第二個input元素 |
input[position()<3] | 前兩個input元素 |
//input[@value] | 有value屬性的input元素 |
//input[@name='passwd' and @pwd='123456'] | 屬性name的值等於passwd並且屬性pwd的值等於123456的input元素 |
//ul/*[5] | ul的第五個元素 |
//*[text()='heading'] | 任意包含heading文本的元素 |
//input[@*='123456'] | 任意屬性的值等於123456的input元素 |
//a/@href | 獲取a標簽的href屬性的值 |
二、常用的五種方法(相對路徑)
①使用標簽名+節點屬性定位
1 語法://標簽名[@屬性名=屬性值] 2 find_element_by_xpath("//input[@id='kw']") # @后跟屬性,可以是任何屬性
②組合元素索引(下標)定位
1 find_element_by_xpath("//*[@id='J_login_form']/*/*/input[2]")
③通過部分屬性值匹配
語法://標簽名[contains(@屬性名,部分屬性值)] 語法://標簽名[starts-with(@屬性名,部分屬性值)] 語法://標簽名[ends-with(@屬性名,部分屬性值)] a. starts-with 例子://input[starts-with(@id, 'ctrl')] 解析:匹配以ctrl開始的屬性值
b.ends-with 例子://input[ends-with(@id, '_userName')] 解析:匹配以userName結尾的屬性值
備注:
1、ends-with的屬性對應的值是否需要_開頭,目前不清楚
2、ends-with可能定位不到元素,原因:ends-with方法是xpath2.0的語法,而瀏覽器只支持xpath1.0。
可能的報錯:SyntaxError: Failed to execute ‘evaluate’ on ‘Document’: The string ‘//input[ends-with(@id,‘w’)]’ is not a valid XPath expression.(Session info: chrome=94.0.4606.81)
c.contains 例子://input[contains(@id, 'uaserName')]
解析:匹配含有userName結尾的屬性值
④、使用文本內容匹配
1 語法:文本全部匹配://標簽名[text()=文本內容] 2 語法:文本部分匹配-包含://標簽名[contains(text(), 部分文本內容)] 3 find_element_by_xpath("//a[text()='退出’]") #文本全部匹配 4 find_element_by_xpath("//a[contains(text(), '出’)]") # 文本部分匹配
⑤、使用軸定位表達式
xpath軸可定義某個相對於當前節點的節點集:
- child: 選取當前節點的所有子元素
- parent: 選取當前節點的父節點
- descendant: 選取當前節點的所有后代元素(子、孫等)
- ancestor: 選取當前節點的所有先輩(父、祖父等)
- descendant-or-self: 選取當前節點的所有后代元素(子、孫等)以及當前節點本身
- ancestor-or-self: 選取當前節點的所有先輩(父、祖父等)以及當前節點本身
- preceding-sibling: 選取當前節點k開始標簽之前的所有同級節點
- following-sibling: 選取當前節點結束標簽之后的所有同級節點
- preceding: 選取文檔中當前節點的開始標簽之前的所有節點
- following: 選取文檔中當前節點的結束標簽之后的所有節點
- self: 選取當前節點
- attribute: 選取當前節點的所有屬性
- namespace: 選取當前節點的所有命名空間節點
1>descendant表示取當前節點的所有后代元素
2>following表示選取當前節點結束標簽之后的所有節點
三、XPATH定位驗證
1、驗證Xpath定位元素是否正確,可以在Google Chrome的elements或console中進行驗證
- 在需要定位的頁面,按F12后,切換至elements列下,按下Ctrl+f鍵,輸入xpath表達式
- 在需要定位的頁面,按F12后,切換至console列下,輸入表達式。語法是:$x("your_xpath_selector")
1> 表達式正確,元素定位正確時,會查找出該元素,如下圖
2>未定位准確,找不到該元素,查找結果為空,如圖:
3>表達式不正確,無法正常識別情況,可能會有很多種錯誤,列舉一個例子,如圖:
原因:語法中括號里需要通過雙引號括起來,如果xpath語句中有雙引號,要改成單引號,不然只能解析到第一對雙引號的內容。這也是需要特別注意的一點。
四、XPATH實例
1、示例一
現在要引用id為“J_password”的input元素,可以像下面這樣寫:
find_element_by_xpath("//*[@id='J_login_form']/dl/dt/input[@id='J_password']")
當然我們也可以用*號省略具體的標簽名稱,但元素的層級關系必須體現出來:
find_element_by_xpath("//*[@id='J_login_form']/*/*/input[@id='J_password']")
2、示例二:模糊定位
這段代碼中的“退出”這個超鏈接,沒有標准id元素,只有一個rel和href,不是很好定位。用xpath的幾種模糊匹配模式來定位它:
a.用contains關鍵字:
find_element_by_xpath(“//a[contains(@href, ‘logout’)]”)
這句話的意思是尋找頁面中href屬性值包含有logout這個單詞的所有a元素,由於這個退出按鈕的href屬性里肯定會包含logout,所以這種方式是可行的,也會經常用到。其中@后面可以跟該元素任意的屬性名。
b.用Text關鍵字:
find_element_by_xpath(“//a[contains(text(), ’退出’)]")
c.用start-with:
find_element_by_xpath(“//a[starts-with(@rel, ‘nofo’)]")
尋找rel屬性以nofo開頭的a元素。其中@后面的rel可以替換成元素的任意其他屬性
3、示例三:@xxx 是選擇所有屬性為xxx的屬性
//@id 選擇所有的id屬性
//BBB[@id] 選擇有id屬性的BBB元素
//BBB[@*] 選擇有任意屬性的BBB元素
//BBB[not(@*)] 選擇沒有屬性的BBB元素
4、示例四:使用normalize-space函數去掉前后空格
要點擊“貨運表現”, 菜單中的所有項的id都是“vertab”,所以不能用id來定位,那么先用文本的xpath試試://a[text()='貨運表現'];
發現定位不了,因為文本“貨運表現”的前后有空格和換行,那么用包含文本的xpath試試://a[contains(text(), '貨運表現')];
但是這個菜單中還有個“非貨運表現”,文本也包含“貨運表現”,顯然不行;
再看這個寫法://a[contains(text(),'貨運表現') and not(contains(text(),'非貨運表現'))];
發現匹配到了多個,原來頁面其他地方還有符合這個條件的,前面說了,這個菜單里面的幾個項都有共同的id,那么加上id的條件試試://a[@id='vertab' and contains(text(),'貨運表現') and not(contains(text(),'非貨運表現'))],這個時候可以定位到;
有沒有更簡單的方法呢,每種語言中都有去除前后空格的方法,那xpath呢?
normalize-space這個方法就可以去除文本中的前后空格和回車,所以這樣寫://a[normalize-space(text())='貨運表現‘]
——————XPATH之normalize-space(.)和normalize-space(text())區別:
.是當前節點,如果在需要字符串的地方使用它,引擎會自動將節點轉換為節點的字符串值,對於元素來說,該元素是元素內連接的所有文本節點
text()僅選擇作為當前節點的直接子節點的文本節點
例如:給定XML:
<a>Foo
<b>Bar</b>
lish
</a>
假設<a>是當前節點,normalize-space(.)將返回Foo、Bar、lish,但normalize-space(text())會失敗,因為text()返回兩個文本節點(Foo和lish)的節點集,normalize-space()不接受。
長話短說,如果你想標准化一個元素中的所有文本,請使用.
。如果要選擇特定的文本節點,請使用text()
,但請始終記住盡管名稱不同,但會text()
返回一個節點集,如果節點集只有一個元素,它將自動轉換為字符串。
5、示例五:count()函數可以計數所選元素的個數
//*[count(BBB)=2] 選擇含有2個BBB子元素的元素
//*[count(*)=2] 選擇含有2個子元素的元素
6、示例六:name()函數返回元素的名稱
//*[name()='BBB'] 選擇所有名稱為BBB的元素(這里等價於//BBB)
start-with()函數在該函數的第一個參數字符串是以第二個參數字符開始的情況返回true,
//*[starts-with(name(),'B')] 選擇所有名稱以"B"起始的元素
contains()函數當其第一個字符串參數包含有第二個字符串參數時返回true。
//*[contains(name(),'C')] 選擇所有名稱包含"C"的元素
7、示例七:string-length()函數返回字符串的字符數
//*[string-length(name()) = 3] 選擇名字長度為3的元素
/*[string-length(name()) < 3] 選擇名字長度小於3的元素
//*[string-length(name()) > 3] 選擇名字長度大於3的元素
8、示例八:多個路徑可以用分隔符 | 合並在一起
——用|表示或者。不同於java中用||表示或者
——可以合並的路徑數目沒有限制
//CCC | //BBB 選擇所有的CCC和BBB元素
/AAA/EEE | //BBB 選擇所有的BBB元素和所有是AAA的子元素的EEE元素
五、xpath總結
1、xpath中 / 和 //的區別
如果路徑以斜線 / 開始, 那么該路徑就表示到一個元素的絕對路徑(/表示文檔里根下的那些節點)
如果路徑以雙斜線 // 開頭, 則表示選擇文檔中所有滿足雙斜線//之后規則的元素。即,//后的是匹配的通配符,返回所有滿足這個路徑條件的節點。(//表示文檔里的任何位置的節點)
例如:
//a//b/@abc 指的是文檔中所有a元素的屬性為abc的后代b元素(包括子代元素)(多級);
//a/b/@abc 指的是文檔中所有a元素的屬性為abc的子代b元素(一級);
/a/b/@abc 指的是根節點b元素的屬性為abc的子代b元素(一級)。
六、python3解析庫——lxml
lxml是python的一個解析庫,支持HTML和XML的解析,支持XPath解析方式,而且解析效率非常高。
1 from lxml import etree 2 3 text = ''' 4 <div> 5 <ul> 6 <li class="item-0" name="iii"><a href="link1.html">第一個</a></li> 7 <li class="item-1"><a href="link2.html">second item</a></li> 8 <li class="item-2"><a href="link5.html">a屬性</a> 9 <li class="aaa" name="item"><a href="link222.html">第二個</a></li> 10 <li class="item-8"><a href="link8.html">hhh888</a></li> 11 <li class="aaa" name="item"><a href="link333.html">第三個</a></li> 12 <li class="item-10"><a href="link00.html">hhh000</a></li> 13 <li class="aaa" name="item"><a href="link444.html">第四個</a></li> 14 <li class="item"><a href="link6.html">hhh</a></li> 15 </ul> 16 </div> 17 ''' 18 # html = etree.HTML(text) # 初始化生成一個xpath解析對象 19 # result = etree.tostring(html, encoding='utf-8') # 解析對象輸出代碼 20 # print(type(html)) 21 # print(type(result)) 22 # print(result) 23 # print(result.decode('utf-8')) 24 html = etree.HTML(text, etree.HTMLParser()) 25 26 # 屬性獲取 27 result0 = html.xpath("//li[@class='item-0']/a/@href") # 獲取li標簽的a標簽的href屬性值 28 result1 = html.xpath("//li[@class='item-0']//@href") # 獲取li標簽的所有子孫節點的href屬性值 29 print(result0, result1) 30 31 # 這里需要注意,[@class='aaa']和[last()]的位置,否則匹配結果存在差別,踩坑之一 32 result2 = html.xpath("//li[@class='aaa'][last()]/a/@href") # 獲取class屬性為aaa的最后一個li 33 result3 = html.xpath("//li[@class='aaa'][1]/a/@href") # 獲取class屬性為aaa的第一個li 34 result4 = html.xpath("//li[@class='aaa'][position()>1]/a/@href") # 獲取class屬性為aaa,且位置為第二個及之后的li 35 result5 = html.xpath("//li[@class='aaa'][last()-2]/a/@href") # 獲取class屬性為aaa的倒數第三個li 36 print(result2, result3, result4, result5) 37 38 # 節點軸選擇 39 result00 = html.xpath("//li[1]/ancestor::*") # 獲取所有祖先節點 40 result01 = html.xpath("//li[1]/ancestor::div") # 獲取所有祖先div 41 result02 = html.xpath("//li[1]/attribute::*") # 獲取所有屬性值 42 result03 = html.xpath("//li[1]/child::*") # 獲取所有直接子節點 43 result04 = html.xpath("//li[1]/descendant::a") # 獲取所有子孫節點的a節點 44 result05 = html.xpath("//li[1]/following::*") # 獲取當前節點之后的所有節點 45 result06 = html.xpath("//li[1]/following-sibling::*") # 獲取當前節點的所有同級節點 46 print(result00, result01, result02, result03, result04, result05, result06)
案例應用:抓取TIOBE指數前20名排行開發語言
腳本見gitee項目