当前位置:   article > 正文

网易云音乐《练习题》爬虫+可视化分析_爬虫网易云歌单的实习报告

爬虫网易云歌单的实习报告

一、实验内容

1、采用爬虫技术爬取相关的网络数据(内容自选)保存在本地,并将内容用显示出来(软件:python)。

2、进行数据预处理,数据清洗(是否有缺失值,处理缺失值),数据转换(是否需要进行数据转换)。

3、将爬取到的内容保存到MySQL数据库中。

4、采用统计汇总和统计的方法对数据进行总结,将数据可视化表示出来。

二、数据爬取背景说明

本次实验爬取的对象是一首名为《练习题》的流行音乐,该音乐由江映蓉演唱,由胡小健作词,郑金光作曲,于2010年9月13日发行并收录在江映蓉发行的首张专辑《坏天使》中。《练习题》是给逝去爱情的纪念,其中表达了作者对爱情的乐观态度,希望演唱这首歌能让人们明白,爱情逝去之后,伤心点到为止就好2011年,该歌曲被评为2010年北京流行音乐典礼年度金曲。时隔12年,现在运用python爬取网易云音乐中该歌曲的相关内容,并进行可视化分析。

三、实验记录

1、编写程序

在爬取内容保存到数据库之前,需要创建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')

⑤将表保存到mysql数据库中

由于开始运用爬虫爬取数据时,数据已经保存到数据库中,故此过程已经完成,不再重复进行。

⑥数据清洗&数据可视化

第④项已经进行了初步的数据处理,但是在可视化过程中,还要进行一些数据清洗:用户年龄错误、用户城市编码转换等,在数据可视化过程中不同的数据会涉及到不同的数据处理,所以在可视化过程中会进行相应的数据清洗。

评论时间按天分布图,部分重要代码如下:

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)

2、数据可视化结果

评论时间按天分布可视化结果如下图

图1 评论时间按天分布可视化图

结果分析:从折线图可以看出,在2021-02前后评论较大幅度增多,其他时间趋于平静。可以解释为疫情期间,大家有了更多的时间听音乐,所以在这个期间评论数增多,而在其他天数,收听并评论这首歌的用户较少。

评论时间按小时分布可视化结果如下图:

图2 评论结果按小时分布可视化图 

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

评论时间按周分布可视化如下图:

图3 评论结果按周分布可视化图 

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

用户年龄分布图如下图所示:

 图4 用户年龄分布图

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

用户地区分布图如下图所示:

图5 用户地区分布图 

结果分析:从用户地区分布图可以看出,用户涵盖了全国各大省份,其中收听《练习题》这首歌最多的的用户大多来自于广东省,浙江省,江苏省等沿海地区,四川省、湖南省等中南部地区次之,总的来说,用户大多来自东南部省市。 

情感得分分析图如下图所示:

图6 情感得分图 

结果分析:情感得分0.85-1的较多。说明大部分用户评论是积极的,用户评价偏好。说明现代人的情感偏向是积极向好的。

情感标签分析图如下图所示:

图7 情感标签图 

结果分析:从情感标签分析图可以看出,大多数听该音乐的人情感标签为1,即积极情感。可见,大家对爱情或者失恋还是持积极乐观的态度。

词云图:

图8 词云图

结果分析:从评论词云图可以看出,大家评论得最多的是“练习题”,“没关系”,“说”,“闲”等词语,可以看出大家在评论中表达的都是一些内心感受,把评论区当成了疏解心中烦恼的地方,诉说自己和他人的故事。 

 

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号