Python3使用Xpath解析網易雲音樂歌手頁面
Xpath最初被設計用來搜尋XML文件,但它同樣適用於HTML文件的搜尋。通過簡潔明瞭的路徑選擇表示式,它提供了強大的選擇功能;同時得益於其內建的豐富的函式,它可以匹配和處理字串、數值、時間等資料格式,幾乎所有節點我們都可以通過Xpath來定位。
在Python中,lxml
庫為我們提供了完整的Xpath選擇器,今天我們就用它來學習Xpath的使用,我們的目標是用最少的時間來掌握使用頻率最高的核心技能,而這些核心技能基本上可以滿足我們網頁抓取的需求。
畢竟我們不是單獨在使用Xpath,在Python中,很多資料處理和匹配的工作我們可以用更加“Python”、更加通用的方法來解決,沒必要為了5%的使用而花費數倍的時間。
我們都知道,在很多領域裡,從0到80分只需要花費很少的時間,從80分到95分則可能會花費上一階段的數倍時間,至於從95分往上,每一分的提高都可能需要巨大的時間成本。我們需要權衡最初的學習訴求、收穫和時間成本的匹配度等,以判斷我們要到達哪一個水平,並規劃出對應的學習方案。
我學習爬蟲的目的並不是成為一個精通網路爬蟲的大師,而是將它作為一個工具,用來幫助我更好地進行資料探勘分析的工作。因此,在學習過程中會盡可能地功力,力求以最少的時間掌握最核心的技能。Xpath簡直是針對這種學習思路設計的,因為它太容易上手了,核心功能只需要十分鐘就可以熟練掌握,而那多達上百的函式對我們來說可能一輩子都用不到幾回。
歡迎大家關注我的個人部落格【數洞】 【備用站】
一、Xpath常用規則
下表是最常用的Xpath規則,絕大多數的Xpath表示式都由它們構成。
表示式 | 描述 |
---|---|
nodename |
選取此節點的所有子節點 |
/ |
從當前節點直接選取子節點 |
// |
從當前節點選取所有子孫節點 |
. |
選取當前節點 |
.. |
選取當前節點的父節點 |
@ |
選取屬性 |
二、抓取趙雷熱門作品頁面
單純的羅列簡直是耍流氓,實戰才是硬道理。正如標題所言,今天我們就使用Xpath來解析網易雲音樂的歌手頁面。我個人很喜歡趙雷,那我們就先嚐試解析一下趙雷的熱門作品。
網易雲音樂抓取難度較低,沒有亂七八糟的驗證,抓取的時候我們只需要帶上header
就可以成功獲取我們需要的內容了。
首先,我們開啟網易雲音樂的首頁,搜尋並進入趙雷的頁面。右鍵檢查並切換到Network選項卡,重新整理一下,就看到了一大串網路請求,我們要做的就是從中定位到歌曲列表所在的請求。
我們優先看document類的檔案,第一個開啟後通過preview可以看到這裡是通用內容,包含了一些網易雲音樂的資訊,那麼接下來我們看下邊這個紅框裡的請求,首先請求名稱裡包含了artist以及一個對應的id,看起來有點像。
接下來我們單擊進去看看:
我們成功看到了趙雷的熱門作品列表,說明我們找對了位置。我們同樣可以通過在response裡搜尋來確定這一請求是否是我們尋找的那一個。比如我們搜尋“成都”、“南方姑娘”等,來看下我們的歌曲列表是不是在這個response中。
確定了請求之後,我們就需要抓取並解析了。首先我們切換到Headers選項,在General下找到Request URL作為請求連線;然後在Request Headers下找到‘User-Agent’,並將其複製下來用作模擬瀏覽器發起請求。
接下來我們嘗試抓取頁面:
import requests
url = 'https://music.163.com/artist?id=6731'
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36'
}
html = requests.get(url, headers=headers)
print(html.status_code)
結果如下:
200
這說明我們的請求成功了,接下來我們看下html的內容是否符合要求:
print(html.text)
這裡列印的結果太長就不貼出來了,我們可以把列印的內容和剛才那個請求返回的結果做一個比對,看是不是一樣的內容。通過觀察,我們發現這就是我們需要的內容。
三、解析熱門作品列表
1. 構建物件
那麼接下來就要解析了,解析之前,我們需要先使用lxml
構建我們需要的物件:
from lxml import etree
result = etree.HTML(html.text)
print(type(result))
print(result)
輸出為:
<class 'lxml.etree._Element'> <Element html at 0x7fdef020afc8>
2. 子節點、子孫節點、屬性過濾、文字選取
然後我們觀察網頁,定位到歌單位置:
我們發現歌曲列表藏在一個<ul>
標籤中,這個標籤擁有class="f-hide"
屬性;<ul>
標籤中嵌套了一個<li>
標籤;<li>
標籤中又嵌套了一個<a>
標籤,這個<a>
標籤有個href
屬性,其值為每首歌的相對連結,而歌曲名稱就在<a>
標籤對應的文字中。
好了,接下來我們構建Xpath路徑選擇表示式:
song_list = result.xpath('//ul[@class="f-hide"]/li/a/text()')
print(song_list)
看下輸出:
['成都', '南方姑娘', '理想', '畫', '我們的時光', '少年錦時', '阿刁', '鼓樓', '三十歲的女人', '無法長大', '八十年代的歌', '十九歲', '彩虹下面', '瑪麗', '讓我偷偷看你', '吉姆餐廳', '朵', '靜下來', '家鄉', '已是兩條路上的人', '未給姐姐遞出的信', '北京的冬天', '孤獨', '小屋', '再也不會去麗江', '再見北京', '媽媽', '夢中的哈德森', '人家', '背影', '窯上路', '浮游', '不開的脣', '開往北京的火車', '趙小雷', '往事只能回味', '愛人你在哪裡', '未給姐姐遞出的信 ', 'Over', '民謠', '憑什麼說愛你', '逆流而上', '夏天', '米店', '飛來飛去', '朵兒 (Live版)', '何必', '塔吉汗', '月亮粑粑 (Live)']
好,我們成功解析出了歌曲列表。那麼接下來我們回頭看一下這個表示式:
首先,我們使用.xpath()
方法來呼叫Xpath表示式;//ul[@class="f-hide"]
代表我們選取所有擁有class="f-hide"
屬性的<ul>
標籤;/li
代表在上邊返回的<ul>
標籤的子節點中選取所有的<li>
標籤,/a
同理,進一步選取子節點中的<a>
標籤;/text()
則是將結果中<a>
標籤中的文字資訊返回為一個列表。
是不是很簡單?Xpath使用起來完全符合我們的直觀感覺,非常人性化。
3. 父節點
那如果我想檢視某個節點的父節點怎麼辦?很好辦,使用/..
即可:
temp = result.xpath('//ul[@class="f-hide"]/../@class')
print(temp)
輸出為:
['u-slt f-ib']
也就是說,剛才我們定位到的<ul>
節點的class
屬性的值為'u-slt f-ib'
。這裡我們使用..
選取了父節點,並使用/@
選取了該節點的class
屬性。
4. 屬性選取
關於屬性的選取,我們再看一個例子:
link_list = result.xpath('//ul[@class="f-hide"]/li/a/@href')
print(link_list)
輸出為:
['/song?id=436514312', '/song?id=202373', '/song?id=29567189', '/song?id=202369', '/song?id=29567193', '/song?id=29567192', '/song?id=447925059', '/song?id=447926067', '/song?id=29567191', '/song?id=437608773', '/song?id=447925066', '/song?id=530995556', '/song?id=1295824647', '/song?id=447925058', '/song?id=33166602', '/song?id=29567187', '/song?id=447926063', '/song?id=517567264', '/song?id=29567188', '/song?id=28111471', '/song?id=202368', '/song?id=29567185', '/song?id=447925063', '/song?id=29567194', '/song?id=34852810', '/song?id=447925067', '/song?id=202377', '/song?id=29567190', '/song?id=202367', '/song?id=202376', '/song?id=447926068', '/song?id=29567186', '/song?id=202370', '/song?id=202375', '/song?id=202371', '/song?id=433018045', '/song?id=433018044', '/song?id=29810320', '/song?id=202374', '/song?id=433018041', '/song?id=432792901', '/song?id=433018046', '/song?id=432792905', '/song?id=31460216', '/song?id=433018047', '/song?id=553546118', '/song?id=432792904', '/song?id=432792903', '/song?id=460628183']
這次我們沒有選取歌名,而是通過<a>
標籤的href
屬性選取了每首歌的相對連結地址。通過這一地址,我們可以與music.163.com/#
一起拼接出每首歌的實際地址。
5. 屬性多值情況處理
前邊我們通過//ul[@class="f-hide"]
嘗試了按照屬性進行篩選,那麼當一個屬性有多個取值的時候我們怎麼辦呢?
觀察網頁,有這麼一個<div>
標籤,它的class
屬性有三個取值,假如我們想選取擁有class="sltbtn"
屬性的<div>
標籤,就會把它給漏掉。這時候,Xpath自帶的contains()
函式就該出馬了:
temp2 = result.xpath('//div[contains(@class, "sltbtn")]/@class')
print(temp2)
輸出為:
['u-btn2 u-btn2-1 sltbtn']
我們成功通過class
屬性的一個取值定位到了這個標籤,並選取了它的class
屬性的所有取值。
6. 多屬性過濾處理
那麼還有一種常見的情況,就是假如我需要通過多個屬性來定位一個標籤的時候,應該怎麼辦呢?注意,這裡是多個屬性,上邊一種情況是一個屬性的多個取值。
針對多個屬性的情況,我們可以通過and運算子來連線,Xpath支援一系列的運算子,包括and
、or
、|
、加減乘除以及大小判斷等。
以上邊這個例子來說,他有class
、data-res-type
等屬性,下面我們來看如何同時過濾他的兩個屬性值:
temp3 = result.xpath('//li[@class="choose" and @data-res-type>1]/text()')
print(temp3)
輸出為:
['作詞作品', '作曲作品']
可以看到,由於我設定了@data-res-type>1
,所以出來了兩條記錄,因為有兩個<li>
標籤的@data-res-type
屬性的取值分別為2和3。
7. 按順序選擇
best_song = result.xpath('//ul[@class="f-hide"]/li/a[1]/text()')
last_song = result.xpath('//ul[@class="f-hide"]/li/a[last()]/text()')
last_3_song = result.xpath('//ul[@class="f-hide"]/li/a[last()-2]/text()')
best_3_songs = result.xpath('//ul[@class="f-hide"]/li/a[position()<4]/text()')
print(best_song)
print(last_song)
print(last_3_song)
print(best_3_songs)
輸出為:
['成都'] ['月亮粑粑 (Live)'] ['何必'] ['成都', '南方姑娘', '理想']
我們使用[1]
選取了第一個<li>
標籤,使用[last()]
選取了最後一個標籤,使用[last()-2]
選取了倒數第三個標籤,使用[position()<4]
選取了前三個標籤。
8. 節點軸選擇
Xpath有一系列的軸選擇方法,如下表所示:
軸名稱 | 結果 |
---|---|
ancestor | 選取當前節點的所有先輩(父、祖父等) |
ancestor-or-self | 選取當前節點的所有先輩(父、祖父等)以及當前節點本身 |
attribute | 選取當前節點的所有屬性 |
child | 選取當前節點的所有子元素 |
descendant | 選取當前節點的所有後代元素(子、孫等) |
descendant-or-self | 選取當前節點的所有後代元素(子、孫等)以及當前節點本身 |
following | 選取文件中當前節點的結束標籤之後的所有節點 |
namespace | 選取當前節點的所有名稱空間節點 |
parent | 選取當前節點的父節點 |
preceding | 選取文件中當前節點的開始標籤之前的所有節點 |
preceding-sibling | 選取當前節點之前的所有同級節點 |
self | 選取當前節點 |
這些軸選擇有一些跟上邊提到的方法是一樣的,大部分都能通過前邊提到的/
、//
、..
等組合而來。但很多情況下,直接使用這些軸會更加快捷。軸選擇的使用方法如下,以剛才的父節點為例,我們將..
替換為parent::*
,*
代表選取父節點中的所有節點,當然,事實上這裡只有1個節點,不過在::
之後必須要選擇至少一個節點,這是語法的要求,所以我們用*
作為字尾加在parent::
後邊。
temp = result.xpath('//ul[@class="f-hide"]/parent::/@class')
print(temp)
好了,今天我們演示瞭如何通過Xpath解析網頁資料,上述內容基本涵蓋了爬取網頁過程中的絕大多數解析需求,我們只需多加練習,能夠在不同場景下靈活組合這些基本技能,就可以順暢地從HTML文字中獲取我們所需要的資訊了。