【升级的爬虫】全自动爬取网易云 任意歌曲 热门 / 最新 评论
本帖最后由 0HB 于 2022-3-23 09:47 编辑最近学习了小甲鱼老师的[爬虫] 爬取网易云音乐的精彩评论,发现居然只能实现对 “已知id” 的某一首歌曲进行爬取“热门评论”。{:9_241:}
不甘之情顿时涌上心头,遍历全网,经过整合,算是完成了“自动爬取网易云音乐任意歌曲 热门 / 最新 评论”的任务。{:9_231:}
吼吼!此时,我才记起可以翻翻看鱼C论坛里是否已有完美的解决方案,发现是没有的。正好,那么便分享出来,同鱼友们共适之!
注:1.本次分享不涉及教学(因为码力尚浅的我确也不明晰其中解密的过程,只是做了下代码的搬运工/整合者,具体原理可见下文中标出的原文链接)
2.目的是最大程度完成课程›《极客Python之效率革命》›爬取网易云音乐的精彩评论的 任务,为方便学习而整合了代码(将一一标明作者与出处),侵权必删。
3.欢迎鱼友转载/学习,还烦请记得标出原文链接中的作者(麻烦了{:9_221:} )
那么,事不宜迟,我们开始第一项
功能简介:
通过输入的歌名与歌手名,即可自动获得此歌曲的id,其后通过id 来得到 url并进行爬取,期间可以选择爬取“热门评价”或“最新评价”。最后的最后,将获得的数据保存在当前目录的新建文件夹“comments”里。
好,那么先来看看效果图:
下载想要歌曲的 热门评论
txt文本展示
想要歌曲的 最新评论
xt文本展示
使用方法:
运行WyyComment.py(其中引用了‘WyyGetID’ (获取网易云歌曲ID) 与 ‘WyyGetC.py’)(获取歌曲下评论)
接下来,是代码:
# 命名为:WyyComment.py
import WyyGetID# 这个模块 我们用来获得所需要下载 网易云歌曲 的 id
import WyyGetC# 这个模块 我们借已获得的 id 来获得所需要下载 网易云歌曲 的 评论
import re
import os, sys
def save(song_id, song_name, people_name):# 按格式保存爬取的最新评论
data = []
file = open('./comments/{0}.txt'.format(song_name + ' - ' + people_name + '_最新评论'), 'w+')
with open("./comments/" + str(song_id) + ".txt") as f:
for each_line in f:
data.append(re.findall(r" '(.+?)',", each_line))
file.write('{0} 的歌曲 {1} 最新评论如下\n'.format(people_name, song_name))
for each_one in data:
name = '用户名:' + each_one + '\n'
try:
content = '评价:' + ''.join(each_one.split("content': '")) + '\n' * 2
except:
pass
file.write(name + content)
os.remove("./comments/" + str(song_id) + ".txt")
def get(choice):
print('自动爬取网易云音乐歌曲 {0} 评论'.format(choice))
print('—————————程序开始—————————')
s_name = input('请输入歌曲名:')
p_name = input('请输入歌手名:')
try:
id1, p_name1 = WyyGetID.GetID(s_name, p_name)# 有一说一,p_name1更全面。
except:
print('未找到该歌手的歌曲,请重新尝试。')
sys.exit()
if choice == '最新':
WyyGetC.GetNC(str(id1))
save(id1, s_name, p_name1)
print('已成功获取最新评论')
elif choice == '热门':
WyyGetC.GetHC(str(id1), s_name, p_name1)
print('已成功获取热门评论')
print('—————————程序结束—————————')
if __name__ == '__main__':
while 1:
choice = input('请选择获取你想要获取评论的类型("最新" 或 "热门"):')
if choice in ['最新', '热门']:
break
get(choice)
# 应命名为: WyyGetID.py
获取id的功能 及 原理讲解 来自:【实战:爬取网易云音乐歌曲对应id并剔除无版权歌曲】 作者:如梦如幻uuu
(注:特别指出,其中依赖库:pycryptodome,原文中打错了,已纠正)
import requests
import random
import base64
from Crypto.Cipher import AES
import json
import binascii
# 这个模块 我们用来获得所需要下载 网易云歌曲 的 id
class Music_api():
# 设置从JS文件提取的RSA的模数、协商的AES对称密钥、RSA的公钥等重要信息
def __init__(self):
self.modulus = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
self.nonce = '0CoJUm6Qyw8W8jud'
self.pubKey = '010001'
self.url = "https://music.163.com/weapi/cloudsearch/get/web?csrf_token="
self.HEADER = {}
self.setHeader()
self.secKey = self.getRandom()
# 生成16字节即256位的随机数
def getRandom(self):
string = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
res = ""
for i in range(16):
res += string
return res
# AES加密,用seckey对text加密
def aesEncrypt(self, text, secKey):
pad = 16 - len(text) % 16
text = text + pad * chr(pad)
encryptor = AES.new(secKey.encode('utf-8'), 2, '0102030405060708'.encode('utf-8'))
ciphertext = encryptor.encrypt(text.encode('utf-8'))
ciphertext = base64.b64encode(ciphertext).decode("utf-8")
return ciphertext
# 快速模幂运算,求 x^y mod mo
def quickpow(self, x, y, mo):
res = 1
while y:
if y & 1:
res = res * x % mo
y = y // 2
x = x * x % mo
return res
# rsa加密
def rsaEncrypt(self, text, pubKey, modulus):
text = text[::-1]
a = int(binascii.hexlify(str.encode(text)), 16)
b = int(pubKey, 16)
c = int(modulus, 16)
rs = self.quickpow(a, b, c)
return format(rs, 'x').zfill(256)
# 设置请求头
def setHeader(self):
self.HEADER = {
'Accept': '*/*',
'Accept-Encoding': 'gzip,deflate,sdch',
'Accept-Language': 'zh-CN,zh;q=0.8,gl;q=0.6,zh-TW;q=0.4',
'Connection': 'keep-alive',
'Content-Type': 'application/x-www-form-urlencoded',
'Host': 'music.163.com',
'Referer': 'https://music.163.com/search/',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.152 Safari/537.36'
}
# 设置相应的请求参数,从而搜索列表
# 总体的密码加密步骤为:
# 首先用nonce对text加密生成密文1
# 然后用随机数seckey加密密文1生成密文2
# 随后,用公钥加密seckey生成密文3
# 其中,密文2作为请求参数中的params,密文3作为encSeckey字段
# 这样,接收方可以通过私钥解密密文3获得seckey(随机数)
# 然后用seckey解密密文2获得密文1
# 最终用统一协商的密钥nonce解密密文1最终获得text
def search(self, s, offset, type="1"):
text = {"hlpretag": "<span class=\"s-fc7\">",
"hlposttag": "</span>",
"#/discover": "",
"s": s,
"type": type,
"offset": offset,
"total": "true",
"limit": "30",
"csrf_token": ""}
text = json.dumps(text)
params = self.aesEncrypt(self.aesEncrypt(text, self.nonce), self.secKey)
encSecKey = self.rsaEncrypt(self.secKey, self.pubKey, self.modulus)
data = {
'params': params,
'encSecKey': encSecKey
}
result = requests.post(url=self.url,
data=data,
headers=self.HEADER).json()
return result
# 获取指定音乐列表(相当于主函数)
def get_music_list(self, keywords):
music_list = []
for offset in range(1):
result = Music_api().search(keywords, str(offset))
result = result['result']['songs']
for music in result:
# if music['copyright'] == 1 and music['fee'] == 8:
if (music['privilege']['fee'] == 0 or music['privilege']['payed']) and music['privilege']['pl'] > 0 and \
music['privilege']['dl'] == 0:
continue
if music['privilege']['dl'] == 0 and music['privilege']['pl'] == 0:
continue
# if music['fee'] == 8:
music_list.append(music)
return music_list
def GetID(song_name, people_name):
contents = Music_api().get_music_list(song_name)
data_dict = {}
for i in contents:# 测试
data_dict['name']] = i['id']
if people_name in data_dict.keys():
return i['id'], i['ar']['name']
# 应命名为: WyyGetC.py
获得 最新评论的功能及 讲解 来自: 【python爬取网易云音乐歌曲评论信息】 作者:NewJune
# 参考地址:https://www.zhihu.com/question/36081767
# 完美兼容win10、python3.6(亲测Mac、python3.8,神州行!),由于python3.6下pycrypto库已经停止维护,可以安装pyCryptodome库代替,pyCyrpto库的后续分支,有一个叫pyCryptodome的库,是前代的延伸版。
import sys
import codecs
import requests, json, os
import base64
from Crypto.Cipher import AES
# 这个模块 我们借已获得的 id 来获得所需要下载 网易云歌曲 的 评论
class Spider():
def __init__(self, idNum):
# user-Agent字段直接从浏览器中复制过来即可,请求头中其他字段非必须项,也可以从浏览器中找到所有字段都放到Request Headers
self.header = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.221 Safari/537.36 SE 2.X MetaSr 1.0',
'Referer': 'http://music.163.com/'}
self.url = 'https://music.163.com/weapi/v1/resource/comments/R_SO_4_' + idNum + '?csrf_token='# 每一次的base_url只有歌曲id不同,构造url即可。
def __get_jsons(self, url, page):
# 获取两个参数
music = WangYiYun()
text = music.create_random_16()
params = music.get_params(text, page)
encSecKey = music.get_encSEcKey(text)
fromdata = {'params': params, 'encSecKey': encSecKey}
jsons = requests.post(url, data=fromdata, headers=self.header)
# print(jsons.raise_for_status())
# 打印返回来的内容,是个json格式的
# print(jsons.content)
return jsons.text
def json2list(self, jsons):
'''把json转成字典,并把他重要的信息获取出来存入列表'''
# 可以用json.loads()把它转成字典
# print(json.loads(jsons.text))
users = json.loads(jsons)
comments = []
for user in users['comments']:
# print(user['user']['nickname']+' : '+user['content']+' 点赞数:'+str(user['likedCount']))
name = user['user']['nickname']
content = user['content']
# 点赞数
likedCount = user['likedCount']
# 提取所需json中所需的字段构造字典
user_dict = {'name': name, 'content': content, 'likedCount': likedCount}
# 将提取的字典信息追加到列表中
comments.append(user_dict)
return comments
def run(self, idNum):
self.page = 1
while True:
jsons = self.__get_jsons(self.url, self.page)
comments = self.json2list(jsons)
non_bmp_map = dict.fromkeys(range(0x10000, sys.maxunicode + 1), 0xfffd)
## print(str(comments).translate(non_bmp_map))
print('self.page = ' + str(self.page))# 控制台打印正在爬取的页码数
print(idNum)# 打印正在爬取的歌曲id
# 在该脚本同级目录下生成“comments”文件夹
dirName = u'{}'.format('comments')
if not os.path.exists(dirName):
os.makedirs(dirName)
with open("./comments/" + idNum + ".txt", "a", encoding='utf-8') as f:# 结果写入txt文件
## print(len(comments))
for ii in range(len(comments)):
f.write(str(comments).translate(non_bmp_map))
f.write('\n')
## print(ii)
f.close()
# 当这一页的评论数少于20条时,证明已经获取完
## self.write2sql(comments)
if len(comments) < 100:# 当limits设置为100时,默认每次服务器请求结果100条comments,当小于此数,意味爬到最后一页。
print('评论已经获取完')
break
self.page += 1
# 找出post的两个参数params和encSecKey
class WangYiYun():
def __init__(self):
# 在网易云获取的三个参数
self.second_param = '010001'
self.third_param = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'
self.fourth_param = '0CoJUm6Qyw8W8jud'
def create_random_16(self):
'''获取随机十六个字母拼接成的字符串'''
return (''.join(map(lambda xx: (hex(ord(xx))), str(os.urandom(16)))))
def aesEncrypt(self, text, key):
# 偏移量
iv = '0102030405060708'
# 文本
pad = 16 - len(text) % 16
text = text + pad * chr(pad)# 补齐文本长度
encryptor = AES.new(bytearray(key, 'utf-8'), AES.MODE_CBC, bytearray(iv, 'utf-8'))
# encryptor = AES.new(key, 2, iv)
ciphertext = encryptor.encrypt(bytearray(text, 'utf-8'))
## print(bytearray(key,'utf-8'))
ciphertext = base64.b64encode(ciphertext)
return ciphertext
def get_params(self, text, page):
'''获取网易云第一个参数'''
# 第一个参数
if page == 1:
self.first_param = '{rid: "", offset: "0", total: "true", limit: "100", csrf_token: ""}'
# rid: "R_SO_4_557581284",经测试该值可以置空,不影响结果的执行。
else:
self.first_param = '{rid: "", offset:%s, total: "false", limit: "100", csrf_token: ""}' % str(
(page - 1) * 40)# limit参数可以灵活设置,默认为20,设置为100,爬取效率可以提高
params = self.aesEncrypt(self.first_param, self.fourth_param).decode('utf-8')
params = self.aesEncrypt(params, text)
return params
def rsaEncrypt(self, pubKey, text, modulus):
'''进行rsa加密'''
text = text[::-1]
rs = int(codecs.encode(text.encode('utf-8'), 'hex_codec'), 16) ** int(pubKey, 16) % int(modulus, 16)
return format(rs, 'x').zfill(256)
def get_encSEcKey(self, text):
'''获取第二个参数'''
pubKey = self.second_param
moudulus = self.third_param
encSecKey = self.rsaEncrypt(pubKey, text, moudulus)
return encSecKey
def GetNC(id):# Get New Comments & 获取最新评论
idPs = # 列表内 id 可以给多个值,会增加循环依次获取。
for jj in range(len(idPs)):
idNum = idPs
spider = Spider(idNum)# 根据Spider类实例化spider对象
spider.run(idNum)# 调用spider对象的run方法
"""
获取网易云音乐任意歌曲 最新 评论,以上
"""
def GetHC(ID, s_name, p_name):# Get Hot Comments & 获取热门评论
header = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.221 Safari/537.36 SE 2.X MetaSr 1.0'}
url = 'http://music.163.com/api/v1/resource/comments/R_SO_4_' + str(ID)
req = requests.get(url, headers=header)
req = req.json()
dirName = u'{}'.format('comments')
if not os.path.exists(dirName):
os.makedirs(dirName)
with open('./comments/{0}.txt'.format(s_name + ' - ' + p_name + '_热门评论'), 'w+') as file:
file.write('{0} 的歌曲 {1} 热门评论如下\n'.format(p_name, s_name))
for each_one in req['hotComments']:
name = each_one['user']['nickname']
comment = each_one['content']
liked_count = str(each_one['likedCount'])
file.write('用户名:{0} 总获赞:{1}\n评价:{2} \n\n'.format(name, liked_count, comment))
写在最后:哈哈,路遥知码力,大家都要加油啊!{:9_225:} 使用时,把三个文件放在同一目录下,分别命好名字,下载好依赖的库,就可以愉快的运行 WyyComment.py 了。奥,时间到了,生而..{:10_245:} 学习 学习 {:10_256:} {:10_277:} 鱼币中奖概率设置太低了,第一次发分享帖,下次注意{:10_256:} 看看 厉害 学习 学会了 666 赚鱼币做作业 {:5_95:} 学习! 学习 我版本太低了 太好了,学习学习,正好需要这个,感谢(O) asddd 顶