我分析了55W歌词,就是想听听中国民谣在唱什么

news/2024/7/19 10:12:00 标签: 数据可视化, 爬虫, 数据清洗

1. 歌词获取

首先我需要一个民谣歌曲集合,选歌单的原则是尽力为选择能代表中国民谣的作品,事实上,现在民谣制作的门槛是真的低。有的民谣里面通篇就几个词翻来覆去。比如底下这种歌单很快就舍弃掉了。

(野鸡民谣)

最终选择的是 歌单:民谣合集。歌单比较全,总共大概2128首歌,涉及到不少华语民谣歌手。按每人50首来算,这也有40左右个中国民谣人了

网易提供了(体验并不好的)某些API,

# 获取歌单中歌曲API url
playlist_url = 'http://music.163.com/api/playlist/detail?id=[id]'
# 获取歌曲的歌词API url
song_lyrics_url = 'http://music.163.com/api/song/lyric?os=pc&id=[id]&lv=-1'

实际请求时,请将[id]替换为真实id,比如《民谣合集》歌单id为2618345367,你可以从网易音乐相关超链接上找到。这两个API返回都是JSON格式,需要自己解析。(可以看出实际中这个#是没什么用的)

然后,我们请求获取歌单的所有歌曲信息(歌曲云上id、歌曲名、作者)

def get_playlist(playlist_id, path=''):
    """
    获得歌单中所有歌曲的id,cloud_id,title,author信息
    :param playlist_id: 歌单id
    :param path: 保存的路径和文件名
    :return: text
    """
    json_text = requests.get(playlist_url % playlist_id).text
    json_dict = json.loads(json_text)
    text_list = []
    for idx, song in enumerate(json_dict['result']['tracks']):
        if not idx:
            text_list.append('id    cloud_id    title    author')
        text_list.append('%d    %d    %s    %s' % (idx + 1, song['id'], song['name'], song['artists'][0]['name']))
    text = '\n'.join(text_list)
    if path:
        with open(path, 'w') as file:
            file.write(text)
    return text

我们还需要一个函数,请求获取某首歌的歌词

def get_lyric(song_id, path=''):
    """
    根据歌曲id获得歌曲歌词
    :param song_id:
    :param path: 当前歌词被保存的路径和文件名
    :return:
    """
    json_text = requests.get(song_lyrics_url % song_id).text
    json_dict = json.loads(json_text)
    text_list = []
    for idx, line in enumerate(json_dict['lrc']['lyric'].split('\n')):
        if '作曲' in line or '作词' in line:  # 去除歌词中可能出现的作词、作曲行
            continue
        if line.strip() != '':
            if ']' in line:  # []内可能是时间信息,去除
                if line.rindex(']') + 1 != len(line):
                    line = line[line.rindex(']') + 1:].strip()
                else:
                    continue
            if ':' in line:  # 冒号前面可能是歌者,应去除。e.g.: "女:"、"老狼:"
                line = line[line.rindex(':') + 1:]
            if ':' in line:  # 冒号前面可能是歌者,应去除。e.g.: "女:"、"老狼:"
                line = line[line.rindex(':') + 1:]
            text_list.append(line.strip())

    text = '\n'.join(text_list)
    if path:
        with open(path, 'w') as file:
            file.write(text)
    return text

最后,我们将此歌单中所有歌曲歌词都保存下来

if __name__ == '__main__':
    # 获取"中国民谣集"歌单中所有歌曲歌词
    df = pd.read_csv(os.path.join(cfg.ProjectPath, cfg.DataDirectory, cfg.SongInfoFileName), delimiter='    ',
                     engine='python')
    for idx, cloud_id, title, author in df.values:
        try:
            get_lyric(cloud_id, file_name='%d_%s_%s' % (idx, title, author))
        except IOError as ioe:
            print('id为%s,云id为%s的文件异常,%s' % (idx, cloud_id, ioe))
        except BaseException as be:
            print('id为%s,云id为%s的文件异常,%s' % (idx, cloud_id, be))

写个函数在目录里自动生成目录保存下来所有歌词

def makedir(dir_name):
    path = r"C:\Users\admin\PycharmProjects\Cloudlyric\data"
    dir_name = path+'./'+dir_name
    folder = os.path.exists(dir_name)
    if not folder:
        os.makedirs(dir_name)
        print("creat dir success")
    else:
        print("this folder has existed")
    return dir_name

2.数据存储和初步简单清洗

简单的看了一下爬下来的每一个数据,发现前两行经常有不用的作词作曲信息,想办法去除掉

def ConvertStrToFile(dir_name, filename, str):
    if (str == ""):
        return
    filename = filename.replace('/', '')

    text_list = ""

    for idx, line in enumerate(str.split('\n')):
        if '作曲' in line or '作词' in line or '编曲' in line:  # 去除歌词中可能出现的作词、作曲行
            continue
        if line.strip() != '':
            if ']' in line:  # []内可能是时间信息,去除
                if line.rindex(']') + 1 != len(line):
                    line = line[line.rindex(']') + 1:].strip()
                else:
                    continue
            if ':' in line:  # 冒号前面可能是歌者,应去除。e.g.: "女:"、"老狼:"
                line = line[line.rindex(':') + 1:]
            if ':' in line:  # 冒号前面可能是歌者,应去除。e.g.: "女:"、"老狼:"
                line = line[line.rindex(':') + 1:]
            text_list +=line.strip()
    path = r"C:\Users\admin\PycharmProjects\Cloudlyric\data"
    with open(path+"./"+dir_name + "//" + filename + ".txt", 'w',encoding='utf-8') as f:
        f.write(text_list)

于是保存歌词成txt文档在最开始创建的目录里

def get_list_lyric(playlist_id):
    songlist = get_list(playlist_id)
    print(songlist)
    playlist_name = songlist['playlist_name']
    idlist = songlist['list']

    dir=makedir(playlist_name)

    for music in idlist:
        ConvertStrToFile(playlist_name, music.text, get_lyric(music['href']))
        print("File " + music.text + " is writing on the disk")

    print("All files have created successfully")

    return dir

3.数据的清洗、分词、统计

在分词前,为了最终结果得出不必要的语气词以及冗余词,我是用的是哈工大停用词表再结合结巴分词,效果好了不少

def load_stopwords():
    """
    加载停用词
    :return:
    """
    stopwords = set()
    with open(os.path.join(naming.ProjectPath, naming.DictDirectory, naming.StopwordsFileName),encoding='utf-8') as file:
        for line in file:
            stopwords.add(line[:-1])  # 切除换行符
    stopwords.add(' ')
    return stopwords


def get_words_freq(stopwords, lyrics_dir):
    """
    统计词频
    :param stopwords: 停用词集合
    :type stopwords: set(stopword1, stopword2, ...)
    :param lyrics_dir: 歌词路径
    :return: {word1: freq, word2: freq, ...}
    """
    # 迭代每个文件,对每个文件,每一行分词,去除停用词后,统计词频
    words_freq = {}
    for file_name in os.listdir(lyrics_dir):
        try:
            with open(os.path.join(lyrics_dir, file_name),encoding='utf-8') as file:
                for line in file:
                    words = posseg.cut(line.strip())
                    for word, pos in words:
                        if word not in stopwords and pos[0] in pos_retained:  # 过滤停用词,词性筛选
                            if word not in words_freq:
                                words_freq[word] = 1
                            else:
                                words_freq[word] += 1
        except IOError as ioe:
            logger.warning('异常信息:%s' % ioe)
        except BaseException as be:
            logger.warning('异常信息:%s' % be)
    return words_freq

为了方便,得出的结果转为json格式,存储在data数据目录下

初步优化

发现许多地方名称、路径又多又杂。又开了一个py文件naming来设置命名规范  大概是这样的

# -*- coding: utf-8 -*-
from dataGet import getData
__author__ = 'Jmh'

# Project path
ProjectPath = r'C:\Users\admin\PycharmProjects\Cloudlyric'

# Data directorye
DataDirectory = 'data'
LyricsDirectory = 'data/民谣合集 - 歌单 - 网易云音乐'
DictDirectory = 'data/dict'
Resource = 'resource'

# File name

StopwordsFileName = 'stopwords'  # 停用词文件名

简单的数据处理后,轻易可以得出各种自己想要的数据:

4.运用WordClouD词云

此部分比较简单也就写的比较随意了,生成了两个,一个带图片蒙版一个不带

wordcloud = WordCloud(background_color='white',mask=alice_mask,max_words =1000,font_path=r"C:\\Windows\\Fonts\\STFANGSO.ttf",).fit_words(read_dict)
#生成词云(通常字体路径均设置在C:\\Windows\\Fonts\\也可自行下载)
#不加这一句显示口字形乱码  ""报错
plt.imshow(wordcloud)
plt.axis('off')
plt.show()

其中生成中文词云时,不加font_path会显示满屏幕的口字乱码

带蒙版的,需要ps去除背景且大小要合适,最大词频数控制在1000个左右

 通过词云,我们大概很清晰的能看出来:民谣歌手最常出现的几个词“时间”“姑娘”“梦想”“世界”“远方”“城市”

看来民谣歌手也没有那么丧。

5.制作图表

共用到两种工具:matplot和echarts  

绘制柱状图:

import matplotlib.pyplot as plt
import numpy as np
plt.rcParams['font.sans-serif']=['SimHei'] #用来正常显示中文标签
plt.rcParams['axes.unicode_minus']=False #用来正常显示负号
# 创建一个点数为 8 x 6 的窗口, 并设置分辨率为 80像素/每英寸
plt.figure(figsize=(8, 6), dpi=80)

# 再创建一个规格为 1 x 1 的子图
plt.subplot(1, 1, 1)

# 柱子总数
N = 11
# 包含每个柱子对应值的序列
values = (75, 17, 9, 9, 2,7,6,12,7,9,25)

# 包含每个柱子下标的序列
index = np.arange(N)

# 柱子的宽度
width = 0.35

# 绘制柱状图, 每根柱子的颜色为紫罗兰色
p2 = plt.bar(index, values, width, label="城市", color="#87CEFA")

# 设置横轴标签
plt.xlabel('')
# 设置纵轴标签
plt.ylabel('次数')

# 添加标题
plt.title('民谣歌手最钟爱的城市')

# 添加纵横轴的刻度
plt.xticks(index, ('北京', '上海', '深圳', '成都', '苏州', '重庆','武汉','南京','青岛','郑州','兰州'))
plt.yticks(np.arange(0, 81, 10))

# 添加图例
plt.legend(loc="upper right")

plt.show()

民谣歌手最爱的城市是北京,其次竟然是兰州,出乎了我的意料

比起其他季节,民谣歌手更爱歌颂冬天

即是丧如民谣歌手,人还是要向前看。

 

民谣歌手更喜欢忧郁的蓝色 


http://www.niftyadmin.cn/n/1187057.html

相关文章

Oracle 11gR2新建空表不分配Segment

一、引言: 在看《收获,不止Oracle》的神奇,走进逻辑体系世界一章时,需要新建一张表查看Extents的情况,由于该书的环境是ORACLE10G的,因此新建空表以后立刻就分配Segment,而我使用的是Oracle11gR…

认识Visible=false

如果有一个隐藏input来存放数据。但是一旦隐藏了Jquery就$不到了! 所以还要采取其他的方法。之前一直想当然的认为Jquery还能看到的。想当然害死人啊!转载于:https://www.cnblogs.com/master-zhu/archive/2010/06/28/1766980.html

Allan 译 The Little MongoDB Book (Chapter 2 - Updating)

Chapter 2 - Updating在第一章中我们介绍了四个CRUD(create, read, update和delete)操作中的三个。在这一章中了解我们跳过的个:update。 Update有几个让人惊叹的行为,这也就是为什么我们为什么用一章来介绍它。Update: Replace V…

kettle An error occurred, processing will be stopped: 错误 解决方法

上午在使用KETTLE时,报了一个 An error occurred, processing will be stopped: 错误,手动跑没有问题,用jekens调用就报错。 具体原因不清楚,后面把 表输出组件默认为使用批量插入,数量为1000条。将1000条修改为500条,…

【Maven学习笔记】mvn help:system 命令的说明

mvn help:system 命令的说明 笔者用得是windows 10 x64系统 下载了Maven3,正确配置了系统变量M2_HOME的值,并且添加到Path变量路径当中。 简单来说,Maven是用Java语言写的项目管理工具,下载解压包,设置系统环境变量的值…

20080603 - Facebook 平台正式开放

Facebook 开放平台是现有 Facebook 运行平台架构的映像。它主要包括: - API infrastructure - FBML parser - FQL parser - FBJS 同时,包含许多常用方法和标签的实现。 http://developers.facebook.com/fbopen/ Facebook Open Platform is a snapshot of…

使用vector时报很多warning

我大学学的是C语言&#xff0c;所以研究生阶段想自学C,毕竟他们有那么多不同。但是在使用vector的时候报warning了———— Compiling... TextSearch.cpp f:/workspaceforc/容器/textsearch.cpp(12) : warning C4786: std::reverse_iterator<std::basic_string<char,std…

借助URLOS快速安装jenkins-持续集成工具

简介 Jenkins是一个开源的、提供友好操作界面的持续集成(CI)工具&#xff0c;起源于Hudson&#xff08;Hudson是商用的&#xff09;&#xff0c;主要用于持续、自动的构建/测试软件项目、监控外部任务的运行&#xff08;这个比较抽象&#xff0c;暂且写上&#xff0c;不做解释&…