ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python爬虫实战:网易云音乐评论抓取与情感分析全流程

Python爬虫实战:网易云音乐评论抓取与情感分析全流程 简介本资源是一套基于Python的网易云音乐评论数据采集与情感分析完整实践项目面向计算机、人工智能、通信工程等专业的在校学生、教师及初学者解决音乐平台用户行为数据获取、文本情感判别与多维结果可视化的核心问题适用于课程设计、毕业设计、数据分析入门及AI应用实战。压缩包共122个文件约18.67MB包含14个核心Python脚本含爬虫、情感分析、数据清洗与Web服务模块、8个HTML前端页面、13个JavaScript交互逻辑文件、7个CSS样式文件及26张可视化图表截图体现前后端协同与数据驱动展示能力。已有243人学习下载项目源自高分毕设答辩平均96分所有代码均经实测运行通过附详细注释与README说明支持快速部署与二次开发。读者可直接复现从评论抓取、用户画像构建、实时情感分析到网页动态可视化的全流程并获得结构清晰的工程目录、可调试的本地服务环境及面向实际场景的排错参考。 之前给自己定了个小目标用Python爬虫把网易云音乐某首歌的评论区整个抓下来再对评论做情感分析顺带把用户信息和分析结果可视化。折腾了几天踩了不少坑最后总算是跑通了。这篇文章就把整个项目的完整思路和代码整理出来包括接口分析、参数加密、分页翻页、情感分析、可视化这几块想练爬虫和数据分析的朋友可以直接参考。先说这个项目适合谁。如果你已经会Python基础语法requests能装、能简单用用想练练真正的爬虫而不只是爬个静态网页那这个案例的难度刚刚好——它卡在“会一点但没做过完整项目”的阶段做完了会有很明显的成就感。如果你是纯零基础建议先把requests、json、pandas的基本用法过一遍再回来看不然中间有些步骤会卡住。整个项目不依赖重型框架跑起来也不吃配置普通笔记本完全没问题。1. 项目整体设计与技术选型1.1 这个项目到底在做什么一句话说清楚输入一首歌的ID程序自动抓取这首歌在网易云音乐下的所有评论把评论内容、评论用户的昵称、VIP类型、所在地等字段存下来然后对评论内容做情感分析最后用图表把评论高频词、情感分布、用户画像、时间趋势展示出来。拆开来看整个需求其实可以分成四层数据采集层模拟前端请求拿到评论接口返回的JSON数据数据解析层从JSON中提取评论内容、用户信息等字段清洗后保存数据分析层对评论内容做分词、停用词过滤、情感打分数据展示层用图表把统计结果可视化形成一份类似“歌曲评论分析报告”的东西这个流程本身不复杂难点集中在第一层——网易云的评论接口是加密的请求体里有两个非常碍眼的参数params和encSecKey。不搞定它们接口永远返回错误。这也是很多入门爬虫玩家卡住的地方。1.2 为什么选这套技术组合选型这块我踩过几次坑说说最终方案的逻辑requests 而不是 scrapy单首歌的评论量大概在几千到几万条requests 完全够用。scrapy 适合大规模分布式抓取对这个项目来说属于杀鸡用牛刀而且爬虫框架的调试成本更高。pycryptodome 处理 AES 和 RSA网易云的加密参数是基于 AES-CBC 和 RSA 混合实现的pycryptodome 是目前 Python 里做对称加密最顺手的库直接解决补位、填充等底层问题。jieba SnowNLP 做情感分析SnowNLP 自带一个情感分析模型直接调用sentiments属性就能拿到 0~1 的情感分数不需要自己训练模型对练手项目非常合适。jieba 做中文分词是事实标准没有第二个更好的选择。pyecharts 做可视化它生成的是交互式 HTML 图表可以鼠标悬停看数据还能组合成多图表页面用来做“可视化大屏”或汇报演示非常合适。相比之下 matplotlib 是静态图交互性和颜值都差一截。这套组合有一个明显的好处每个库都是 Python 数据生态里的主流方案学会了以后可以平移到其他项目里而不是只为了这一个项目服务。1.3 整体流程拆解整个项目的执行顺序是固定的我在做的时候习惯先用文字列一遍再动手写代码在网页端定位评论接口确认URL和请求方式分析请求参数搞清楚params和encSecKey的生成规则用Python实现加密函数成功请求第一页评论根据接口返回的cursor字段写翻页循环抓取全量评论把评论和用户信息写入CSV文件读取CSV对评论内容做分词、情感分析用pyecharts生成各类图表组合成报告页面这个顺序非常关键。很多人一上来就写爬虫爬到一半才意识到需要加密处理回头再补代码结构就会变得很乱。先把流程拆好后面每个环节都是填坑的过程。2. 爬虫核心评论接口分析与加密参数实现2.1 怎么定位网易云的评论接口首先在浏览器打开网易云音乐网页版找到任意一首歌的页面按 F12 打开开发者工具切到 Network 面板再勾选 Fetch/XHR 这个过滤条件。然后往下滚动评论区触发评论加载这时候Network面板里会冒出一个https://music.163.com/weapi/v1/resource/comments/R_SO_4_186016之类的请求。这个URL里面有几个关键信息R_SO_4是资源类型标识R代表 ResourceSO代表 Song4是类型编号。如果是歌单就是R_PL_3如果是专辑就是R_AL_3。后面的数字就是歌曲ID。请求方式是 POST不是 GET所以评论数据不能靠改URL直接拿到。点开请求体会发现表单里只有两个字段params和encSecKey一长串看不懂的密文。这说明网易云前端在提交之前把正常的请求参数做了加密处理。后端只认这种加密后的表单。2.2 加密参数 params 和 encSecKey 是怎么生成的这套加密逻辑在开源社区已经有很多参考实现属于前端公开的安全防护方案。核心流程可以概括为构造一个JSON字符串里面包含rid、pageNo、pageSize、cursor、csrf_token等正常参数。先用一个固定密钥0CoJUm6Qyw8W8jud对JSON字符串做 AES-CBC 加密得到第一次加密结果。生成一个16位随机字符串记为secKey。用secKey作为新密钥对第一次的加密结果再做一次 AES-CBC 加密得到最终的params。用网易云公开的 RSA 公钥对secKey加密得到encSecKey。为什么要加密两层我理解是这样第一层用固定密钥加密是为了隐藏真实请求内容第二层用随机密钥加密是为了让每次请求的params都不一样防止别人用同一个密文直接重放。encSecKey的存在就是为了把随机的secKey安全地传给后端后端再用私钥解开拿到真正的secKey然后逆推回去。这是一个非常典型的 AES RSA 混合加密方案。第5步里RSA加密有个比较隐蔽的细节并不是直接把secKey字符串丢进RSA算法而是先把字符串倒序再转成十六进制整数做模幂运算最后补位到256位十六进制字符串。这个细节很多网上抄的代码都会漏掉漏了之后请求会直接失败。2.3 AES RSA 加密代码实现下面这段代码是我在项目里实际使用的加密函数整体参考了行业通用的实现思路注释写得很详细# -*- coding: utf-8 -*- 网易云音乐 weapi 接口加密参数生成 核心思路 1. 明文先用固定密钥 NONCE 做一次 AES-CBC 加密 2. 生成 16 位随机字符串 secKey 3. 用 secKey 对第一步结果再做一次 AES-CBC 加密得到 params 4. 用网易云公开的 RSA 公钥对 secKey 加密得到 encSecKey import base64 import binascii import json import random import string from Crypto.Cipher import AES from Crypto.Util.Padding import pad # 以下均为前端公开的固定参数整个方案在开源社区中有大量参考实现 MODULUS ( 00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725 152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e031 2ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b4 24d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a2 2b8e7 ) PUB_KEY 010001 NONCE 0CoJUm6Qyw8W8jud AES_IV 0102030405060708 def aes_encrypt(text, key): AES-CBC 加密返回 Base64 字符串 cipher AES.new(key.encode(utf-8), AES.MODE_CBC, AES_IV.encode(utf-8)) # pad 自动补齐到 AES 块大小的整数倍 raw pad(text.encode(utf-8), AES.block_size) encrypted cipher.encrypt(raw) return base64.b64encode(encrypted).decode(utf-8) def rsa_encrypt(sec_key): 用 RSA 公钥加密随机密钥返回 256 位十六进制字符串 sec_key sec_key[::-1] # 先倒序这是网易云加密流程里的一个坑 # 字符串转十六进制整数再做模幂运算 rs pow( int(binascii.hexlify(sec_key.encode(utf-8)), 16), int(PUB_KEY, 16), int(MODULUS, 16), ) return format(rs, x).zfill(256) def generate_params(song_id, page_no1, page_size20, cursor-1): 生成请求体里的 params 和 encSecKey :param song_id: 歌曲 ID :param page_no: 页码从 1 开始 :param page_size: 每页数量评论接口实测上限 100 :param cursor: 游标第一页传 -1之后用上次返回的 cursor # 16 位随机字符串每次请求都会变化 sec_key .join(random.choices(string.ascii_letters string.digits, k16)) text json.dumps({ rid: fR_SO_4_{song_id}, threadId: fR_SO_4_{song_id}, pageNo: str(page_no), pageSize: str(page_size), cursor: cursor, offset: str((page_no - 1) * page_size), orderType: 1, csrf_token: , }) # 两层 AES 加密 first_enc aes_encrypt(text, NONCE) params aes_encrypt(first_enc, sec_key) return params, rsa_encrypt(sec_key)这段代码有几个地方需要特别留意第一MODULUS字符串很长手打很容易打错。建议从开源项目的公开代码里复制或者自己在浏览器里从JS文件里提取。第二rsa_encrypt里那行sec_key[::-1]不能省否则后端解不开。第三offset这个字段要跟上pageNo对应不然翻页到后面会错乱。2.4 请求头、Cookie 与翻页控制有了加密参数接下来就是构造请求。这个接口对请求头的要求不算苛刻但User-Agent和Referer最好都带上不然容易触发风控。我自己用的请求头是这样headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Referer: https://music.163.com/, Origin: https://music.163.com, Content-Type: application/x-www-form-urlencoded, }Cookie 方面浏览器里登录状态下复制过来的完整 Cookie 可以直接用。如果你只是想抓评论不要求登录态那么 Cookie 里只要带上必要的标识即可甚至有些情况下不带 Cookie 也能返回数据。但为了稳定建议还是从浏览器里复制一份完整 Cookie 填进去省得请求被拦截。翻页逻辑是这个项目的重头戏。网易云评论接口的翻页不是单纯靠pageNo一直加它有一个游标字段cursor。第一次请求时cursor传-1之后每一次都要把上一轮返回结果里的cursor字段值填进下一次请求否则容易出现评论重复或者漏数据。核心的爬取函数我写成这样import time import requests def get_comments(song_id, page_size100, max_pagesNone): 循环获取某一首歌的评论 :param song_id: 歌曲 ID :param page_size: 每页数量 :param max_pages: 最多翻多少页None 表示翻到没有为止 :return: 评论列表 url fhttps://music.163.com/weapi/v1/resource/comments/R_SO_4_{song_id} comments [] cursor -1 page_no 1 while True: # 每次请求都生成新的加密参数 params, enc_sec_key generate_params( song_id, page_nopage_no, page_sizepage_size, cursorcursor ) data { params: params, encSecKey: enc_sec_key, } resp requests.post(url, headersheaders, datadata, timeout10) result resp.json() # 接口异常时一般会返回 code ! 200 if result.get(code) ! 200: print(f第 {page_no} 页请求失败: {result}) break page_comments result.get(comments, []) if not page_comments: print(没有更多评论了) break comments.extend(page_comments) print(f第 {page_no} 页完成本页 {len(page_comments)} 条累计 {len(comments)} 条) # 更新游标这行很关键 cursor result.get(cursor, -1) # 如果这一页不满一页说明已经到底了 if len(page_comments) page_size: print(已到达最后一页) break if max_pages and page_no max_pages: break page_no 1 time.sleep(1) # 控制频率避免给服务器造成压力 return comments这里我特意在每次请求之间加了time.sleep(1)。很多人为了快会把延时去掉结果请求几页之后直接被服务器拒绝。爬虫不是越快越好控制频率反而能让你把数据完整抓完。2.5 评论数据字段与解析接口返回的JSON结构大致是这样{ comments: [ { commentId: 123456789, content: 这首歌真的听了十年, p a hrefhttps://download.csdn.net/download/m0_73728511/88735280 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p
返回列表