初入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網路爬蟲開發實戰 ,崔慶才著 】
內容有所改動