1. 数据爬取

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

工具:

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 re

ROOT_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, None

headers = {

"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, datas

except Exception as e:

print('Down err>>> ', e)

pass

def saveData(data):

if not data:

return None

conn = 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)

pass

cursor.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 re

ROOT_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 None

print('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'] = 0

else:

data['age'] =(2018-1970)-(int(js['profile']['birthday'])//(1000*365*24*3600))

if int(data['age'])<0:

data['age'] = 0

data['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)

pass

return None

def saveData(data):

if not data:

return None

conn = 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)

pass

finally:

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_COMMENTS

try:

cursor.execute(sql)

res = cursor.fetchall()

return res

except Exception as e:

print('get err>>> ', e)

pass

finally:

cursor.close()

conn.close()

return None

if __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,Map

TABLE_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.6

import numpy as np

import pymysql

from snownlp import SnowNLP

from pyecharts import Bar

TABLE_COMMENTS = '****'

DATABASE = '****'

SONGNAME = '****'

def getText():

conn = pymysql.connect(host='localhost', user='root', passwd='root', db=DATABASE, charset='utf8')

sql = 'SELECT id,content FROM '+TABLE_COMMENTS

text = pd.read_sql(sql%(SONGNAME), con=conn)

return text

def 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'] = False

def 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

python3爬取网易云歌单数据清洗_网易云音乐爬虫 数据可视化分析相关推荐

  1. python3爬取网易云歌单数据清洗_利用Python网络爬虫抓取网易云歌词

    今天小编给大家分享网易云音乐歌词爬取方法. 本文的总体思路如下:找到正确的URL,获取源码: 利用bs4解析源码,获取歌曲名和歌曲ID: 调用网易云歌曲API,获取歌词: 将歌词写入文件,并存入本地. ...

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

    网易云音乐爬虫 & 数据可视化分析 1. 数据爬取 1.1 评论爬取 1.2 用户信息爬取 2 数据清洗 & 可视化 歌评文本分析 个人博客:Archiew's blog 源码:htt ...

  3. python3爬取网易云歌单数据清洗_如何利用Python网络爬虫爬取网易云音乐歌词

    赵雷的歌曲 本文以民谣歌神赵雷为数据采集对象,专门采集他的歌曲歌词,其他歌手的歌词采集方式可以类推,下图展示的是<成都>歌词. 赵雷歌曲---<成都> 一般来说,网页上显示的U ...

  4. python3爬取网易云歌单数据清洗_网页抓取网易云音乐及评论数据分析

    网页抓取网易云音乐及评论数据分析 游贤 成都理工大学信息科学与技术学院 [摘 要] 摘要:为了分析网易云音乐中哪些歌曲是热门歌曲,哪些歌曲的评论 最多,从而了解到人们对于不同音乐类型的喜爱程度,采用成 ...

  5. python3爬取网易云歌单数据清洗_实例 | 使用网易云音乐数据演示数据整合与数据清洗...

    作者 | 小F 来源 | 法纳斯特(walker398) 数据整合是对数据进行行列选择.创建.删除等操作. 数据清洗则是将整合好的数据去除其中的错误和异常. 本文利用之前获取的网易云音乐用户数据,来简 ...

  6. python3爬取全民K歌

    Python3爬取全民k歌 环境 python3.5 + requests 1.通过歌曲主页链接爬取 首先打开歌曲主页,打开开发者工具(F12). 选择Network,点击播放,会发现有一个请求返回的 ...

  7. 05 爬取周杰伦所有歌单

    # Author:Nimo_Ding''' 目标: 爬取周杰伦所有歌单 ''' import requests url = 'https://c.y.qq.com/soso/fcgi-bin/clie ...

  8. Python学习-使用Python爬取陈奕迅新歌《我们》网易云热门评论

    <后来的我们>上映也有好几天了,一直没有去看,前几天还爆出退票的事件,电影的主题曲由陈奕迅所唱,特地找了主题曲<我们>的MV看了一遍,还是那个感觉.那天偶然间看到Python中 ...

  9. 讲python的东哥_小伙子不讲武德,竟用Python爬取了B站上1.4万条马老师视频数据来分析...

    看到标题, 啪的一下你就进来了吧! 如果有经常刷B站的小伙伴,肯定都知道B站鬼畜现在的顶流是谁? 印度:没错正是在下 那必须是当代大师浑元形意太极拳掌门人「马保国」先生啊! 实话讲,马保国走进大家视野 ...

  10. python爬取网易云歌单

    写在前面: 我的博客已迁移至自建服务器:博客传送门,CSDN博客暂时停止,如有机器学习方面的兴趣,欢迎来看一看. 此外目前我在gitHub上准备一些李航的<统计学习方法>的实现算法,目标将 ...

最新文章

  1. 内存泄漏的定位与排查:Heap Profiling 原理解析
  2. lol12月25服务器维护,lol12月10日维护到几点 英雄联盟12月10日10.25版本更新维护时间...
  3. 每日一题 Day8:CodeForces-996A.Hit the Lottery(贪心)
  4. html语言的网页制作,HTML语言的网页制作技巧与方法
  5. 修复Windows10系统的注册表?
  6. java开发英语词典app_英语词典app哪个好 5款好用的英语词典app推荐
  7. 消防信号总线原理_消防二总线的构成以及工作原理?
  8. 控制系统分析与设计(三):非线性数学模型的线性化
  9. 2020寒假第三周总结
  10. Unity Shader 内置函数
  11. 两对等位基因控制一对相对性状的规律(基因互作)
  12. 【网络】TOE、RDMA、smartNIC 是什么和区别|DPU
  13. IDM下载器 v6.38.20 最新中文版 免费的多线程下载工具
  14. Linux 下 find(文件查找)命令的用法总结
  15. 程序设计思维与实践 csp路径解析、csp炉石传说
  16. AIS数据压缩-改进的DP算法(Improved DP algorithm)
  17. python写入csv指定单元格_使用python将值插入csv中的特定单元格
  18. python学习 python实现证件照剪裁、缩放、修改底色
  19. 中兴交换机配置telnet连接_原创:思科交换机常用配置
  20. ubuntu 硬盘情况占用分析

热门文章

  1. python判断是否有弹出窗口_使用Python爬取弹出窗口信息的实例
  2. 计算机审计风险未来发展,浅谈计算机审计风险的研究
  3. 自写string.h头文件(部分)
  4. 计算机毕业设计之会议预约系统设计与实现
  5. php会议预约,会议预约系统-信息发布系统|多媒体信息发布系统|视展信息发布系统首页...
  6. Java项目:SSM会议室预约系统
  7. linux中rpm命令,linux中rpm命令使用介绍
  8. Python包:包的概念、2种建立包方式、包的使用(代码 + 图文)
  9. 2023苏州科技大学计算机考研信息汇总
  10. 软件运维监控有哪些?