网易云音乐爬虫 & 数据可视化分析

  • 1. 数据爬取
    • 1.1 评论爬取
    • 1.2 用户信息爬取
  • 2 数据清洗 & 可视化
    • 歌评文本分析

个人博客:Archiew’s blog
源码:https://github.com/Archiewyq/music_163 欢迎 star (●’◡’●)

1. 数据爬取

爬虫部分主要是调用官方API,本次用到的API主要有两个:

获取评论:
http://music.163.com/api/v1/resource/comments/R_SO_4_{歌曲ID}?limit={每页限制数量}&offset={评论数总偏移}

获取评论对应用户的信息:
https://music.163.com/api/v1/user/detail/{用户ID}

工具:
Python3.6
sublime3
MySQL(数据存储)
scrapy(数据清洗)
pyecharts(可视化工具库)

* 关于网易云音乐官方API,后期会整理一下做个汇总放在GitHub上。

1.1 评论爬取

实际操作过程中,网易云官方对于API的请求是有限制的,有条件的可以采用更换代理IP来防反爬,本次采用的是单线程爬取,所以IP封的并不太频繁,后面会对代码进行重构,实现多线程+更换IP来加快爬取速度。

根据获取评论的API,请求URL有3个可变部分:歌曲ID、每页限制数limit和评论总偏移量offset,通过API分析得知:当offeset=0时,返回json数据中包含有评论总数量total,所以根据API可设计爬虫如下:

# -*- coding:utf8 -*-
# python3.6
from urllib import request
import json
import pymysql
from datetime import datetime
import reROOT_URL = 'http://music.163.com/api/v1/resource/comments/R_SO_4_%s?limit=%s&offset=%s'
LIMIT_NUMS = 50    # 每页限制爬取数
DATABASE = ''    # 数据库名
TABLE = ''   # 数据库表名
# 数据表设计如下:
'''
id(int)             commentId(varchar)
content(text)       likedCount(int)
userId(varchar) time(datetime)
'''
PATTERN = re.compile(r'[\n\t\r\/]') # 替换掉评论中的特殊字符以防插入数据库时报错def getData(url):if not url:return None, Noneheaders = {"User-Agent": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36',"Host": "music.163.com",
}print('Crawling>>> ' + url)try:req = request.Request(url, headers=headers)content = request.urlopen(req).read().decode("utf-8")js = json.loads(content)total = int(js['total'])datas = []for c in js['comments']:data = dict()data['commentId'] = c['commentId']data['content'] = PATTERN.sub('', c['content'])data['time'] = datetime.fromtimestamp(c['time']//1000)data['likedCount'] = c['likedCount']data['userId'] = c['user']['userId']datas.append(data)return total, datasexcept Exception as e:print('Down err>>> ', e)passdef saveData(data):if not data:return Noneconn = pymysql.connect(host='localhost', user='****', passwd='****', db='****', charset='utf8mb4') # 注意字符集要设为utf8mb4,以支持存储评论中的emoji表情cursor = conn.cursor()sql = 'insert into ' + TABLE + ' (id,commentId,content,likedCount,time,userId) VALUES (%s,%s,%s,%s,%s,%s)'for d in data:try:cursor.execute('SELECT max(id) FROM '+TABLE)id_ = cursor.fetchone()[0]cursor.execute(sql, (id_+1,d['commentId'], d['content'], d['likedCount'], d['time'], d['userId']))conn.commit()except Exception as e:print('mysql err>>> ',d['commentId'],e)passcursor.close()conn.close()  if __name__ == '__main__':songId = input('歌曲ID:').strip()total,data = getData(ROOT_URL%(songId, LIMIT_NUMS, 0))saveData(data)if total:for i in range(1, total//EVERY_PAGE_NUMS+1):_, data = getData(ROOT_URL%(songId, LIMIT_NUMS, i*(LIMIT_NUMS)))saveData(data)

以上代码实现了单线程爬取网易云音乐某首歌曲的评论并存储进数据库(在这里其实有个坑!!!不过不影响,后面会讲到)。实际上,API返回的不仅仅包含代码中所提到的信息,具体可自行测试,我们还想要得到评论对应的用户的具体信息,但是这个API返回的用户信息不全面,所以接下来,针对评论对应的用户信息进行抓取。

1.2 用户信息爬取

根据获取用户信息的API,请求URL有1个可变部分:用户ID,前一部分已经将每条评论对应的用户ID也存储下来,这里只需要从数据库取用户ID并抓取信息即可,所以根据API可设计爬虫如下:

# -*- coding:utf8 -*-
# python3.6
from urllib import request
import json
import pymysql
import reROOT_URL = 'https://music.163.com/api/v1/user/detail/'
DATABASE = '****'
TABLE_USERS = '****'
TABLE_COMMENTS = '****'
# 数据表设计如下:
'''
id(int)             userId(varchar)
gender(char)        userName(varchar)
age(int)            level(int)
city(varchar)       sign(text)
eventCount(int) followedCount(int)
followsCount(int)   recordCount(int)
avatar(varchar)
'''
PATTERN = re.compile(r'[\n\t\r\/]') # 替换掉签名中的特殊字符以防插入数据库时报错def getData(url):if not url:return Noneprint('Crawling>>> ' + url)try:req = request.Request(url, headers=headers)content = request.urlopen(req).read().decode("utf-8")js = json.loads(content)data = {}if js['code'] == 200:data['userId'] = js['profile']['userId']data['userName'] = js['profile']['nickname']data['avatar'] = js['profile']['avatarUrl']data['gender'] = js['profile']['gender']if int(js['profile']['birthday'])<0:data['age'] = 0else:data['age'] =(2018-1970)-(int(js['profile']['birthday'])//(1000*365*24*3600))if int(data['age'])<0:data['age'] = 0data['level'] = js['level']data['sign'] = PATTERN.sub(' ', js['profile']['signature'])data['eventCount'] = js['profile']['eventCount']data['followCount'] = js['profile']['follows']data['fanCount'] = js['profile']['followeds']data['city'] = js['profile']['city']data['recordCount'] = js['listenSongs']except Exception as e:print('Down err>>> ', e)passreturn Nonedef saveData(data):if not data:return Noneconn = pymysql.connect(host='localhost', user='****', passwd='****', db=DATABASE, charset='utf8mb4') # 注意字符集要设为utf8mb4,以支持存储签名中的emoji表情cursor = conn.cursor()sql = 'insert into ' + TABLE + ' (id,userName,gender,age,level,city,sign,eventCount,followsCount,followedCount,recordCount,avatar,userId) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)'try:cursor.execute('SELECT max(id) FROM '+TABLE_USERS)id_ = cursor.fetchone()[0]cursor.execute(sql, (id_+1,data['userName'],data['gender'],data['age'],data['level'],data['city'],data['sign'],data['eventCount'],data['followsCount'],data['followedCount'],data['recordCount'],data['avatar'],data['userId']))conn.commit()except Exception as e:print('mysql err>>> ',data['userId'],e)passfinally:cursor.close()conn.close()  def getID():conn = pymysql.connect(host='localhost', user='****', passwd='****', db=DATABASE, charset='utf8mb4')cursor = conn.cursor()sql = 'SELECT userId FROM '+TABLE_COMMENTStry:cursor.execute(sql)res = cursor.fetchall()return resexcept Exception as e:print('get err>>> ', e)passfinally:cursor.close()conn.close()return Noneif __name__ == '__main__':usersID = getID()for i in usersID:data = getData(ROOT_URL+i[0].strip())saveData(data)

以上代码实现了单线程爬取网易云音乐用户信息并存储进数据库。至此,已经完成了歌曲评论和对应用户信息的抓取。接下来,对抓取到的数据进行清洗及可视化分析。

2 数据清洗 & 可视化

关于数据的清洗,实际上在上一部分抓取数据的过程中已经做了一部分,包括:后台返回的空用户信息、重复数据的去重等。除此之外,还要进行一些清洗:用户年龄错误、用户城市编码转换等。

关于数据的去重,评论部分可以以sommentId为数据库索引,利用数据库来自动去重;用户信息部分以用户ID为数据库索引实现自动去重。

API返回的用户年龄一般是时间戳的形式(以毫秒计)、有时候也会返回一个负值或者一个大于当前时间的值,暂时没有找到这两种值代表的含义,故而一律按0来处理。

API返回的用户信息中,城市分为province和city两个字段,本此分析中只保存了city字段。实际上字段值是一个城市code码,具体对照在这里下载。

在这部分,利用Python的数据处理库pandas进行数据处理,利用可视化库pyecharts进行数据可视化。处理代码如下:

# -*- coding:utf8 -*-
# python3.6
import pandas as pd
import pymysql
from pyecharts import Bar,Pie,Line,Scatter,MapTABLE_COMMENTS = '****'
TABLE_USERS = '****'
DATABASE = '****'conn = pymysql.connect(host='localhost', user='****', passwd='****', db=DATABASE, charset='utf8mb4')
sql_users = 'SELECT id,gender,age,city FROM '+TABLE_USERS
sql_comments = 'SELECT id,time FROM '+TABLE_COMMENTS
comments = pd.read_sql(sql_comments, con=conn)
users = pd.read_sql(sql_users, con=conn)# 评论时间(按天)分布分析
comments_day = comments['time'].dt.date
data = comments_day.id.groupby(comments_day['time']).count()
line = Line('评论时间(按天)分布')
line.use_theme('dark')
line.add('',data.index.values,data.values,is_fill=True,
)
line.render(r'./评论时间(按天)分布.html')
# 评论时间(按小时)分布分析
comments_hour = comments['time'].dt.hour
data = comments_hour.id.groupby(comments_hour['time']).count()
line = Line('评论时间(按小时)分布')
line.use_theme('dark')
line.add('',data.index.values,data.values,is_fill=True,
)
line.render(r'./评论时间(按小时)分布.html')
# 评论时间(按周)分布分析
comments_week = comments['time'].dt.dayofweek
data = comments_week.id.groupby(comments_week['time']).count()
line = Line('评论时间(按周)分布')
line.use_theme('dark')
line.add('',data.index.values,data.values,is_fill=True,
)
line.render(r'./评论时间(按周)分布.html')# 用户年龄分布分析
age = users[users['age']>0]   # 清洗掉年龄小于1的数据
age = age.id.groupby(age['age']).count() # 以年龄值对数据分组
Bar = Bar('用户年龄分布')
Bar.use_theme('dark')
Bar.add('',age.index.values,age.values,is_fill=True,
)
Bar.render(r'./用户年龄分布图.html') # 生成渲染的html文件# 用户地区分布分析
# 城市code编码转换
def city_group(cityCode):city_map = {'11': '北京','12': '天津','31': '上海','50': '重庆','5e': '重庆','81': '香港','82': '澳门','13': '河北','14': '山西','15': '内蒙古','21': '辽宁','22': '吉林','23': '黑龙江','32': '江苏','33': '浙江','34': '安徽','35': '福建','36': '江西','37': '山东','41': '河南','42': '湖北','43': '湖南','44': '广东','45': '广西','46': '海南','51': '四川','52': '贵州','53': '云南','54': '西藏','61': '陕西','62': '甘肃','63': '青海','64': '宁夏','65': '新疆','71': '台湾','10': '其他',}return city_map[cityCode[:2]]city = users['city'].apply(city_group)
city = city.id.groupby(city['city']).count()
map_ = Map('用户地区分布图')
map_.add('',city.index.values,city.values,maptype='china',is_visualmap=True,visual_text_color='#000',is_label_show=True,
)
map_.render(r'./用户地区分布图.html')

以上,是对抓取到的数据采用可视化库pyecharts进行可视化分析,得到的结果如下:

评论时间按周分布图可以看出,评论数在一周当中前面较少,后面逐渐增多,这可以解释为往后接近周末,大家有更多时间来听听歌、刷刷歌评,而一旦周末过完,评论量马上下降(周日到周一的下降过渡),大家又回归到工作当中。

评论时间按小时分布图可以看出,评论数在一天当中有两个小高峰:11点-13点和22点-0点。这可以解释为用户在中午午饭时间和晚上下班(课)在家时间有更多的时间来听歌刷评论,符合用户的日常。至于为什么早上没有出现一个小高峰,大概是早上大家都在抢时间上班(学),没有多少时间去刷评论。

用户年龄分布图可以看出,用户大多集中在14-30岁之间,以20岁左右居多,除去虚假年龄之外,这个年龄分布也符合网易云用户的年龄段。图中可以看出28岁有个高峰,猜测可能是包含了一些异常数据,有兴趣的化可以做进一步分析。

用户地区分布图可以看出,用户涵盖了全国各大省份,因为中间数据(坑)的缺失,并没有展现出哪个省份特别突出的情况。对别的歌评(完全数据)的可视化分析,可以看出明显的地区分布差异。

** 接下来说说前面提到的!!!
细心观察评论数(按天)分布那张图,发现2017年到2018年间有很大一部分数据缺失,这实际上是因为在数据抓取过程中出现的问题。研究了一下发现,根据获取歌曲评论的API,实际上每首歌最多只能获得2w条左右(去重后)的评论,对于评论数超过2w的歌曲,只能获得前后(日期)各1w条评论,而且这个限制对于网易云官网也是存在的,具体表现为:对一首评论数超过2w的歌,如果一直往后浏览评论,会发现从第500页(网页端网易云每页20条评论)往后,后台返回的内容和第500页完全一样,从后往前同理。这应该是官方后台做了限制,连自家也不放过。。。

此次分析只是对某一首歌曲评论时间、用户年龄/地区分布进行的,实际上抓取到的信息不仅仅在于此,可以做进一步分析(比如利用评论内容进行文本内容分析等),这部分,未来会进一步分析。当然也可以根据自己情况对不同歌曲进行分析。


2018-12-17

歌评文本分析

评论的文本分析做了两部分:情感分析和词云生成。
情感分析采用Python的文本分析库snownlp,代码如下:

# -*- coding:utf8 -*-
# python3.6import numpy as np
import pymysql
from snownlp import SnowNLP
from pyecharts import BarTABLE_COMMENTS = '****'
DATABASE = '****'
SONGNAME = '****'def getText():conn = pymysql.connect(host='localhost', user='root', passwd='root', db=DATABASE, charset='utf8')sql = 'SELECT id,content FROM '+TABLE_COMMENTStext = pd.read_sql(sql%(SONGNAME), con=conn)return textdef getSemi(text):text['content'] = text['content'].apply(lambda x:round(SnowNLP(x).sentiments, 2))semiscore = text.id.groupby(text['content']).count()bar = Bar('评论情感得分')bar.use_theme('dark')bar.add('',y_axis = semiscore.values,x_axis = semiscore.index.values,is_fill=True,)bar.render(r'情感得分分析.html')text['content'] = text['content'].apply(lambda x:1 if x>0.5 else -1)semilabel = text.id.groupby(text['content']).count()bar = Bar('评论情感标签')bar.use_theme('dark')bar.add('',y_axis = semilabel.values,x_axis = semilabel.index.values,is_fill=True,)bar.render(r'情感标签分析.html')

结果:

词云生成采用jieba分词库分词,wordcloud生成词云,代码如下:

from wordcloud import WordCloud
import matplotlib.pyplot as plt
plt.style.use('ggplot')
plt.rcParams['axes.unicode_minus'] = Falsedef getWordcloud(text):text = ''.join(str(s) for s in text['content'] if s)word_list = jieba.cut(text, cut_all=False)stopwords = [line.strip() for line in open(r'./StopWords.txt', 'r').readlines()]  # 导入停用词clean_list = [seg for seg in word_list if seg not in stopwords] #去除停用词clean_text = ''.join(clean_list)# 生成词云cloud = WordCloud(font_path = r'C:/Windows/Fonts/msyh.ttc',background_color = 'white',max_words = 800,max_font_size = 64)word_cloud = cloud.generate(clean_text)# 绘制词云plt.figure(figsize=(12, 12))plt.imshow(word_cloud)plt.axis('off')plt.show()if __name__ == '__main__':text = getText()getSemi(text)getWordcloud(text)


中文停用词下载

Todo:

  • 对评论内容做进一步分析 2018.12.17
  • 代码重构,增加多线程等 2018.12.19
  • 代码开源在Github上 2018.12.19

网易云音乐爬虫 数据可视化分析相关推荐

  1. Python网易云音乐爬虫大数据分析可视化系统——大屏数据可视化开发之路

    介绍 现在比较流行的大数据数据可视化都是大屏,有钱的人会使用阿里云全家桶的DataV或者商业化的大屏解决方案,但是在国内还是小公司比较多,本人50年大数据开发经验,精通数据可视化,曾经处理过百万亿级别 ...

  2. 网易云音乐爬虫实战——肖战《红梅赞》下评论数据挖掘与分析

    网易云音乐爬虫实战--肖战<红梅赞>下评论数据挖掘与分析 前言 本章工具 数据挖掘部分 1.获取歌曲评论 2.根据ID获取用户信息 数据分析部分 1.评论数时间分布 2.评论内容词云 3. ...

  3. python爬虫网易云音乐最热评论并分析_Python3实现爬虫抓取网易云音乐的热门评论分析(图)...

    这篇文章主要给大家介绍了关于Python3实战之爬虫抓取网易云音乐热评的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧. ...

  4. Java语言开发在线音乐推荐网 音乐推荐系统 网易云音乐爬虫 基于用户、物品的协同过滤推荐算法 SSM(Spring+SpringMVC+Mybatis)框架 大数据、人工智能、机器学习项目开发

    Java语言开发在线音乐推荐网 音乐推荐系统 网易云音乐爬虫 基于用户.物品的协同过滤推荐算法 SSM(Spring+SpringMVC+Mybatis)框架 大数据.人工智能.机器学习项目开发Mus ...

  5. 关于网易云音乐爬虫的api接口?

    抓包能力有限,分析了一下网易云音乐的一些api接口,但是关于它很多post请求都是加了密,没有弄太明白.之前在知乎看到过一个豆瓣工程师写的教程,但是被投诉删掉了,请问有网友fork了的吗?因为我觉得他 ...

  6. go post 参数_go语言实现网易云音乐爬虫

    前言 最近在学习go,学习一门语言最好的方式就是实践,之前学习python也是从爬虫入手,现在使用go语言写一个网易云音乐的爬虫,下面会简单介绍开发的过程,代码是初学者的水平,欢迎吐槽. 本项目git ...

  7. python爬虫实例网易云-Python3爬虫实例之网易云音乐爬虫

    本篇文章给大家带来的内容是Python3爬虫实例之网易云音乐爬虫.有一定的参考价值,有需要的朋友可以参考一下,希望对你们有所帮助. 此次的目标是爬取网易云音乐上指定歌曲所有评论并生成词云 具体步骤: ...

  8. 【博学谷学习记录】超强总结,用心分享 |产品经理-从盈利模式和推广方法对酷我英语和网易云音乐进行竞品分析

    [博学谷学习记录]超强总结,用心分享 |产品经理-从盈利模式和推广方法对酷我英语和网易云音乐进行竞品分析 前言 据产业信息网报道数据,2020年中国网络音乐用户规模达6.58亿,其中手机网络音乐用户占 ...

  9. 网易云音乐爬虫代码示例

    网易云音乐爬虫代码示例 以下是代码示例 import os import requests from bs4 import BeautifulSoupurl = 'https://music.163. ...

最新文章

  1. Docker安装Apache与运行简单的web服务——httpd helloworld
  2. Activiti——准备开发环境(一)
  3. Metasploit中aggregator插件无法使用
  4. php怎么输出mysql一条数据,MySQL数据输出在一个可打印的PHP变量
  5. 1.5 编程基础之循环控制(45题)
  6. Mac 使用Eigen库
  7. 判断端口是否能用_扫描器篇(八)之python+scapy构造TCP协议包扫描主机端口
  8. 获得百度智能云access token
  9. eclipse的自动检查语法错误功能让我有点烦,能不能关掉,或者,只是10秒检查一次。。...
  10. 分子动力学模拟-gromacs的基本使用
  11. QODBC查询Oracle中文乱码问题
  12. 基于MATLAB的温度报警,基于Matlab的小型温度检测系统设计
  13. 【gp数据库】统计常用窗口函数详解
  14. springboot接口签名统一效验_Spring Boot 优雅地实现接口参数校验
  15. 通信协议晦涩难懂搞不定?看完这些动图恍然大悟
  16. java基础-(六)-使用 Spring Initializr 创建springBoot项目
  17. jquery 鼠标拖动排序Li或Table
  18. 基于PHP+MySQL实现注册和登录功能
  19. ERD Online 4.0.3_fix 元数据在线建模(免费、私有部署)
  20. 微信小程序Swiper组件轮播图片尺寸填坑

热门文章

  1. 【故障检测】基于 KPCA 的故障检测【T2 和 Q 统计指数的可视化】(Matlab代码实现)
  2. 1109: 方块消除 blocks
  3. subplot函数介绍
  4. 手动搭建一个https服务器,并颁发证书
  5. 中国酒店周刊 | 上海万信福朋喜来登、包头茂业万豪、武汉江夏万枫酒店开业...
  6. 独家爆料!在美团搞安全,是种怎样的体验?
  7. SM2加解密、签名验签
  8. plt.scatter设置点大小_设置电脑桌面壁纸图文教程,下载图片设置计算机系统屏幕背景方法...
  9. Python GDAL矢量转栅格详解
  10. 阿里云ECS服务器跨账号迁移