Python3爬虫抓取《曾经我也想过一了百了》热评-因为像你这样的人生于这世上,我稍稍喜欢这个世界了。

xiaoxiao2025-12-10  10

作为初学者,怀着紧张的心情来分享我的第一个爬虫小程序。看到很多网易云音乐热评的爬虫,我也来试试。我这次要爬取的是《曾经我也想过一了百了》中岛美嘉的这首歌。

参考文章链接:

https://blog.csdn.net/weixin_38517705/article/details/80189500 多谢大佬!

首先进行抓包分析

首先用浏览器打开网易云音乐的网页版,进入歌曲页面,可以看到下面有评论。接着 F12 进入开发者控制台,接下来就要做的是,找到歌曲评论对应的 URL“R_SO_4_26830207?csrf_token=”,并分析验证其数据跟网页现实的数据是否吻合。 在Preview的界面,可以看到全部评论是comments,热门评论是hotComments。在Headers页面,可以看到请求方式是POST,以及请求所需要的各种参数。发现表单中需要填两个数据,名称为 params 和 encSecKey。后面紧跟的是一大串字符,换几首歌会发现,每首歌的 params 和 encSecKey 都是不一样的,因此,这两个数据可能经过一个特定的算法进行加密过的。 在此感谢大佬解析过网易云音乐的加密原理,直接拿来用就可以啦。

加密原理请参考:https://www.zhihu.com/question/36081767

def createSecretKey(self,size): return (''.join(map(lambda xx: (hex(ord(xx))[2:]), str(os.urandom(size)))))[0:16] # 进行aes加密 def aesEncrypt(self,text, secKey): pad = 16 - len(text) % 16 #print("leix") #print(type(text)) #print(type(pad * chr(pad))) if isinstance(text,bytes): #print("type(text)=='bytes'") text=text.decode('utf-8') text = text + str(pad * chr(pad)) encryptor = AES.new(secKey, AES.MODE_CBC, '0102030405060708') ciphertext = encryptor.encrypt(text) ciphertext = base64.b64encode(ciphertext) return ciphertext # 进行rsa加密 def rsaEncrypt(self,text, pubKey, modulus): text = text[::-1] #rs = int(text.encode('hex'), 16) ** int(pubKey, 16) % int(modulus, 16) rs = int(codecs.encode(text.encode('utf-8'),'hex_codec'), 16) ** int(pubKey, 16) % int(modulus, 16) return format(rs, 'x').zfill(256) # 将明文text进行两次aes加密获得密文encText def encrypted_request(self,text): modulus = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7' nonce = '0CoJUm6Qyw8W8jud' pubKey = '010001' text = json.dumps(text) secKey = self.createSecretKey(16) encText = self.aesEncrypt(self.aesEncrypt(text, nonce), secKey) encSecKey = self.rsaEncrypt(secKey, pubKey, modulus) data = { 'params': encText, 'encSecKey': encSecKey } return data

还有一个offset偏移量的参数,来限制显示不同页的评论。

def get_offset(self, offset): if offset == 0: text = {'rid': '', 'offset': '0', 'total': 'true', 'limit': '20', 'csrf_token': ''} else: text = {'rid': '', 'offset': '%s' % offset, 'total': 'false', 'limit': '20', 'csrf_token': ''} return text

爬取数据

接着我们可以向网页发出request请求,然后开始爬取数据啦。根据要求,需要爬取歌曲的热评以及全部评论,且热评只在第一页出现。因此写了两个函数来分别爬取热评以及全部评论,且由于后续需要对数据进行可视化分析,因此根据需要生成了txt文件以及csv文件。 爬取热门评论

def get_hot_comment(self): hot_comments_list = [] hot_comments_list.append(u"用户ID,用户昵称,用户头像地址,评论时间,点赞总数,评论内容\n") req = self.get_json_data(self.comment_url, offset=0) print("已连接热评") hot_comments = req['hotComments'] # 热门评论 print("共有%d条热门评论!\n" % len(hot_comments)) for item in hot_comments: comment = item['content'] # 评论内容 comments = comment.replace('\n','').replace('\t','').replace(' ','') print(comments) likedCount = int(item['likedCount']) # 点赞总数 comment_time = item['time']# 评论时间(时间戳) comment_times = comment_time/1000 publicTime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(comment_times)) # print(publicTime) userID = item['user']['userId'] # 评论者id nickname = item['user']['nickname'] # 昵称 avatarUrl = item['user']['avatarUrl'] # 头像地址 comment_info = str(userID) + u"," + nickname + u"," + avatarUrl + u"," + str( publicTime) + u"," + str(likedCount) + u"," + comments + u"\n" # print(comment_info) hot_comments_list.append(comment_info) with codecs.open("%s_hotcomments.csv"%(song_id), 'a', encoding='utf-8') as f: f.writelines(hot_comments_list)

爬取全部评论

def get_song_comment(self): '''某首歌下全部评论 ''' all_comments_list = [] # 存放所有评论 # all_comments_list.append(u"用户ID,用户昵称,用户头像地址,评论时间,点赞总数,评论内容\n") # 头部信息 comment_info = [] data = self.get_json_data(self.comment_url, offset=0) comment_count = data['total'] if comment_count < 20: comments = data['comments'] for item in comments: comment = item['content'] # 评论内容 comments = comment.replace('\n', '').replace('\t', '').replace(' ', '') likedCount = int(item['likedCount']) # 点赞总数 comment_time = item['time'] # 评论时间(时间戳)13位 要除以1000 comment_times = comment_time / 1000 publicTime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(comment_times)) # print(type(comment_time)) int userID = item['user']['userId'] # 评论者id nickname = item['user']['nickname'] # 昵称 avatarUrl = item['user']['avatarUrl'] # 头像地址 comment_info = str(userID) + u"," + nickname + u"," + avatarUrl + u"," + str( publicTime) + u"," + str(likedCount) + u"," + comments + u"\n" all_comments_list.append(comment_info) with codecs.open("%s_allcomments.csv"%(song_id), 'a', encoding='utf-8') as f: f.writelines(all_comments_list) if comment_count > 20: for offset in range(20, int(comment_count), 20): print("开始爬取第{}页".format(offset/20)) comment = self.get_json_data(self.comment_url, offset=offset) _comments = comment['comments'] for item in _comments: comment = item['content'] # 评论内容 comments = comment.replace('\n', '').replace('\t', '').replace(' ', '')#处理换行符以及空格 likedCount = int(item['likedCount']) # 点赞总数 comment_time = item['time'] # 评论时间(时间戳) comment_times = comment_time / 1000 publicTime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(comment_times)) userID = item['user']['userId'] # 评论者id nickname = item['user']['nickname'] # 昵称 avatarUrl = item['user']['avatarUrl'] # 头像地址 all_comments_list = str(userID) + u"," + nickname + u"," + avatarUrl + u"," + str( publicTime) + u"," + str(likedCount) + u"," + comments + u"\n" # all_comments_list.append(comment_info) with codecs.open("%s_allcomments.csv"%(song_id), 'a', encoding='utf-8') as f: f.writelines(all_comments_list) return all_comments_list

时间戳转换 爬取出的数据中,时间是以13位的时间戳呈现,因此要进行转换,通过网上的方法,13位的时间戳在转换时要先除以1000。

comment_time = item['time'] # 评论时间(时间戳) comment_times = comment_time / 1000 publicTime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(comment_times))

结巴中文分词

为了单独对评论内容进行分析,因此,对评论内容进行了单独的爬取。 中文文本的预处理过程可以分为分词->去停用词(包含空格、回车、标点符号等都算作停用词)->词频统计。 全模式会对已经分词出来的词再进行分词,一般情况下使用精确模式。如果对分词的结果不满意,可以添加自定义词典。使用停用词时,采用的是哈工大的停用词表。

from collections import Counter import jieba import io import sys sys.stdout = io.TextIOWrapper(sys.stdout.buffer,encoding='gb18030') song_id = input('please input song_id:') # 添加需要自定以的分词 jieba.add_word("wifi") jieba.add_word("小众") jieba.add_word("天冷") jieba.add_word("这首歌") # 创建停用词list def stopwordslist(filepath): stopwords = [line.strip() for line in open(filepath, 'r').readlines()] return stopwords # 对句子进行分词 def seg_sentence(sentence): sentence_seged = jieba.cut(sentence.strip()) stopwords = stopwordslist('stopwords.txt') # 这里加载停用词的路径 outstr = '' for word in sentence_seged: if word not in stopwords: if len(word) > 1 and word != '\r\n': outstr += word outstr += " " return outstr inputs = open('%s_allcomments_content.txt'%(song_id), 'r',encoding='utf-8') # 加载要处理的文件的路径 outputs = open('jieba_%s.txt'%(song_id), 'w',encoding='utf-8') # 加载处理后的文件路径 for line in inputs: line_seg = seg_sentence(line) # 这里的返回值是字符串 outputs.write(line_seg) outputs.close() inputs.close() # WordCount with open('jieba_%s.txt'%(song_id), 'r',encoding='utf-8') as fr: # 读入已经去除停用词的文件 data = jieba.cut(fr.read()) data = Counter(data) with open('%s_jieba.txt'%(song_id), 'w',encoding='utf-8') as fw: # 读入存储wordcount的文件路径 for k, v in data.most_common(500): fw.write('%s,%d\n' % (k, v))

可视化

词云图采用的是wordcloud包。 由于wordcloud本身的分配的颜色不太美观,因此我选择了对其添加背景图,根据背景图的颜色展示词云图:使用numpy包打开背景图并转化为数字矩阵,通过ImageColorGenerator从背景图片生成颜色值,从而使词云图颜色变为背景图颜色。 也可以使用tableau进行数据可视化。下面是我对评论时间以及热门评论的分析,以及对全部评论进行结巴分词后的词云图展示。

最后

曾经有人说过,如果你也曾在深夜哭泣第二天照常打起精神上班上课,如果你也曾被阴霾重重包围却对生活留了一丝缝隙让阳光照进,如果你曾迷惘,如果你曾受挫,如果你曾也大骂去他的生活,如果你曾也与看不见的敌人战斗过,如果你曾也想过一了百了,推荐中岛美嘉的《曾经我也想过一了百了》。 从词云图中,可以看到,出现最多的词语就是“加油”、“好好活着”。可见评论者更多的是从这首歌中得到力量。热评第一的是“我曾想死是因为,被说成是冷酷的人。想要被爱而哭泣,是因为尝到了人的温暖。我曾想死是因为,你美丽的笑了。一味想着死的事,一定是因为太过认真地活。我曾想死是因为,还未和你相遇。因为像你这样的人生于这世上,我稍稍喜欢这个世界了。” 新的和年轻的,总是美好的,哪怕充满未知。人能越活越年轻越活越自如想想真是概率太低且太难了。不过既然以后只有越来越老越来越沉重的份儿,可能真的要更珍惜现在吧。

转载请注明原文地址: https://www.6miu.com/read-5040617.html

最新回复(0)