赞
踩
1、采用爬虫技术爬取相关的网络数据(内容自选)保存在本地,并将内容用显示出来(软件:python)。
2、进行数据预处理,数据清洗(是否有缺失值,处理缺失值),数据转换(是否需要进行数据转换)。
3、将爬取到的内容保存到MySQL数据库中。
4、采用统计汇总和统计的方法对数据进行总结,将数据可视化表示出来。
本次实验爬取的对象是一首名为《练习题》的流行音乐,该音乐由江映蓉演唱,由胡小健作词,郑金光作曲,于2010年9月13日发行并收录在江映蓉发行的首张专辑《坏天使》中。《练习题》是给逝去爱情的纪念,其中表达了作者对爱情的乐观态度,希望演唱这首歌能让人们明白,爱情逝去之后,伤心点到为止就好2011年,该歌曲被评为2010年北京流行音乐典礼年度金曲。时隔12年,现在运用python爬取网易云音乐中该歌曲的相关内容,并进行可视化分析。
在爬取内容保存到数据库之前,需要创建temp1表保存《练习题》这首歌的评论信息,创建temp2表保存《练习题》这首歌评论用户的相关信息。在数据库创建表的代码如下所示:
CREATE TABLE temp1 ( commentId char(20) PRIMARY KEY, content VARCHAR(255), likedCount INT, time DATETIME, userId char(20) ); CREATE TABLE temp2 ( userId VARCHAR(255), gender CHAR(1), userName VARCHAR(255), age INT, level INT, city VARCHAR(255), sign TEXT, eventCount INT, followedCount INT, followsCount INT, recordCount INT, avatar VARCHAR(255) ); |
爬取使用单线程爬取网易云音乐中《练习题》这首歌的评论、评论时间、评论号、评论时间、用户id,并直接保存到数据库中。代码如下图所示:
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 = 'dengyayun' # 数据库名 TABLE = 'temp1' # 数据库表名 # 数据表设计如下: ''' 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='root', passwd='123456', db='dengyayun', charset='utf8mb4') cursor = conn.cursor() sql = 'insert into ' + TABLE + ' (commentId,content,likedCount,time,userId) VALUES (%s,%s,%s,%s,%s)' for d in data: try: #cursor.execute('SELECT max(c) FROM '+TABLE) #id_ = cursor.fetchone()[0] cursor.execute(sql, (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//50+1): _, data = getData(ROOT_URL%(songId, LIMIT_NUMS, i*(LIMIT_NUMS))) saveData(data) |
在爬取数据的过程中,需要先获取网易云音乐中《练习题》这首歌的id,本实验所用id为251594,
根据获取用户信息的API,请求URL有1个可变部分:用户id,前一部分已经将每条评论对应的用户id也存储下来,这里只需要从数据库取用户id并抓取信息即可。代码如下图所示:
from urllib import request import json import pymysql import re ROOT_URL = 'https://music.163.com/api/v1/user/detail/' DATABASE = 'dengyayun' TABLE_USERS = 'temp2' TABLE_COMMENTS = 'temp1' # 数据表设计如下: ''' 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\/]') 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", } 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['followsCount'] = js['profile']['follows'] data['followedCount'] = js['profile']['followeds'] data['city'] = js['profile']['city'] data['recordCount'] = js['listenSongs'] saveData(data) 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='root', passwd='123456', db='dengyayun', charset='utf8mb4') cursor = conn.cursor() sql = 'insert into ' + TABLE_USERS + ' (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)' try: cursor.execute(sql, (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='root', passwd='123456', db='dengyayun', 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: getData(ROOT_URL+i[0].strip()) |
把评论表temp1保存到本地为temp3,代码如下:
import pymysql,xlwt def export_excel(table_name): conn = pymysql.connect(host='localhost', user='root', passwd='123456', db='dengyayun', charset='utf8') cur = conn.cursor() sql = 'select * from %s;' %'temp1' #读取数据 cur.execute(sql) fileds = [filed[0] for filed in cur.description] all_date = cur.fetchall() #所有数据 for result in all_date: print(result) #写excel book = xlwt.Workbook() #创建一个book sheet = book.add_sheet('result') #创建一个sheet表 for col,filed in enumerate(fileds): sheet.write(0,col,filed) #从第一行开始写 row = 1 for data in all_date: for col,filed in enumerate(data): sheet.write(row,col,filed) row += 1 book.save('E:/bigdataleixuelin/%s.xls' %'temp3') if __name__ == '__main__': export_excel('stocks') |
把用户表temp2保存到本地为temp4,代码如下图所示:
import pymysql,xlwt def export_excel(table_name): conn = pymysql.connect(host='localhost', user='root', passwd='123456', db='dengyayun', charset='utf8') cur = conn.cursor() sql = 'select * from %s;' %'temp2' #读取数据 cur.execute(sql) fileds = [filed[0] for filed in cur.description] all_date = cur.fetchall() #所有数据 for result in all_date: print(result) #写excel book = xlwt.Workbook() #创建一个book sheet = book.add_sheet('result') #创建一个sheet表 for col,filed in enumerate(fileds): sheet.write(0,col,filed) #从第一行开始写 row = 1 for data in all_date: for col,filed in enumerate(data): sheet.write(row,col,filed) row += 1 book.save('E:/bigdataleixuelin/%s.xls' %'temp4') if __name__ == '__main__': export_excel('stocks') |
得到的数据中存在缺失值,将缺失值用“0”替代。代码如下图所示:
import pandas as pd # 读取Excel文件 df = pd.read_excel('E:/bigdataleixuelin/temp1.xlsx', engine='openpyxl') dh = pd.read_excel('E:/bigdataleixuelin/temp2.xlsx', engine='openpyxl') # 使用fillna函数填充空值,这里我们用0填充所有的空值 df.fillna(0, inplace=True) dh.fillna(0, inplace=True) # 将填充后的DataFrame写回Excel文件 df.to_excel('E:/bigdataleixuelin/temp1.xlsx', index=False, engine='openpyxl') dh.to_excel('E:/bigdataleixuelin/temp2.xlsx', index=False, engine='openpyxl') |
由于开始运用爬虫爬取数据时,数据已经保存到数据库中,故此过程已经完成,不再重复进行。
第④项已经进行了初步的数据处理,但是在可视化过程中,还要进行一些数据清洗:用户年龄错误、用户城市编码转换等,在数据可视化过程中不同的数据会涉及到不同的数据处理,所以在可视化过程中会进行相应的数据清洗。
评论时间按天分布图,部分重要代码如下:
comments_day = comments['time'].dt.date data = comments.groupby(comments_day).userid.count() line=Line(init_opts=opts.InitOpts(theme=ThemeType.CHALK)) line.add_xaxis(data.index.tolist()) line.add_yaxis(series_name='评论时间按天分布',y_axis=data.tolist()) line.render(r'E:/bigdataleixuelin/评论时间(按天)分布.html') |
评论时间按小时分布,部分重要代码如下:
comments_hour = comments['time'].dt.hour data = comments_hour.groupby(comments_hour).count() line = Line(init_opts=opts.InitOpts(theme=ThemeType.CHALK)) line.add_xaxis(data.index.tolist()) line.add_yaxis(series_name='评论时间按小时分布',y_axis=data.tolist()) line.render(r'E:/bigdataleixuelin/评论时间(按小时)分布.html') |
评论时间按周分布,部分重要代码如下:
comments_week = comments['time'].dt.dayofweek data = comments_week.groupby(comments_week).count() line = Line(init_opts=opts.InitOpts(theme=ThemeType.CHALK)) line.add_xaxis(data.index.tolist()) line.add_yaxis(series_name='评论时间按周分布',y_axis=data.tolist()) line.render(r'E:/bigdataleixuelin/评论时间(按周)分布.html') |
用户年龄分布分析,会首先清洗掉年龄小于1的数据。部分重要代码如下:
age = users[users['age']>0] # 清洗掉年龄小于1的数据 age = age.groupby(age['age']).userid.count() # 以年龄值对数据分组 bar = Bar(init_opts=opts.InitOpts(theme=ThemeType.CHALK)) bar.add_xaxis( age.index.tolist()) bar.add_yaxis(series_name='用户年龄',y_axis= age.tolist()) bar.render(r'E:/bigdataleixuelin/用户年龄分布图.html') # 生成渲染的html文件 |
用户地区分布分析,需要对用户地区编码进行转换,部分重要代码如下:
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.groupby(city).count() |
评论情感得分分析,部分重要代码如下:
text['content'] = text['content'].apply(lambda x:round(SnowNLP(x).sentiments, 2)) semiscore = text.userid.groupby(text['content']).count() bar = pyec.Bar(init_opts=opts.InitOpts(theme=ThemeType.CHALK)) bar.add_xaxis(semiscore.index.tolist()) bar.add_yaxis(series_name='评论情感得分',y_axis=semiscore.tolist()) bar.render(r'E:/bigdataleixuelin/情感得分分析.html') |
评论情感标签分析,部分重要代码如下:
text['content'] = text['content'].apply(lambda x:1 if x>0.5 else -1) semilabel = text.userid.groupby(text['content']).count() # bar = pyec.Bar() bar = pyec.Bar(init_opts=opts.InitOpts(theme=ThemeType.CHALK)) bar.add_xaxis(semilabel.index.tolist()) bar.add_yaxis(series_name='评论情感标签', y_axis=semilabel.tolist()) bar.render(r'E:/bigdataleixuelin/情感标签分析.html') |
歌曲评论词云图,部分重要代码如下:
TABLE_COMMENTS = 'temp1' DATABASE = 'dengyayun' SONGNAME = 'five hours' def getText(): conn = pymysql.connect(host='localhost', user='root', passwd='123456', db=DATABASE, charset='utf8') sql = 'SELECT id,content FROM '+TABLE_COMMENTS text = pd.read_sql(sql=sql, con=conn) return text 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'E:\bigdataleixuelin\python爬虫数据可视化分析大作业\python代码\stopwords.txt', 'r',encoding='utf-8').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() getWordcloud(text) |
评论时间按天分布可视化结果如下图
图1 评论时间按天分布可视化图
结果分析:从折线图可以看出,在2021-02前后评论较大幅度增多,其他时间趋于平静。可以解释为疫情期间,大家有了更多的时间听音乐,所以在这个期间评论数增多,而在其他天数,收听并评论这首歌的用户较少。
评论时间按小时分布可视化结果如下图:
图2 评论结果按小时分布可视化图
结果分析:从评论时间按小时分布图可以看出,评论数在一天当中有两个小高峰:11点-13点和22点-0点。这可以解释为用户在中午午饭时间和晚上下班(课)在家时间有更多的时间来听歌刷评论,符合用户的日常。至于为什么早上没有出现一个小高峰,大概是早上大家都在抢着时间上班(学),或者睡懒觉,就没有多少时间去刷评论。
评论时间按周分布可视化如下图:
图3 评论结果按周分布可视化图
结果分析:从评论时间按周分布图可以看出,评论数在一周当中前面较少,后面逐渐增多,这可以解释为往后接近周末,大家有更多时间来听听歌、刷刷歌评,而一旦周末过完,评论量马上下降(周日到周一的下降过渡),大家又回归到工作当中。
用户年龄分布图如下图所示:
图4 用户年龄分布图
结果分析:从用户年龄分布图可以看出,用户大多集中在14-29岁之间,以20岁左右居多,除去虚假年龄之外,这个年龄分布也符合网易云用户的年龄段。图中可以看出28岁有个小高峰,猜测可能是包含了一些异常数据。
用户地区分布图如下图所示:
图5 用户地区分布图
结果分析:从用户地区分布图可以看出,用户涵盖了全国各大省份,其中收听《练习题》这首歌最多的的用户大多来自于广东省,浙江省,江苏省等沿海地区,四川省、湖南省等中南部地区次之,总的来说,用户大多来自东南部省市。
情感得分分析图如下图所示:
图6 情感得分图
结果分析:情感得分0.85-1的较多。说明大部分用户评论是积极的,用户评价偏好。说明现代人的情感偏向是积极向好的。
情感标签分析图如下图所示:
图7 情感标签图
结果分析:从情感标签分析图可以看出,大多数听该音乐的人情感标签为1,即积极情感。可见,大家对爱情或者失恋还是持积极乐观的态度。
词云图:
图8 词云图
结果分析:从评论词云图可以看出,大家评论得最多的是“练习题”,“没关系”,“说”,“闲”等词语,可以看出大家在评论中表达的都是一些内心感受,把评论区当成了疏解心中烦恼的地方,诉说自己和他人的故事。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。