1. 程式人生 > >小白學 Python 爬蟲(20):Xpath 進階

小白學 Python 爬蟲(20):Xpath 進階

人生苦短,我用 Python

前文傳送門:

小白學 Python 爬蟲(1):開篇

小白學 Python 爬蟲(2):前置準備(一)基本類庫的安裝

小白學 Python 爬蟲(3):前置準備(二)Linux基礎入門

小白學 Python 爬蟲(4):前置準備(三)Docker基礎入門

小白學 Python 爬蟲(5):前置準備(四)資料庫基礎

小白學 Python 爬蟲(6):前置準備(五)爬蟲框架的安裝

小白學 Python 爬蟲(7):HTTP 基礎

小白學 Python 爬蟲(8):網頁基礎

小白學 Python 爬蟲(9):爬蟲基礎

小白學 Python 爬蟲(10):Session 和 Cookies

小白學 Python 爬蟲(11):urllib 基礎使用(一)

小白學 Python 爬蟲(12):urllib 基礎使用(二)

小白學 Python 爬蟲(13):urllib 基礎使用(三)

小白學 Python 爬蟲(14):urllib 基礎使用(四)

小白學 Python 爬蟲(15):urllib 基礎使用(五)

小白學 Python 爬蟲(16):urllib 實戰之爬取妹子圖

小白學 Python 爬蟲(17):Requests 基礎使用

小白學 Python 爬蟲(18):Requests 進階操作

小白學 Python 爬蟲(19):Xpath 基操

引言

文接上篇,我們接著聊,上篇我們介紹了 Xpath 一些常用的匹配方式, DOM 節點我們可以匹配出來了,這並不是我們的最終目的,我們是要從這些節點中取出來我們想要的資料。本篇我們接著介紹如何使用 Xpath 獲取資料。

文字獲取

我們先嚐試下獲取第一篇文章的題目,獲取節點中的文字我們可以使用 text() 來進行獲取,如圖:

程式碼如下:

from lxml import etree
import requests

response = requests.get('https://www.geekdigging.com/')
html_str = response.content.decode('UTF-8')
html = etree.HTML(html_str)

result_1 = html.xpath('/html/body/section/div/div/main/article[1]/div[2]/div/h3/a/text()')
print(result_1)

結果如下:

['小白學 Python 爬蟲(18):Requests 進階操作']

哇,上面示例裡面的表示式好長啊,這個怎麼寫出來的,怎麼寫的稍後再說,先介紹一下這個表示式的意思,仔細看一下,這個表示式其實是從整個 HTML 原始碼的最外層的 <html> 標籤寫起,一層一層的定位到了我們所需要的節點,然後再使用 text() 方法獲取了其中的文字內容。

關於這個表示式怎麼來的,肯定不是小編寫的,這麼寫講實話是有點傻,完全沒必要從整個文件的最外層開始寫。

其實這個是 Chrome 幫我們生成的,具體操作可見下圖:

這時 Chrome 會自動幫我們把這個節點的表示式 copy 到當前的剪下板上,只需要我們在程式裡 ctrl + v 一下。

屬性獲取

有些情況下,我們可能不止需要節點中的文字資料,可能還會需要節點中的屬性資料,比如上面的示例,我們除了想知道文章標題,其實還想知道文章的跳轉路徑:

result_2 = html.xpath('/html/body/section/div/div/main/article[1]/div[2]/div/h3/a/@href')
print(result_2)

結果如下:

['/2019/12/11/1468953802/']

這裡需要注意的是,此處和屬性匹配的方法不同,屬性匹配是中括號加屬性名和值來限定某個屬性,如 [@class="container"] ,而此處的 @href 指的是獲取節點的某個屬性,二者需要做好區分。

屬性多值匹配

某些時候吧,某些節點的某個屬性可能有多個值,這個多見於 class 屬性,由於某些編碼習慣以及某些其他原因,這個屬性經常性會出現多個值,這時如果只使用其中的一個值的話,就無法匹配了。

如果這麼寫的話:

result_3 = html.xpath('//div[@class="post-head"]')
print(result_3)

結果如下:

[]

可以看到,這裡沒有匹配到任何節點,這時,我們可以使用一個函式:contains() ,上面的示例可以改成這樣:

result_3 = html.xpath('//div[contains(@class, "post-head")]')
print(result_3)

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

多屬性匹配

除了上面的一個屬性有多個值的情況,還經常會出現需要使用多個屬性才能確定一個唯一的節點。

這時,我們可以使用運算子來進行處理。

還是這個示例,我們獲取 <img> 這個節點,如果只是使用 class 屬性來進行獲取,會獲得很多個節點:

result_4 = html.xpath('//img[@class="img-ajax"]')
print(result_4)

結果如下:

[<Element img at 0x1984505a788>, <Element img at 0x1984505a7c8>, <Element img at 0x1984505a808>, <Element img at 0x1984505a848>, <Element img at 0x1984505a888>, <Element img at 0x1984505a908>, <Element img at 0x1984505a948>, <Element img at 0x1984505a988>, <Element img at 0x1984505a9c8>, <Element img at 0x1984505a8c8>, <Element img at 0x1984505aa08>, <Element img at 0x1984505aa48>]

如果我們加上 alt 屬性一起進行匹配的話,就可以獲得唯一的節點:

result_4 = html.xpath('//img[@class="img-ajax" and @alt="小白學 Python 爬蟲(18):Requests 進階操作"]')
print(result_4)

結果如下:

[<Element img at 0x299905e6a48>]

Xpath 支援很多的運算子,詳細見下表(來源:https://www.w3school.com.cn/xpath/xpath_operators.asp)

運算子 描述 例項 返回值
| 計算兩個節點集 //book | //cd 返回所有擁有 book 和 cd 元素的節點集
+ 加法 6 + 4 10
- 減法 6 - 4 2
* 乘法 6 * 4 24
div 除法 8 div 4 2
= 等於 price=9.80 如果 price 是 9.80,則返回 true。如果 price 是 9.90,則返回 false。
!= 不等於 price!=9.80 如果 price 是 9.90,則返回 true。如果 price 是 9.80,則返回 false。
< 小於 price<9.80 如果 price 是 9.00,則返回 true。如果 price 是 9.90,則返回 false。
<= 小於或等於 price<=9.80 如果 price 是 9.00,則返回 true。如果 price 是 9.90,則返回 false。
> 大於 price>9.80 如果 price 是 9.90,則返回 true。如果 price 是 9.80,則返回 false。
>= 大於或等於 price>=9.80 如果 price 是 9.90,則返回 true。如果 price 是 9.70,則返回 false。
or price=9.80 or price=9.70 如果 price 是 9.80,則返回 true。如果 price 是 9.50,則返回 false。
and price>9.00 and price<9.90 如果 price 是 9.80,則返回 true。如果 price 是 8.50,則返回 false。
mod 計算除法的餘數 5 mod 2 1

按順序選擇

有些時候,我們匹配出來很多的節點,但是我們只想獲取其中的某一個節點,比如第一個或者最後一個,這時可以使用中括號傳入索引的方法獲取特定次序的節點。

我們還是文章的題目為例,我們先獲取所有的文章題目,再進行選擇,示例程式碼如下:

result_5 = html.xpath('//article/div/div/h3[@class="post-title"]/a/text()')
print(result_5)
result_6 = html.xpath('//article[1]/div/div/h3[@class="post-title"]/a/text()')
print(result_6)
result_7 = html.xpath('//article[last()]/div/div/h3[@class="post-title"]/a/text()')
print(result_7)
result_8 = html.xpath('//article[position() < 5]/div/div/h3[@class="post-title"]/a/text()')
print(result_8)

結果如下:

['小白學 Python 爬蟲(18):Requests 進階操作', '小白學 Python 爬蟲(17):Requests 基礎使用', '小白學 Python 爬蟲(16):urllib 實戰之爬取妹子圖', '如何用 Python 寫一個簡易的抽獎程式', '小白學 Python 爬蟲(15):urllib 基礎使用(五)', '我們真的在被 APP “竊聽” 麼?', '小白學 Python 爬蟲(14):urllib 基礎使用(四)', '小白學 Python 爬蟲(13):urllib 基礎使用(三)', '小白學 Python 爬蟲(12):urllib 基礎使用(二)', '小白學 Python 爬蟲(11):urllib 基礎使用(一)', '老司機大型車禍現場', '小白學 Python 爬蟲(10):Session 和 Cookies']
['小白學 Python 爬蟲(18):Requests 進階操作']
['小白學 Python 爬蟲(10):Session 和 Cookies']
['小白學 Python 爬蟲(18):Requests 進階操作', '小白學 Python 爬蟲(17):Requests 基礎使用', '小白學 Python 爬蟲(16):urllib 實戰之爬取妹子圖', '如何用 Python 寫一個簡易的抽獎程式']

第一次,我們選取了當前頁面所有的文章的題目。

第二次,我們選擇了當前頁面第一篇文章的題目,這裡注意下,中括號中傳入數字1即可,這裡的開始是以 1 為第一個的,不是程式中的 0 為第一個。

第三次,我們使用 last() 函式,獲取了最後一篇文章的題目。

第四次,我們選擇了位置小於 5 的文章題目。

節點軸

軸可定義相對於當前節點的節點集。

軸名稱 結果
ancestor 選取當前節點的所有先輩(父、祖父等)。
ancestor-or-self 選取當前節點的所有先輩(父、祖父等)以及當前節點本身。
attribute 選取當前節點的所有屬性。
child 選取當前節點的所有子元素。
descendant 選取當前節點的所有後代元素(子、孫等)。
descendant-or-self 選取當前節點的所有後代元素(子、孫等)以及當前節點本身。
following 選取文件中當前節點的結束標籤之後的所有節點。
namespace 選取當前節點的所有名稱空間節點。
parent 選取當前節點的父節點。
preceding 選取文件中當前節點的開始標籤之前的所有節點。
preceding-sibling 選取當前節點之前的所有同級節點。
self 選取當前節點。

我們以 ancestor 軸來做示例:

# 節點軸示例
# 獲取所有祖先節點
result_9 = html.xpath('//article/ancestor::*')
print(result_9)
# 獲取祖先節點 main 節點
result_10 = html.xpath('//article/ancestor::main')
print(result_10)

結果如下:

[<Element html at 0x2a266171208>, <Element body at 0x2a266171248>, <Element section at 0x2a266171288>, <Element div at 0x2a2661712c8>, <Element div at 0x2a266171308>, <Element main at 0x2a266171388>]
[<Element main at 0x2a266171388>]

關於節點軸就先介紹到這裡,更多的軸的用法可以參考:https://www.w3school.com.cn/xpath/xpath_axes.asp 。

示例程式碼

本系列的所有程式碼小編都會放在程式碼管理倉庫 Github 和 Gitee 上,方便大家取用。

示例程式碼-Github

示例程式碼-Gi