美文网首页
python3爬取网易云音乐《我们》全部评论

python3爬取网易云音乐《我们》全部评论

作者: 海淀小天 | 来源:发表于2018-05-04 02:19 被阅读0次

    伴随着《后来的我们》的上映,这首歌的评论也是一路暴涨。如今让我们趁着这波秋风(虽然已经有点晚了),把这首歌的评论爬下来。虽然之前还打算做出词云可视化出来······然而,还是太懒了
    之前爬过豆瓣《霸王别姬》的影评做过这个了,就不想做了QAQ

    首先非常感谢之前大佬们贡献,没有他们,我肯定爬不下来这个的。其次这首歌一共12万条评论,但是实际上这部分代码只爬到630页也就是1万多条评论就报了一个错误,但是可以进行改进以得到所有评论。

    参考文章链接:https://www.jianshu.com/p/87c022b19669
    https://www.zhihu.com/question/36081767
    https://blog.csdn.net/qq_28304687/article/details/78678851

    • 分析网页
      打开PC端,搜索《我们》这首歌,打开开发者模式,选择Network
    image.png

    接下来我们来看一下返回正文,也就是Preview内容都包含了什么

    image.png

    事先声明一下:作者本次程序直接拿到了json格式的Comment的全部内容,并未对其进行进一步的解析。
    接下来我们看一下此次post请求所需要的各种参数:

    image.png

    parmas和encSecKey是经过加密算法产生的两个参数,幸运的是我们并不是很需要懂它的加密原理,已经有不少的大佬解析过网易云音乐的加密原理了。我们直接拿过来用就可以了。

    #如果想要详细了解这部分内容的话,请参考https://www.zhihu.com/question/36081767
    
    也可以参照这个完整的项目,不过是python2写的:https://blog.csdn.net/qq_28304687/article/details/78678851
    

    这里贴一下得到这两个参数的代码:

    # 首先生成长度为16的随机字符串作为密钥secKey
        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,因为secKey是在客户端上生成的,所以还需要对其进行RSA加密再传给服务端
        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
    

    到这里我们需要的最难拿到的的参数就已经得到了
    其他需要的参数还有headers,这个直接赋值headers里的就行了,还有一个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请求,然后拿到我们想要的网页内容了。
    请求是这样发出的:

        def get_post_req(self, url, data):
            try:
                req = requests.post(url, headers=self.headers, data=data)
                print("已连接上网易云音乐")
            except Exception as e:
                print (url)
                print(e)
            if(req==""):
                print("什么都没有抓取到")
            else:
                #print(req.json())
                #这里我们就得到了所有的json格式的评论了,并把它写进了txt文本
                with codecs.open(filename, 'a', encoding='utf-8') as f:
                    f.writelines(req.text)
            print("----------------")
            return req.json()
    

    接下来只需要把得到的json格式的网页源码进行解析就可以了,用re正则表达式匹配也好,用beautifulsoup解析也行,都不是很难。

    下面就贴出本次的全部代码吧:

    # -*-coding:utf-8-*-
    'author:guoya'
    import os
    import time
    import json
    import base64
    
    import requests
    
    import codecs
    from Crypto.Cipher import AES
    class CommentCrawl(object):
        '''评论的API封装成一个类,直接传入评论的API,再调用函数get_song_comment()和get_album_comment()即可分别获取歌曲和专辑的评论信息 '''
        def __init__(self,comment_url):
            self.comment_url = comment_url
            self.headers = {
                "Referer":"http://music.163.com/song?id=551816010",
                "User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.101 Safari/537.36",
            }
    
    
    
        # 生成长度为16的随机字符串作为密钥secKey
        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,因为secKey是在客户端上生成的,所以还需要对其进行RSA加密再传给服务端
        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
    
        def get_post_req(self, url, data):
            try:
                req = requests.post(url, headers=self.headers, data=data)
                print("已连接上网易云音乐")
            except Exception as e:
                print (url)
                print(e)
            if(req==""):
                print("什么都没有抓取到")
            else:
                #print(req.json())
                #这里我们就得到了所有的json格式的评论了,并把它写进了txt文本
                with codecs.open(filename, 'a', encoding='utf-8') as f:
                    f.writelines(req.text)
            print("----------------")
            return req.json()
    
        # 偏移量
        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
    
        # 得到json格式的评论
        def get_json_data(self,url,offset):
            text = self.get_offset(offset)
            data = self.encrypted_request(text)
            json_text = self.get_post_req(url, data)
            return json_text
    
        def get_song_comment(self):
            '''某首歌下全部评论 '''
    
            comment_info = []
            data = self.get_json_data(self.comment_url, offset=0)
            comment_count = data['total']
            #comment_count = 3
            if comment_count:
                comment_info.append(data)
            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)
                    comment_info.append(comment)
            return comment_info
    
        def get_album_comment(self, comment_count):
            '''某专辑下全部评论 '''
    
            album_comment_info = []
            if comment_count:
                for offset in range(0, int(comment_count), 20):
                    comment = self.get_json_data(self.comment_url, offset=offset)
                    album_comment_info.append(comment)
            return album_comment_info
    
    def save_to_file(list, filname):
        with codecs.open(filename, 'a', encoding='utf-8') as f:
            f.writelines(list)
    
        print("写入文件成功!")
    
    
    start_time = time.time()
    filename = "后来的我们.txt"
    comment_url='http://music.163.com/weapi/v1/resource/comments/R_SO_4_551816010?csrf_token='
    all_coment=[]
    craw_song_pinglun=CommentCrawl(comment_url)
    print("开始")
    all_coment=craw_song_pinglun.get_song_comment()
    print("--------------")
    
    
    
    end_time = time.time()  # 结束时间
    print("程序耗时%f秒." % (end_time - start_time))
    print("已完成")
    

    本次程序爬取到630页的报了一个错误。建议优化及改进方案:爬完每页之后sleep几秒,或者尝试多线程爬取,或者换ip。我已经不太想继续研究这个了 。爬取内容和源码已上传到百度云盘。链接:https://pan.baidu.com/s/1gJxxzxFOMQWII4ORNh4f5A) 密码:kmjj

    image.png

    后面这个就和题目无关了。


    word_cloud_霸王别姬.jpg yuji.jpg

    相关文章

      网友评论

          本文标题:python3爬取网易云音乐《我们》全部评论

          本文链接:https://www.haomeiwen.com/subject/vmlrrftx.html