爬取网易云评论

任务爬取网易云黄老板的shape of you下面赞超过1000的评论

网页爬取

本次任务的难点就在于网页爬取,可以结合知乎关于此问题的回答一起看

网页分析

打开网页之后切换评论的页数,可以看到网址的URL并没有变化,没有像豆瓣一样出现page=X,猜测是直接通过加载JavaScript数据包改变评论。
在这里插入图片描述
打开F12,刷新一下,选择NetWork,勾选XHR,经过分析,评论数据是由R_SO_4_…数据包发过来的。
在这里插入图片描述
选中这个数据包,我们分析一下。
这是一个POST数据包,对每一页评论URL没有变。服务器应该是用过请求的其他数据确定我们需要的是哪一页。
在这里插入图片描述
往下翻,到From Data,显然我们这两个参数是经过加密的,大概率就是我们在找的数据。
在这里插入图片描述
我们去看看对应的JavaScript请求,点击Initiator,可以看到对应的JavaScript请求,点击一下core_f69…
在这里插入图片描述
可以看到跳转到了Sources部分,代码不太方便看,可以点击一下左下角的{}符号
在这里插入图片描述
经过查找,发现我们要的params参数和enSecKey参数由一个bVj7c的变量提供的,而bVj7c是通过window.asrsea函数得到的,其共有四个参数
JSON.stringify(i8a),
brx9o([“流泪”, “强”]),
brx9o(Xs4w.md),
brx9o([“爱心”, “女孩”, “惊恐”, “大笑”])
(选这几个词来加密的程序员一定是个有故事的程序员~)
我们把断点打在13092(左击一下行号就可以设置断点)
在这里插入图片描述
现在点击一下网页评论的其他页可以看到对应的参数
在这里插入图片描述
按下esc键调出console,在console中依次输入四个参数,可以得到对应的值,经过对比,发现后三个为常数,而第一个参数通过改变offset来确定页数,每次变化20,从0开始变化。
在这里插入图片描述

参数获取

现在我们来实现一下window.asrsea得到我们要的params和enSecKey。
把代码下载下来后,找到window.asrsea位置。
简单分析一下,
function a实现生成长度为a的随机字符串;
function b是把a和b一起进行AES加密,iv设置为0102030405060708;
function c将a,b,c一起进行RSA加密
function d也就是我们要用的window.asrsea,可以由四个参数得到params和enSecKey
在这里插入图片描述
我们也用pycrypto模仿实现一下(可以搜一下愿意对应着看)
安装pycrypto模块报错的话,可以用

pip install -i https://pypi.douban.com/simple/ pycryptodome

代码:

class MusicSpider:

    def __init__(self):
        self.headers = {
            'accept' : "*/*",
            'origin' : "https://music.163.com",
            'Host': "music.163.com",
            'user-agent' : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36",
        }
        # 第二个参数
        self.second_param = "010001"
        # 第三个参数
        self.third_param = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
        # 第四个参数
        self.forth_param = "0CoJUm6Qyw8W8jud"

    def get_params(self, page):
        offset = str((page - 1) * 20)
        self.first_param = '{rid:"", offset:"%s", total:"%s", limit:"20", csrf_token:""}' % (offset, 'true')
        self.random_strs = self.generate_random_strs(16) # 生成长度为16的随机字符串
        # 两次AES加密之后得到params的值
        self.params = self.AES_encrypt(self.first_param, self.forth_param)
        self.params = self.AES_encrypt(self.params.decode('utf-8'), self.random_strs)

    def get_encSecKey(self):
        # RSA加密之后得到encSecKey的值
        self.encSecKey = self.RSAencrypt(self.random_strs, self.second_param, self.third_param)

    #生成随机字符串
    def generate_random_strs(self, length):
        string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
        random_strs = ""
        for i in range(length):
            temp = random.randint(0, len(string)-1)
            random_strs += list(string)[temp]
        return random_strs

    #AES加密
    def AES_encrypt(self, msg, key):
        # 如果不是16的倍数则进行填充(paddiing)
        padding = 16 - len(msg) % 16
        # 这里使用padding对应的单字符进行填充
        msg = msg + padding * chr(padding)
        # 用来加密或者解密的初始向量(必须是16位)
        iv = '0102030405060708'

        encryptor = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv.encode('utf-8'))
        # 加密后得到的是byte类型的数据
        encrypt_text = encryptor.encrypt(msg.encode('utf-8'))
        # 使用Base64进行编码,返回byte字符串
        encrypt_text = base64.b64encode(encrypt_text)
        return encrypt_text

    # RSA加密
    def RSAencrypt(self, randomstrs, key, f):
        # 随机字符串逆序排列
        string = randomstrs[::-1]
        # 将随机字符串转换成byte类型数据
        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)

数据分析

这部分与知乎分析json数据类似
回到Network 栏,找到Preview,可以看到,评论内容在comments下的content,点赞数在comments下的likedCount
在这里插入图片描述
将params和encSecKey作为数据,发送post请求,返回json文件

    def get_json(self, url):
        self.post = {
            'params' : self.params,
            'encSecKey': self.encSecKey,
        }
        try:
            self.response = requests.post(url, data=self.post, headers = self.headers)
            if self.response.status_code == 200:
                return self.response.json()
        except requests.ConnectionError:
            return None

数据存储

在得到的json文件中获取content和likedcount,当likedcount超过100就保存content

    def get_comments(self, url):
        f = open('./comments.txt', 'w', encoding='utf-8')
        self.get_params(1)
        self.get_encSecKey()
        data = self.get_json(url)
        page = data.get('total') // 20 + 1 if (data.get('total')%20) else 0
        for i in range(1, page):
            self.get_params(i)
            self.get_encSecKey()
            data = self.get_json(url)
            for comment in data.get("comments"):
                likedcount = comment.get('likedCount')
                content = comment.get("content")
                if likedcount > 100 :
                    f.write(content+'\n')
            print("第%d页抓取完毕"%i)
            time.sleep(5)

得到的评论做个词云叭
在这里插入图片描述

完整代码

from Crypto.Cipher import AES
import base64
import time
import requests
import random
import codecs
from urllib.parse import urlencode

class MusicSpider:

    def __init__(self):
        self.headers = {
            'accept' : "*/*",
            'origin' : "https://music.163.com",
            'Host': "music.163.com",
            'user-agent' : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36",
        }
        # 第二个参数
        self.second_param = "010001"
        # 第三个参数
        self.third_param = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
        # 第四个参数
        self.forth_param = "0CoJUm6Qyw8W8jud"

    def get_params(self, page):
        offset = str((page - 1) * 20)
        self.first_param = '{rid:"", offset:"%s", total:"%s", limit:"20", csrf_token:""}' % (offset, 'true')
        self.random_strs = self.generate_random_strs(16) # 生成长度为16的随机字符串
        # 两次AES加密之后得到params的值
        self.params = self.AES_encrypt(self.first_param, self.forth_param)
        self.params = self.AES_encrypt(self.params.decode('utf-8'), self.random_strs)
       
    def get_encSecKey(self):
        # RSA加密之后得到encSecKey的值
        self.encSecKey = self.RSAencrypt(self.random_strs, self.second_param, self.third_param)

    #生成随机字符串
    def generate_random_strs(self, length):
        string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
        random_strs = ""
        for i in range(length):
            temp = random.randint(0, len(string)-1)
            random_strs += list(string)[temp]
        return random_strs

    #AES加密
    def AES_encrypt(self, msg, key):
        # 如果不是16的倍数则进行填充(paddiing)
        padding = 16 - len(msg) % 16
        # 这里使用padding对应的单字符进行填充
        msg = msg + padding * chr(padding)
        # 用来加密或者解密的初始向量(必须是16位)
        iv = '0102030405060708'

        encryptor = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv.encode('utf-8'))
        # 加密后得到的是byte类型的数据
        encrypt_text = encryptor.encrypt(msg.encode('utf-8'))
        # 使用Base64进行编码,返回byte字符串
        encrypt_text = base64.b64encode(encrypt_text)
        return encrypt_text

    # RSA加密
    def RSAencrypt(self, randomstrs, key, f):
        # 随机字符串逆序排列
        string = randomstrs[::-1]
        # 将随机字符串转换成byte类型数据
        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)

    def get_json(self, url):
        self.post = {
            'params' : self.params,
            'encSecKey': self.encSecKey,
        }
        try:
            self.response = requests.post(url, data=self.post, headers = self.headers)
            if self.response.status_code == 200:
                return self.response.json()
        except requests.ConnectionError:
            return None

    def get_comments(self, url):
        f = open('./comments.txt', 'w', encoding='utf-8')
        self.get_params(1)
        self.get_encSecKey()
        data = self.get_json(url)
        page = data.get('total') // 20 + 1 if (data.get('total')%20) else 0
        for i in range(1, page):
            self.get_params(i)
            self.get_encSecKey()
            data = self.get_json(url)
            for comment in data.get("comments"):
                likedcount = comment.get('likedCount')
                content = comment.get("content")
                if likedcount > 100 :
                    f.write(content+'\n')
            print("第%d抓取完毕"%i)
            time.sleep(5)

if __name__ == "__main__":
	#要其他歌曲的话,改一下URL的R_SO_4_后面的歌曲id即可~
    url = "https://music.163.com/weapi/v1/resource/comments/R_SO_4_451703096?csrf_token="
    musicspider = MusicSpider()
    musicspider.get_comments(url)

### 爬取网易云音乐评论的Python代码示例 以下是一个基于 `requests` 和 `json` 的 Python 爬虫代码示例,用于爬取网易云音乐的评论数据。此代码假设用户已经了解如何绕过网易云音乐的加密机制(例如,通过分析其 API 接口和加密方式)[^1]。 ```python import requests import json # 定义请求头,模拟浏览器行为 headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Referer': 'https://music.163.com/' } # 定义加密参数(需根据实际接口分析得出) def get_encrypted_params(song_id): # 这里需要实现具体的加密逻辑,例如通过 JavaScript 解密算法反编译得到 params = { "rid": f"R_SO_4_{song_id}", # 歌曲ID "offset": "0", # 偏移量 "total": "true", # 是否获取总评论数 "limit": "20", # 每页评论数量 "csrf_token": "" # CSRF Token } # 加密逻辑省略,需根据实际情况补充 return params # 获取评论数据 def fetch_comments(song_id): url = "https://music.163.com/weapi/v1/resource/comments/R_SO_4_{}?csrf_token=".format(song_id) encrypted_params = get_encrypted_params(song_id) # 调用加密函数 response = requests.post(url, data=encrypted_params, headers=headers) if response.status_code == 200: comments_data = response.json() if comments_data['code'] == 200: return comments_data['comments'] return None # 主程序 if __name__ == "__main__": song_id = "123456" # 替换为实际歌曲ID comments = fetch_comments(song_id) if comments: for comment in comments: print(f"用户: {comment['user']['nickname']}") # 用户名 print(f"评论: {comment['content']}") # 评论内容 print(f"点赞数: {comment['likedCount']}") # 点赞数 print("-" * 40) ``` 上述代码中,`get_encrypted_params` 函数需要用户根据实际的 API 加密逻辑进行补充。通常,网易云音乐会使用复杂的加密算法(如 AES、RSA 等)来保护其接口,因此需要通过逆向工程分析其加密方式[^1]。 ### 存储和分析评论数据 为了存储和分析这些评论数据,可以将结果保存到文件或数据库中。例如,保存到 JSON 文件: ```python import json # 将评论数据保存到JSON文件 def save_to_json(comments, filename="comments.json"): with open(filename, "w", encoding="utf-8") as f: json.dump(comments, f, ensure_ascii=False, indent=4) # 示例调用 save_to_json(comments) ``` 此外,还可以对评论数据进行情感分析或其他统计分析,以提取有价值的信息。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值