1. 程式人生 > >中國新說唱熱門歌曲有哪些?今天我們就用Python來分析!

中國新說唱熱門歌曲有哪些?今天我們就用Python來分析!

中國新說唱熱門歌曲有哪些?今天我們就用Python來分析!


之前風靡朋友圈的“skr”流行語把中國新說唱這一節目帶上了熱議高峰,本文就來分析下,如何用Python爬取大受歡迎的說唱歌曲。

首先登入https://music.163.com/ 網易雲音樂搜尋新說唱,開啟Chrome的開發工具工具選擇Network並重新載入頁面,找到與評論資料相關的請求即name為web?csrf_token=的POST請求,如下圖所示:

中國新說唱熱門歌曲有哪些?今天我們就用Python來分析!


檢視該請求的headers我們發現formData包含了兩個引數:params、encSecKey。顯然這兩個引數是經過JS加密的,這就是網易雲反爬蟲的一種策略。如下圖:

中國新說唱熱門歌曲有哪些?今天我們就用Python來分析!


我們再來看一下請求的Initiator有個core 2ab1b2b..js。因此我們需要分析一下這個JS,找出formData加密的規則即可。

將JS檔案進行格式化,全域性搜尋params或者encSecKey:

中國新說唱熱門歌曲有哪些?今天我們就用Python來分析!


params和encSecKey是從bSC8u這個物件中取的:

k8c.cE9v({
 params: bSC8u.encText,
 encSecKey: bSC8u.encSecKey
})

而這個物件是由windows.asrsea() 這個方法獲得的,定位到該方法。如下圖所示:

中國新說唱熱門歌曲有哪些?今天我們就用Python來分析!


通過分析程式碼我們發現d函式才是最終的出口。分析d函式:
  • 通過a(16)函式生成一個長度為16的隨機字串;

  • encText這個引數通過兩次呼叫b(a,b) 函式完成,這個函式的作用為AES加密;

  • 呼叫 c(i, e, f)得到encSecKey,這個函式的作用是進行RSA加密。

AES加密:AES(Advanced Encryption Standard)對稱加密演算法是一種高階資料加密標準,是美國聯邦政府採用的一種區塊加密標準,可有效抵制針對DES的***演算法。特點是,金鑰建立時間短、靈敏性好、記憶體需求低、安全性高。RSA加密:RSA加密演算法是一種非對稱加密演算法。在公開金鑰加密和電子商業中RSA被廣泛使用。到目前為止,世界上還沒有任何可靠的***RSA演算法的方式。只要其鑰匙的長度足夠長,用RSA加密的資訊實際上是不能被解破的。

通過上面分析,除了 i 是一個隨機字串,我們只需要知道d、e、f、g這四個引數就可以構造請求進行後續操作了。接下來我們進行JS斷點除錯。

中國新說唱熱門歌曲有哪些?今天我們就用Python來分析!


經過多次除錯,我們發現e、f、g這三個值是不變的,唯一改變的是d。再結合上文分析,encSecKey由函式c(i, e, f)得到的,那是不是就意味著encSeckey這個值是不變的呢?

然而,經過程式碼測試並不是這樣,要保證encSecKey和params中的隨機字串(也就是i的值)是一樣的才可以。

首先我們先實現函式a,即生成16位的隨機字元:

#生成隨機長度為16的字串的二進位制編碼
def random_b():
 seed = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
 sa = []
 for i in range(16):
 sa.append(random.choice(seed))
 salt = ''.join(sa)
 return bytes(salt, 'utf-8')
 # 更簡單的做法
 # return bytes(''.join(random.sample('1234567890DeepDarkFantasy', 16)), 'utf-8')

其次來實現加密引數的生成(說白了就是翻譯JS程式碼為Python程式碼):

#第二引數,rsa公匙組成
pub_key = "010001"
#第三引數,rsa公匙組成
modulus = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
#第四引數,aes密匙
secret_key = b'0CoJUm6Qyw8W8jud'
"""
AES 加密
"""
def aes_encrypt(text, key):
 # 偏移量
 iv = b'0102030405060708'
 # 對長度不是16倍數的字串進行補全,然後在轉為bytes資料
 pad = 16 - len(text) % 16
 try:
 # 如果接到bytes資料(如第一次aes加密得到的密文)要解碼再進行補全
 text = text.decode()
 except:
 pass
 text = text + pad * chr(pad)
 try:
 text = text.encode()
 except:
 pass
 encryptor = AES.new(key, AES.MODE_CBC, iv)
 ciphertext = encryptor.encrypt(text)
 ciphertext = base64.b64encode(ciphertext).decode('utf-8') # 得到的密文還要進行base64編碼
 return ciphertext
"""
RSA 加密
"""
def rsa_encrypt(random_char):
 text = random_char[::-1]#明文處理,反序並hex編碼
 rsa = int(binascii.hexlify(text), 16) ** int(pub_key, 16) % int(modulus, 16)
 return format(rsa, 'x').zfill(256)
"""
構造params
"""
def aes_param(data):
 text = json.dumps(data)
 random_char = random_b()
 params = aes_encrypt(text, secret_key)#兩次aes加密
 params = aes_encrypt(params, random_char)
 enc_sec_key = rsa_encrypt(random_char)
 data = {
 'params': params,
 'encSecKey': enc_sec_key
 }
 return data

最後引數構造完畢,就可以開始咱們的爬蟲了。

由於RSA是非對稱加密,無法通過encSecKey解密出i,沒有i也就無法解密params,所以也就只能對每個介面進行斷點除錯,觀察請求的構造。

搜尋歌曲的data為:

data = {"hlpretag": "<span class="s-fc7">",
 "hlposttag": "</span>",
 "s": "中國新說唱",
 "type": "1",
 "offset": "0",
 "total": "true", #僅在頁碼為0時為true
 "limit": "30",
 "csrf_token": ""
 }

查詢歌曲資訊的data:

{
 "id": song_id,#歌曲id
 "lv": -1,
 "tv": -1,
 "csrf_token": ""
}

接下來就可以開始寫爬蟲了。分析網頁請求我們發現搜尋歌曲的時候響應是在https://music.163.com/weapi/cloudsearch/get/web?csrf_token= 這個請求裡響應的:

中國新說唱熱門歌曲有哪些?今天我們就用Python來分析!


歌詞資訊由 https://music.163.com/weapi/song/lyric?csrf_token=這個請求響應的:

中國新說唱熱門歌曲有哪些?今天我們就用Python來分析!


萬事俱備,開始Coding!

"""
請求頭
在這裡不能將Referer固定寫死
因為在搜尋歌曲的時候請求的Referer為:https://music.163.com/search/
而檢視歌詞的時候請求的Referer為:'https://music.163.com/song?id=歌曲id
"""
headers = {
 'Connection': 'keep-alive',
 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36',
 'Host': 'music.163.com',
 'Origin': 'https://music.163.com',
 }
"""
通過歌曲id爬歌詞內容
"""
def find_song_word(song_id):
 referer = 'https://music.163.com/song?id=' + song_id
 headers['Referer'] = referer
 url = 'https://music.163.com/weapi/song/lyric?csrf_token='
 param = {"id": song_id, "lv": -1, "tv": -1, "csrf_token": ""}
 data = aes_param(param)
 result = requests.post(url, data=data, headers=headers)
 result = result.json()
 return result['lrc']['lyric']
# mongo服務
client = pymongo.MongoClient('mongodb://127.0.0.1:27017/')
# song資料庫
db = client.song
# song_detail表,沒有自動建立
detail_db = db.song_detail
if __name__ == '__main__':
 # 搜尋歌曲url
 query_url = 'https://music.163.com/weapi/cloudsearch/get/web?csrf_token='
 data = {"hlpretag": "<span class="s-fc7">",
 "hlposttag": "</span>",
 "s": "中國新說唱",
 "type": "1",
 "offset": "0",
 "total": "true",
 "limit": "30",
 "csrf_token": ""
 }
 data = aes_param(data)
 referer = 'https://music.163.com/search/'
 headers['Referer'] = referer
 result = requests.post(query_url, data=data, headers=headers)
 result = result.json()
 songs = result['result']['songs']
 for i in songs:
 songer = []
 # 歌曲id
 song_id = i['id']
 # 歌詞
 song_content = find_song_word(str(song_id))
 # 歌曲名
 song_name = i['name']
 song_arr = i['ar']
 # 一首歌可能多人唱, 是個列表 需要遍歷
 for k in song_arr:
 song_dict = {'id': k['id'], 'name': k['name']}
 songer.append(song_dict)
 # 存入mongodb
 detail_db.insert({'song_id': song_id, 'name': song_name, 'songer': songer, 'content': song_content})

執行之後檢視資料庫:

中國新說唱熱門歌曲有哪些?今天我們就用Python來分析!


現在我們有了Rap歌手的id,從全網爬他們的說唱歌曲還不是so easy?