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文本中獲取我們所需要的信息了。
推薦閱讀: