1. 程式人生 > 實用技巧 >初入xpath

初入xpath

寫在前面

在獲取到伺服器響應(HTML原始碼)之後,我們可以通過正則來提取想要的資訊,但是編寫正則太過麻煩,也容易出錯。然而強大的python有強大的解析庫,可以供苦逼的碼農食用,例如lxml, beautiful Soup. pyquery等,通過這些解析庫,就可以根據網頁的ID ,class等屬性或者節點之間的層次關係來獲取想要的資料。

這篇水文,寫的是通過lxml庫來實現用XPath來提取需要的資訊。

#windows平臺下安裝
pip3 install lxml

XPath,全稱XML path Language,即XML路徑語言,它是一門在XML文件中查詢資訊的語言,但是它同樣適用於HTML文件的查詢資訊。

1.初始化xpath物件

想要用XPath在HTML原始碼中提取想要的資訊,需要用etree模組對HTML原始碼進行初始化構造XPath物件。

情景1 —— 對HTML字串進行初始化

html = etree.HTML(text)

註釋

etree.HTML():構造了一個XPath解析物件並對HTML文字進行自動修正。

demo

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>
'''
#初始化一個xpath物件
html = etree.HTML(text)
#利用etree.tostring()方法進行轉換稱為字串進行輸出,不過卻是tytes型別的,用decode轉成str型別。
result = etree.tostring(html).decode('utf-8')
print(result)
""" 
輸出 
<html><body><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>
     </li></ul>
 </div>
</body></html>
"""

情景2 —— 對HTML文件進行格式化

html = etree.parse('./text.html_path',etree.HTMLParser())

2.xpath常用規則

表示式 描述
nodename 選取此節點的所有子節點
/ 選取當前節點的所有子節點
// 選取當前節點的所有子孫節點
. 選取當前節點
.. 選取當前節點的父節點
@ 選取屬性
[@attrib_name='value‘] 選取具有屬性attrib_name屬性並且值等於value屬性的元素

3.XPath篩選資料基礎

result = html.xpath('匹配規則')

註釋

所有滿足要求的元素物件以列表的形式返回。

(1)獲取所有節點

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

demo

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)
element = html.xpath("//*")  #單雙引號都可以
print(len(element)) #輸出14
print(element) #以列表的形式輸出所有滿足要求的元素

""" [<Element html at 0x2ab2b37c1c0>, <Element body at 0x2ab2b6a9b40>,
 <Element div at 0x2ab2b6a9bc0>, <Element ul at 0x2ab2b6a9c80>,
  <Element li at 0x2ab2b6a9cc0>, <Element a at 0x2ab2b6a9d40>,
   <Element li at 0x2ab2b6a9d80>, <Element a at 0x2ab2b6a9dc0>,
    <Element li at 0x2ab2b6a9e00>, <Element a at 0x2ab2b6a9d00>,
 <Element li at 0x2ab2b6a9e40>, <Element a at 0x2ab2b6a9e80>, 
 <Element li at 0x2ab2b6a9ec0>, <Element a at 0x2ab2b6a9f00>] 
 """

(2)獲取指定元素

指定元素名來獲取

result = html.xpath('//element_name')

demo —— 獲取所有的li元素

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)
element = html.xpath("//li")  #單雙引號都可以
print(len(element))   #s輸出 5
print(element)
"""
輸出

 [<Element li at 0x1d9476a9a40>,
 <Element li at 0x1d9476a9ac0>, 
 <Element li at 0x1d9476a9b80>, 
 <Element li at 0x1d9476a9bc0>,
<Element li at 0x1d9476a9c00>] 
  """

(3)獲取子節點

通過/或著//即可查詢元素的子節點或子孫節點。

demo —— 獲取a節點

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)

#方法一 : 通過子節點的方式
element = html.xpath("//li/a")  #單雙引號都可以
print(len(element)) 
print(element)

#方法二: 通過孫節點的方式
element = html.xpath("//ul//a")  #單雙引號都可以
print(len(element))  
print(element)

""" 
都輸出
5
[<Element a at 0x16ee8cb9b00>, 
<Element a at 0x16ee8cb9b80>,
 <Element a at 0x16ee8cb9c40>, 
 <Element a at 0x16ee8cb9c80>, 
 <Element a at 0x16ee8cb9cc0>]
 """

(4)屬性匹配

在選取資訊的時候可以用@符號和中括號[ ]進行屬性的過濾。

demo —— 選取class屬性等於item-inactive的li

​```
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)

#方法一 : 通過子節點的方式
element = html.xpath("//li[@class='item-inactive']")  #單雙引號都可以
print(len(element)) 
print(element)
""" 
輸出
1
[<Element li at 0x211c9da9b00>]
"""

(5)獲取文字

用XPath中的text()方法獲取節點中的文字。

demo

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)
element = html.xpath("//li[@class='item-inactive']/a/text()")  
print(element)
""" 
輸出
['third item']
 """

用XPath中text()方法來獲取文字,可能會包括一些不需要的其他文字,比如換行符(\n)

(6)獲取屬性

前面提到了,用關鍵字@來進行class屬性篩選,這裡屬性的獲取也是用關鍵字@。

demo

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)
element = html.xpath("//li/a/@href")  
print(element)
""" 
輸出
['link1.html', 'link2.html', 'link3.html', 'link4.html', 'link5.html']
 """

屬性獲取和屬性篩選的對比

名稱 描述
屬性獲取 屬性獲取直接是@加屬性名稱
屬性篩選 屬性篩選用中括號來限制怕[@class='value']

4.XPath篩選資料進階

(1)屬性多值匹配

有時候,某些節點某個屬性可能會有多個值例如

<li class="li li-first"><a href="link.html">first item</a></li>

如果用之前篩選屬性的方法來篩選的話會解析不到該節點。

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[@class='li']//text()")
print(result)#輸出 []

這時候就要用到contains(@屬性名稱,值)函式,程式碼改寫如下:

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')]//text()")
print(result)#輸出 [['first item']]

等效於

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[@class="li li-first"]//text()')
print(result)#輸出 [['first item']]

(2)多屬性的匹配

有時候還會遇到多個屬性確定一個節點的情況,例如

<!-- 同時具有class屬性和name屬性 -->
<li class="li li-first" name="item"><a href="link.html">first item</a></li>

這個時候就要用到運算子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"]//text()')
print(result)#輸出 ['first item']

其他XPath運算子

運算子 描述
or
amd
mod 取餘
| 交集
+ 數值加法
- 數值減法
* 乘法
div 除法
= 等於
!= 不等於
< 小於
> 大於
>= 大於等於

(3)按序選擇

有時候,我們在選擇的時候某些屬性可能同時匹配了多個節點,但是隻想要其中的某個節點,如第二個節點或者最後一個節點,可以利用中括號傳入索引的方法獲取特定次序的節點。

from lxml import etree
text = text = '''
<div>
    <ul>
         <li class="item-0"><a href="link1.html">one</a></li>
         <li class="item-1"><a href="link2.html">two</a></li>
         <li class="item-inactive"><a href="link3.html">three</a></li>
         <li class="item-1"><a href="link4.html"> four</a></li>
         <li class="item-0"><a href="link5.html">five</a>
     </ul>
 </div>
'''
html = etree.HTML(text)
#返回第一個li裡的a的文字
one = html.xpath('//li[1]//a/text()')
print(one) #輸出 ['one']
#選取前三個
two=html.xpath('//li[position()<=3]/a/text()')
print(two) #輸出 ['one', 'two', 'three']
#選取最後一個
last = html.xpath('//li[last()]/a/text()')
print(last) #輸出['five']
#選取倒數第二個
four = html.xpath('//li[last()-1]/a/text()')
print(four) #輸出[' four']

括號裡面的數字是從1開始的。

(4)節點軸選擇

XPath提供了很多節點軸選擇方法,包括獲取子元素,兄弟元素,父元素,祖先元素等。

from lxml import etree
text = text = '''
<div>
    <ul>
         <li class="item-0" name='one'><a href="link1.html">one</a></li>
         <li class="item-1"><a href="link2.html">two</a></li>
         <li class="item-inactive"><a href="link3.html">three</a></li>
         <li class="item-1"><a href="link4.html"> four</a></li>
         <li class="item-0"><a href="link5.html">five</a>
     </ul>
 </div>
'''
html = etree.HTML(text)
#選取第一個li的所有祖先節點
result = html.xpath('//li[1]/ancestor::*')
print(result) 
'''
[<Element html at 0x28c1a0dd140>,
 <Element body at 0x28c1a3e99c0>, 
 <Element div at 0x28c1a3e9a80>, 
 <Element ul at 0x28c1a3e9ac0>]
'''
#選取第一個li的div祖先
result = html.xpath('//li[1]/ancestor::div')
print(result) #輸出 [<Element div at 0x2e75ee89bc0>]

#選取當前節點的所有屬性值
result = html.xpath('//li[1]/attribute::*')
print(result) #輸出 ['item-0', 'one']
#獲取子孫節點中的a節點
result = html.xpath('//li[1]/child::a//text()')
print(result) #輸出 ['one']

更多軸用法

摘自【Python 3網路爬蟲開發實戰 ,崔慶才著 】

內容有所改動