1. 程式人生 > >【Python3 爬蟲學習筆記】解析庫的使用 2 —— 使用XPath 2

【Python3 爬蟲學習筆記】解析庫的使用 2 —— 使用XPath 2

8. 文字獲取

我們使用XPath中的text()方法獲取節點中文字,接下來嘗試獲取前面li節點中的文字,相關程式碼如下:

from lxml import etree

html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li[@class="item-0"]/test()')
print(result)

執行結果如下:

['\r\n']

從結果我們可以看到,沒有獲得任何文字,只獲得了\r\n,主要是因為在XPath中text()前面是/,而此處/的含義是選取直接子節點,很明顯li的直接子節點都是a節點,文字都是在a節點內部的,所以這裡匹配到的結果就是被修正的li節點內部的換行符,因為自動修正的li節點的尾標籤換行了。
即選中的是這兩個節點:

<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節點的尾標籤新增的時候換行了,所以提取文字得到的唯一結果就是li節點的尾標籤和a節點的尾標籤之間的換行符。
因此,如果想獲取li節點內部的文字,就有兩種方式,一種是先選取a節點再獲取文字,另一種就是使用//。
首先,選取到a節點再獲取文字,程式碼如下:

from lxml import etree

html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li[@class="item-0"]/a/text()')
print(result)

執行結果如下:

['first item', 'fifth item']

可以看到,這裡的返回值是兩個,內容都是屬性為item-0的li節點的文字。
這裡我們是逐層選取的,先選取了li節點,又利用/選取了其直接子節點a,然後再選取其文字。
另一種方式程式碼如下:

from lxml import etree

html =
etree.parse('./test.html', etree.HTMLParser()) result = html.xpath('//li[@class="item-0"]//text()') print(result)

執行結果如下:

['first item', 'fifth item', '\r\n']

可以看到,這裡選取所有子節點的文字,其中前兩個就是li的子節點a節點內部的文字,另外一個就是最後一個li節點內部的文字,即換行符。
所以說,如果想要獲取子孫節點內部的所有文字,可以直接用//加text()的方式,這樣可以保證獲取到最全面的文字資訊,但是可能會夾雜一些換行符等特殊字元。如果想獲取某些特定子孫節點下的所有文字,可以先選取到特定的子孫節點,然後再呼叫text()方法獲取其內部文字,這樣可以保證獲取的結果是整潔的。

9. 屬性獲取

可以使用@符號進行屬性獲取。

from lxml import etree

html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li/a/@href')
print(result)

我們通過@href即可獲取節點的href屬性。注意,此處和屬性匹配的方法不同,屬性匹配是中括號加屬性名和值來限定某個屬性,如[@href=“link1.html”],而此處的@href指的是獲取節點的某個屬性,二者需要做好區別。

['link1.html', 'link2.html', 'link3.html', 'link4.html', 'link5.html']

可以看到,我們成功獲取了所有li節點下a節點的href屬性,它們以列表形式返回。

10. 屬性多值匹配

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

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"]/a/text()')
print(result)

這裡HTML文字中li節點的class屬性有兩個值li和li-first,此時如果還想用之前的屬性匹配獲取,就無法匹配了,此時的執行結果如下:

[]

這時就需要用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")]/a/text()')
print(result)

這樣通過contains()方法,第一個引數傳入屬性名稱,第二個引數傳入屬性值,只要此屬性包含所傳入的屬性值,就可以完成匹配了。
執行結果如下:

['first item']

此種方式在某個節點的某個屬性有多個值時經常用到,如某個節點的class屬性通常有多個。

11. 多屬性匹配

另外,我們可能還會遇到一種情況,那就是根據多個屬性確定一個節點,這時就需要同時匹配多個屬性。此時可以使用運算子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節點又增加了一個屬性name。要確定這個節點,需要同時根據class和name屬性來選擇,一個條件是class屬性裡面包含li字串,另一個條件是name屬性為item字串,二者需要同時滿足,需要用and操作符相連,相連之後置於中括號內進行條件篩選。執行結果如下:

['first item']

這裡的and其實是XPath中的運算子。另外,還有很多運算子,如or、mod等,運算子如下表:

運算子 描述 例項 返回值
or age=19 or age=20 如果age是19,則返回true。如果age是21,則返回false
and age>19 and age<21 如果age是20,則返回true。如果age是18,則返回false
mod 計算除法的餘數 5 mod 2 1
+ 加法 6 + 4 10
- 減法 6 - 4 2
* 乘法 6 * 4 24
div 除法 8 div 4 2
= 等於 age=19 如果age是19,則返回true。如果age是20,則返回false
!= 不等於 age!=19 如果age是18,則返回true。如果age是19,則返回false
< 小於 age<19 如果age是18,則返回true。如果age是19,則返回false
<= 小於或等於 age<=19 如果age是19,則返回true。如果age是20,則返回false
> 大於 age>19 如果age是20,則返回true。如果age是19,則返回false
>= 大於或等於 age>=19 如果age是19,則返回true。如果age是18,則返回false

12. 按序選擇

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

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 = html.xpath('//li[1]/a/text()')
print(result)
result = html.xpath('//li[last()]/a/text()')
print(result)
result = html.xpath('//li[position()<3]/a/text()')
print(result)
result = html.xpath('//li[last()-2]/a/text()')
print(result)

第一次選擇時,我們選取了第一個li節點,中括號傳入數字1即可。注意,這裡和程式碼中不同,序號是以1開頭的,不是以0開頭。
第二次選擇時,我們選取了最後一個li節點,中括號中傳入last()即可,返回的便是最後一個li節點。
第三次選擇時,我們選取了位置小於3的li節點,也就是位置序號為1和2的節點,得到的結果就是前兩個li節點。
第四次選擇時,我們選取了倒數第三個li節點,中括號中傳入last()-2即可。因為last()是最後一個,所以last(0-2就是倒數第三個。
執行結果如下:

['first item']
['fifth item']
['first item', 'second item']
['third item']

這裡我們使用了last()、position()等函式。在XPath中,提供了100多個函式,包括存取、數值、字串、邏輯、節點、序列等處理功能,它們的具體作用可以參考:http://www.w3school.com.cn/xpath/xpath_functions.asp。

15. 節點軸選擇

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)
result = html.xpath('//li[1]/ancestor::*')
print(result)
result = html.xpath('//li[1]/ancestor::div')
print(result)
result = html.xpath('//li[1]/attribute::*')
print(result)
result = html.xpath('//li[1]/child::a[@href="link1.html"]')
print(result)
result = html.xpath('//li[1]/descendant::span')
print(result)
result = html.xpath('//li[1]/following::*[2]')
print(result)
result = html.xpath('//li[1]/following-sibling::*')
print(result)

執行結果如下:

[<Element html at 0x287aaf8e688>, <Element body at 0x287aaf8e4c8>, <Element div at 0x287aaf8e388>, <Element ul at 0x287aaf8e3c8>]
[<Element div at 0x287aaf8e388>]
['item-0']
[<Element a at 0x287aaf8e3c8>]
[<Element span at 0x287aaf8e388>]
[<Element a at 0x287aaf8e3c8>]
[<Element li at 0x287aaf8e4c8>, <Element li at 0x287aaf8e288>, <Element li at 0x287aaf8e208>, <Element li at 0x287aaf8e708>]

第一次選擇時,我們呼叫了ancestor軸,可以獲取所有祖先節點。其後需要跟兩個冒號,然後是節點的選擇器,這裡我們直接使用*,表示匹配所有節點,因此返回結果是第一個li節點的所有祖先節點,包括html、body、div和ul。
第二次選擇時,我們又加了限定條件,這次在冒號後面加了div,這樣得到的結果就只有div這個祖先節點了。
第三次選擇時,我們呼叫了attribute軸,可以獲取所有屬性值,其後跟的選擇器還是(星號),這代表獲取節點的所有屬性,返回值就是li節點的所有屬性值。
第四次選擇時,我們呼叫child軸,可以獲取所有直接子節點。這裡我們又加了限定條件,選取href屬性為link1.html的a節點。
第五次選擇時,我們呼叫了descendant軸,可以獲取所有子孫節點。這裡我們又加了限定條件獲取span節點,所以返回的結果只包含span節點而不包含a節點。
第六次選擇時,我們呼叫了following軸,可以獲取當前節點之後的所有節點。這裡我們雖然使用的是(星號)匹配,但又加了索引選擇,所以只獲取了第二個後續節點。
第七次選擇時,我們呼叫了following-sibing軸,可以獲取當前節點之後的所有同級節點。這裡我們使用(星號)匹配,所以獲取了所有後續同級節點。