項目地址

項目gitee地址:https://gitee.com/shawn_chen_rtz/netease_cloud_music.git,歡迎star。

環境準備

需要安裝依賴requests、pycrypto模塊,可通過pip命令。

pip install requests==2.27.1
pip install pycrypto==2.6.1

獲取歌曲id

通過歌曲名獲取到歌曲id對于后續的獲取精彩評論和歌詞信息至關重要。

接口信息

網易云音樂的根據歌曲名稱獲取對應歌曲id的接口信息:http://music.163.com/api/search/get,請求method為GET,入參格式{“s”:song_name,”type”:1,”limit”:1},如下,

GET http://music.163.com/api/search/get
入參格式:
{
's': song_name, # 搜索關鍵字,本文中項目實現傳入待查詢歌曲名稱
'type': 1, # 搜索類型:1為單曲,2為專輯,3為歌手,4為歌詞
'limit': 1, # 返回數量,本文中項目實現,設置為1,即返回1首
}

其中入參的參數‘s’表示查詢關鍵字,接收我們傳入的歌曲名稱;

參數‘type’表示查詢對象分類,在項目實現中設置值為1(表示搜索類型為歌曲);

參數‘limit’表示返回數據數量,在項目實現中設置值為1(只取1首)。

代碼實現

在項目中定義一個查詢歌曲id的方法search_song_id(),具體代碼實現如下,

import requests
import json

def search_song_id(song_name):
# 網易云音樂API地址
url = 'http://music.163.com/api/search/get'
# 參數設置
params = {
's': song_name, # 搜索關鍵字
'type': 1, # 搜索類型:1為單曲,2為專輯,3為歌手,4為歌詞
'limit': 1, # 返回數量
}
# 發送GET請求
response = requests.get(url, params=params)
# 解析JSON數據
data = response.json()
# 檢查是否有結果
if data['result']['songs']:
# 返回歌曲ID
return data['result']['songs'][0]['id']
else:
return None

if __name__ == "__main__":
# 使用函數查找歌曲ID
song_id = search_song_id('Your Song Name')
print(song_id)
song_id = search_song_id('偏偏喜歡你')
print(song_id)

接口http://music.163.com/api/search/get請求后返回的數據組織結構如下,

根據接口返回的數據組織結構,歌曲id即data[‘result’][‘songs’][0][‘id’]。

獲取歌曲歌詞

通過上一節獲取到的歌曲id查詢歌曲歌詞。

接口信息

網易云音樂對應接口信息:http://music.163.com/api/song/lyric?id=song_id+&lv=1&tv=-1,其中song_id替換為目標歌曲id,該接口請求Method為GET。

代碼實現

定義了一個獲取歌曲歌詞的方法get_lyric(),具體代碼實現如下,

import requests
import json

def get_lyric(song_id):
headers = {
"user-agent":"Mozilla/5.0",
"Referer":"http://music.163.com",
"Host":"music.163.com"
}
if not isinstance(song_id,str):
song_id = str(song_id)
url = f"http://music.163.com/api/song/lyric?id={song_id}+&lv=1&tv=-1"
r = requests.get(url,headers=headers)
r.raise_for_status()
r.encoding = r.apparent_encoding
json_obj = json.loads(r.text)
lyric = json_obj['lrc']['lyric']
#print(type(lyric))
return lyric

方法return返回歌曲歌詞lyric。

獲取歌曲精彩評論


歌曲精彩評論接口信息

首先要找到返回歌曲精彩評論數據的目標接口,訪問網易云音樂網頁,搜索某歌曲進入歌曲頁面,F12打開瀏覽器開發者工具—網絡,

刷新頁面,篩選請求列表,定位到目標接口https://music.163.com/weapi/comment/resource/comments/get?csrf_token=,請求Method為POST,接口表單參數有兩個,分別是paramsencSecKey,如下面2張圖所示,

可以看到向接口提交的兩個表單參數都是一長串的字符串,顯然是經過加密的,在下一節將對參數的加密邏輯進行探索分析。

接口參數的加密邏輯分析

對接口參數加密方式的分析可以說是整個項目中最具挑戰的點,事實上就是一種逆向工程,我們要大膽的假設,小心地求證。當然這種大膽假設是在有一定技術基礎的前提下做出的合理假設,在本項目中涉及有前端、后端、加/解密等技術。F12瀏覽器開發者工具-網絡,重復刷新歌曲頁面,可以看到接口參數的值發生了變化但是值對應的字符串長度保持不變,可以猜到參數的加密過程中有隨機性操作且明文的結構可能是固定的。進一步點擊目標接口請求的啟動器標簽,觀察其請求啟動器鏈,可以看到有一個js文件,如下圖所示,

js文件的域名為https://s3.music.126.net,于是去開發者工具-源代碼/來源中去找這個js文件,找到如下圖,

根據域名信息找到該js文件,可以看到其源碼。全文搜索參數paramsencSecKey,果然有定位到,

從此處可以確認這兩個參數是經過js文件中的代碼實現的加密功能。接下來,就是分析這個js文件,確認加密邏輯和所加密的明文文本信息
在js文件中,找到這么一串代碼片段(js代碼),

!function() {
function a(a) {
var d, e, b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", c = "";
for (d = 0; a > d; d += 1)
e = Math.random() * b.length,
e = Math.floor(e),
c += b.charAt(e);
return c
}
function b(a, b) {
var c = CryptoJS.enc.Utf8.parse(b)
, d = CryptoJS.enc.Utf8.parse("0102030405060708")
, e = CryptoJS.enc.Utf8.parse(a)
, f = CryptoJS.AES.encrypt(e, c, {
iv: d,
mode: CryptoJS.mode.CBC
});
return f.toString()
}
function c(a, b, c) {
var d, e;
return setMaxDigits(131),
d = new RSAKeyPair(b,"",c),
e = encryptedString(d, a)
}
function d(d, e, f, g) {
var h = {}
, i = a(16);
return h.encText = b(d, g),
h.encText = b(h.encText, i),
h.encSecKey = c(i, e, f),
h
}
function e(a, b, d, e) {
var f = {};
return f.encText = c(a + e, b, d),
f
}
window.asrsea = d,
window.ecnonasr = e
}();

var bVi0x = window.asrsea(JSON.stringify(i1x), bse6Y(["流淚", "強"]), bse6Y(Qu9l.md), bse6Y(["愛心", "女孩", "驚恐", "大笑"]));
e1x.data = j1x.cr1x({
params: bVi0x.encText,
encSecKey: bVi0x.encSecKey
})

從上面兩個js代碼片段可以一步一步地逆向分析:

  1. 參數paramsencSecKey分別對應的是對象變量bVi0x的屬性encText和encSecKey;
  2. bVi0x對象又是從window.asrsea()方法返回的,該方法有4個參數——JSON.stringify(i1x), bse6Y([“流淚”, “強”]), bse6Y(Qu9l.md), bse6Y([“愛心”, “女孩”, “驚恐”, “大笑”]);
  3. js文件中搜索window.asrsea,定位到其定義位置,看到window.asrsea = d,是d函數本身的引用,js中函數是第一類對象(first class object),同Python,可以作為參數傳給函數,也可以作為函數的返回值。d函數的4個參數d、e、f、g對應上一步驟調用window.asrsea()時傳入的4個參數;事實上d函數就是我們要找的加密函數;
  4. d函數體中又調用了函數a、b、c,首先先確定函數a、b、c的功能。
function a(a) {
var d, e, b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", c = "";
for (d = 0; a > d; d += 1)
e = Math.random() * b.length,
e = Math.floor(e),
c += b.charAt(e);
return c
}

接收一個參數,可以看出a函數實現的功能是從大、小寫字母和0~9數字這個范圍中隨機挑選字符組成長度為參數a的隨機字符串返回。比如調用a函數,a(16)即返回一個長度為16的隨機字符串;

function b(a, b) {
var c = CryptoJS.enc.Utf8.parse(b)
, d = CryptoJS.enc.Utf8.parse("0102030405060708")
, e = CryptoJS.enc.Utf8.parse(a)
, f = CryptoJS.AES.encrypt(e, c, {
iv: d,
mode: CryptoJS.mode.CBC
});
return f.toString()
}

接收兩個參數,對參數a進行AES加密,加密模式為CBC,將結果轉為字符串形式返回;

function c(a, b, c) {
var d, e;
return setMaxDigits(131),
d = new RSAKeyPair(b,"",c),
e = encryptedString(d, a)
}

接收三個參數,對參數a進行RSA加密,返回的結果是十六進制的字符串形式;

function d(d, e, f, g) {
var h = {}, i = a(16);
return h.encText = b(d, g),
h.encText = b(h.encText, i),
h.encSecKey = c(i, e, f),
h
}

可以明確d函數體中的處理邏輯:

以上就是js文件中定義的完整加密處理過程,加密邏輯確認達成函數定義了之后,js需要調用以上函數用以生成加密后的參數,這個過程的關鍵點在于明確加密函數d被調用時接收的4個參數值,即明文文本信息和相關。如何明確呢?F12打開瀏覽器開發者工具打斷點進行調試。

在圖示js文件標記的所在行打斷點,刷新網易云歌曲頁面,點擊右側的箭頭,不斷的繼續執行腳本,同時觀察頁面元素的加載情況(隨著不斷地往下執行腳本,頁面元素像歌詞、評論等信息會逐個顯示出來);在瀏覽器開發者工具—控制臺中打印加密方法的4個參數JSON.stringify(i1x), bse6Y([“流淚”, “強”]), bse6Y(Qu9l.md), bse6Y([“愛心”, “女孩”, “驚恐”, “大笑”]),每次點擊繼續執行腳本執行到斷點處,都打印一次,可以看到后面的三個參數bse6Y([“流淚”, “強”]), bse6Y(Qu9l.md), bse6Y([“愛心”, “女孩”, “驚恐”, “大笑”])的值都是固定不變的,分別是’010001’、’00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7’、‘0CoJUm6Qyw8W8jud’,如下圖,

同時可以看到第一個參數在調試過程中不斷地發生變化,不像其他參數是固定不變的,那么如何確定其值呢?每首歌對應的是各自的評論數據,一一對應關系,不會對不上號,不會錯號,所以合理推斷參數值中應該會有歌曲的id信息。在調試的過程中,不斷執行腳本,不斷在斷點處懸停,當頁面上的評論數據第一次出現時,屆時打印出的console.log(JSON.stringify(i1x))值就是精彩評論接口參數加密函數被調用時傳入的正確值。經調試得到結果,第一個參數的值是'{“rid”:”R_SO_4_139357″,”threadId”:”R_SO_4_139357″,”pageNo”:”1″,”pageSize”:”20″,”cursor”:”-1″,”offset”:”0″,”orderType”:”1″,”csrf_token”:””}’,可以看到其中“R_SO_4_139357”中的139357正是對應歌曲的id,這也驗證了我們之前的推測。以上就全部確定了js文件中調用加密函數d(window.asrsea()方法)時所傳入的4個參數值,即window.asrsea(‘{“rid”:”R_SO_4_139357″,”threadId”:”R_SO_4_139357″,”pageNo”:”1″,”pageSize”:”20″,”cursor”:”-1″,”offset”:”0″,”orderType”:”1″,”csrf_token”:””}’,’010001′,’00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7′,‘0CoJUm6Qyw8W8jud’)。明文文本信息和相關確認達成接下來就是使用Python代碼替換JavaScript重寫上述a、b、c函數功能,并按照d函數體中的處理過程進行調用,獲取到加密參數值,此內容將在下一節進行介紹,read on.

參數加密過程的Python實現

js中的a函數,對應Python實現如下,

import random
def generate_random_strs(length):
"""
生成固定長度的字符串
:param length: 指定生成的字符串長度
:return: 返回length長度的字符串
"""
string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
i = 0
random_strs = ""
while i < length:
e = random.random() * len(string)
e = math.floor(e)
random_strs = random_strs + string[e]
i = i + 1
return random_strs

js中的b函數(AES加密),對應Python實現如下,

from Crypto.Cipher import AES
import base64

def AESencrypt(msg,key):
"""
AES加密
:param msg:
:param key:
:return:
"""
# 查缺
padding = 16 - len(msg) % 16
# 補缺
msg = msg + padding * chr(padding)
# 用來加密或者解密的初始向量(必須是16位)
iv = "0102030405060708"
cipher = AES.new(key,AES.MODE_CBC,iv)
# 加密后得到的是bytes類型的數據
encryptedbytes = cipher.encrypt(msg.encode('utf-8'))
# 使用Base64進行編碼,返回byte字符串
encodestrs = base64.b64encode(encryptedbytes)
# 對byte字符串按utf-8進行解碼
enctext = encodestrs.decode("utf-8")
return enctext

js中的c函數(RSA加密),對應Python實現如下,

import codecs

def RSAencrypt(randomstrs,key,f):
"""
RSA加密
:param randomstrs:
:param key:
:param f:
:return:
"""
string = randomstrs[::-1]
text = bytes(string,'utf-8')
seckey = int(codecs.encode(text,encoding='hex'),16) ** int(key,16) % int(f,16)
return format(seckey,'x').zfill(256)

接下來,我們只要按照js d函數的處理邏輯對generate_random_strs(length)、AESencrypt(msg,key)、RSAencrypt(randomstrs,key,f)函數依次調用,即可獲得加密參數值。在上一節的最后部分,已經確定了js加密函數d被調用時所接收的參數值,其中3個是固定不變的值,第1個參數是含有歌曲id的固定格式的值,用Python拼裝一下,獲取加密參數的代碼如下,

def get_params(song_id):
"""
獲取加密參數encText,encSecKey
:params song_id: str
:return:
"""
# 用于獲取某首歌的評論數據
msg = '{"rid":"R_SO_4_' + song_id + '","threadId":"R_SO_4_' + song_id + '","pageNo":"1","pageSize":"20","cursor":"-1","offset":"0","orderType":"1","csrf_token":""}'
key = '0CoJUm6Qyw8W8jud'
f = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'
e = '010001'

enctext = AESencrypt(msg,key)
i = generate_random_strs(16)
encText = AESencrypt(enctext,i)

encSecKey = RSAencrypt(i,e,f)
return encText,encSecKey

調用get_params(song_id),最終返回加密參數值encText,encSecKey。


獲取歌曲精彩評論代碼實現

根據上一節中返回的加密參數值進行接口請求,最終獲取歌曲精彩評論數據,代碼實現如下,

import requests

from utils import get_params
from get_song_id import search_song_id
from get_song_lyric import get_lyric

song_name = input("請輸入歌曲名:")
song_id = search_song_id(song_name)
# 返回的是數字類型,轉成字符串類型
song_id = str(song_id)
print("查找到的歌曲id:",song_id)
if song_id:
url = "https://music.163.com/weapi/comment/resource/comments/get?csrf_token="
params = get_params(song_id)
data = {"params":params[0],"encSecKey":params[1]}
resp = requests.post(url=url,data=data)
result = resp.json()
hot_comments = result['data']['hotComments']
if hot_comments:
hot_comments = [{"content":hot['content'],"timeStr":hot['timeStr'],"likedCount":hot['likedCount'],"user":hot['user']['nickname'],"avatarUrl":hot['user']['avatarUrl']} for hot in hot_comments]

for hot in hot_comments:
print(hot)
else:
print("該歌曲暫無熱評!")
else:
print("未查到該歌曲!")

總結

本文提供了一個完整的教程,教讀者如何使用Python來獲取網易云音樂中特定歌曲的熱評和歌詞,包括詳細的代碼實現和步驟說明。

總結

目的:開發一個Python程序,用于獲取網易云音樂中特定歌曲的精彩評論和歌詞。

功能

  1. 獲取歌曲精彩評論。
  2. 獲取歌曲歌詞。

項目地址:https://gitee.com/shawn_chen_rtz/netease_cloud_music

環境準備:需要安裝requests和pycrypto模塊,可通過pip命令安裝。

獲取歌曲id:通過歌曲名搜索,獲取歌曲id。使用網易云音樂的搜索API http://music.163.com/api/search/get。

獲取歌曲歌詞:使用歌曲id調用歌詞API http://music.163.com/api/song/lyric?id=song_id&lv=1&tv=-1,替換其中的song_id。

獲取歌曲精彩評論

結果展示

文章轉自微信公眾號@仰望天空的蝸牛

上一篇:

Python + BaiduTransAPI :快速檢索千篇英文文獻(附源碼)

下一篇:

為什么API開發對現代應用至關重要?
#你可能也喜歡這些API文章!

我們有何不同?

API服務商零注冊

多API并行試用

數據驅動選型,提升決策效率

查看全部API→
??

熱門場景實測,選對API

#AI文本生成大模型API

對比大模型API的內容創意新穎性、情感共鳴力、商業轉化潛力

25個渠道
一鍵對比試用API 限時免費

#AI深度推理大模型API

對比大模型API的邏輯推理準確性、分析深度、可視化建議合理性

10個渠道
一鍵對比試用API 限時免費