Advertisement

【Python练习】生成五月天歌名词云图和歌词词频词云图

阅读量:

目录

一、歌词爬取

二、清洗歌词数据

三、歌词分词 词频统计

四、词云图制作

五、从清洗数据到词云图的代码全文


一、歌词爬取

第一步是获取五月天在网易云音乐平台的所有歌词内容,并将其保存至本地数据库中。该代码借鉴了他人的研究成果,并基于爬取网易云音乐某个歌手的全部歌曲的歌词这一参考文献展开开发工作。

自己做了一点小修改,五月天的id是13193,爬取歌词代码如下:

复制代码
 import requests

    
 from lxml import etree
    
 import simplejson
    
 import re
    
 import operator
    
 from functools import reduce
    
  
    
 ua = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36'
    
 headers = {
    
     'User-agent': ua
    
 }
    
  
    
  
    
 class CrawlerLyric:
    
     def __init__(self):
    
     self.author_name = ""
    
  
    
     def get_url_html(self, url):
    
     with requests.Session() as session:
    
         response = session.get(url, headers=headers)
    
         text = response.text
    
         html = etree.HTML(text)
    
     return html
    
  
    
     def get_url_json(self, url):
    
  
    
     with requests.Session() as session:
    
         response = session.get(url, headers=headers)
    
         text = response.text
    
         text_json = simplejson.loads(text)
    
     return text_json
    
  
    
     def parse_song_id(self, html):
    
  
    
     song_ids = html.xpath("//ul[@class='f-hide']//a/@href")
    
     song_names = html.xpath("//ul[@class='f-hide']//a/text()")
    
     self.author_name = html.xpath('//title/text()')
    
     song_ids = [ids[9:len(ids)] for ids in song_ids]
    
     return self.author_name, song_ids, song_names
    
  
    
     def parse_lyric(self, text_json):
    
     try:
    
         lyric = text_json.get('lrc').get('lyric')
    
         regex = re.compile(r'\[.*\]')
    
         final_lyric = re.sub(regex, '', lyric).strip()
    
         return final_lyric
    
     except AttributeError as k:
    
         print(k)
    
         pass
    
  
    
     def get_album(self, html):
    
     album_ids = html.xpath("//ul[@id='m-song-module']/li/p/a/@href")
    
     album_names = html.xpath("//ul[@id='m-song-module']/li/p/a/text()")
    
     album_ids = [ids.split('=')[-1] for ids in album_ids]
    
     return album_ids, album_names
    
  
    
     def get_top50(self, sing_id):
    
     url_singer = 'https://music.163.com/artist?id=' + str(sing_id)  # 陈奕迅
    
     html_50 = self.get_url_html(url_singer)
    
     author_name, song_ids, song_names = self.parse_song_id(html_50)
    
     # print(author_name, song_ids, song_names)
    
     for song_id, song_name in zip(song_ids, song_names):
    
         url_song = 'http://music.163.com/api/song/lyric?' + 'id=' + str(song_id) + '&lv=1&kv=1&tv=-1'
    
         json_text = self.get_url_json(url_song)
    
         print(song_name)
    
         print(self.parse_lyric(json_text))
    
         print('-' * 30)
    
  
    
     def get_all_song_id(self, album_ids):
    
  
    
     with requests.Session() as session:
    
         all_song_ids, all_song_names = [], []
    
         for album_id in album_ids:
    
             one_album_url = "https://music.163.com/album?id=" + str(album_id)
    
             response = session.get(one_album_url, headers=headers)
    
             text = response.text
    
             html = etree.HTML(text)
    
             album_song_ids = html.xpath("//ul[@class='f-hide']/li/a/@href")
    
             album_song_names = html.xpath("//ul[@class='f-hide']/li/a/text()")
    
             album_song_ids = [ids.split('=')[-1] for ids in album_song_ids]
    
  
    
             all_song_ids.append(album_song_ids)
    
             all_song_names.append(album_song_names)
    
  
    
     return all_song_ids, all_song_names
    
  
    
     def get_all_song_lyric(self, singer_id):
    
     album_url = "https://music.163.com/artist/album?id=" + str(singer_id) + "&limit=150&offset=0"
    
     html_album = self.get_url_html(album_url)
    
     album_ids, album_names = self.get_album(html_album)
    
     all_song_ids, all_song_names = self.get_all_song_id(album_ids)
    
     all_song_ids = reduce(operator.add, all_song_ids)
    
     all_song_names = reduce(operator.add, all_song_names)
    
     print(all_song_ids)
    
     print(all_song_names)
    
  
    
     for song_id, song_name in zip(all_song_ids, all_song_names):
    
         url_song = 'http://music.163.com/api/song/lyric?' + 'id=' + str(song_id) + '&lv=1&kv=1&tv=-1'
    
         json_text = self.get_url_json(url_song)
    
         try:
    
             with open('maydaylrc.txt', 'a+') as f:
    
                 f.write('歌曲名:'+song_name+'\n')
    
                 f.write(self.parse_lyric(json_text)+'\n')
    
                 print(song_name)
    
         except Exception as e:
    
             pass
    
  
    
  
    
  
    
 if __name__ == "__main__":
    
     sing_id = '13193'  # 五月天
    
     c = CrawlerLyric()
    
     c.get_all_song_lyric(sing_id)

运行完成后生成命名为"maydaylrc.txt"的歌词文件,并在每个歌名前添加"歌曲名:"以便后续的数据清理工作

二、清洗歌词数据

歌名,歌词首行位置,歌词末行位置

歌名、歌词起始行号、歌词结束行号

复制代码
 def tosong(text: list) -> list:  # 从lrc文件导出歌曲名及对应歌词首末行

    
     songs = []
    
     for line in text:
    
     if '歌曲名:' in line:
    
         index = text.index(line)  # 歌曲名所在行数
    
         lastindex = index + 1
    
         while lastindex < len(text) and '歌曲名:' not in text[lastindex]:
    
             lastindex += 1  # 该歌曲的歌词最后一行
    
         song_name = line.split(":")[1]  # 取歌曲名
    
         songs.append([song_name, index, lastindex])
    
     return songs

重新定义函数cleansong,并将输入参数设为列表songs。该函数将去除live版本、日语版本及具有20周年纪念意义的重复曲目,并外加非由五月天原创的曲目。返回去生成纯净后的歌曲列表song_result用于生成五月天相关名词云图

复制代码
 def cleansong(songs: list) -> list:  # 清洗歌曲名数据

    
     song_dt = {}
    
     nolst = (
    
     '缩影', '女字旁', '爱的轮回', '刚好', '她并非不想', '我还是爱着你', '无人之境', '是什么让我遇见这样的你', '三城记',
    
     '如果我是石头', '青岛巴士', '寻根(带我去三义)', '诚品激突', '武装以后(演奏曲)',
    
     '那一年的花季(探母瑄瑄版配乐)', '灵魂能有多重', '哪一楼的阿?(台北的脚步)', '三义之夏', '轻功(慢摇滚演奏版)',
    
     '寂寞星球 人人寂寞',
    
     '故事都随风(开往哈尔滨的列车)', '爱是绿洲', '鲨鱼摇滚', '火花一般', '旅途上', '企鹅午后', '公路电影',
    
     '年华', '一个人的表情', '金赛的迷惑', '…的愉快', 'What\'s Your Story?', 'Song for you', '成功間近', '人生有限会社',
    
     '頑固',
    
     'Party Animal', '少年漂流記', 'Song for you', 'Buzzin’', 'Dancin\' Dancin\'', '一歩一歩', '恋愛ING', '乾杯',
    
     '孫悟空',
    
     '末日', '明日', 'To Find My Paradise', 'Enrich Your Life', '探母', '五月之恋序曲', '前传', '咖哩鱼蛋', '海豚的歌',
    
     '神的孩子都在跳舞','刻在我心底的名字','干啦干啦','凡人歌','玫瑰少年')
    
     m = u"[\u3040-\u31FF]+"  # 日文字符
    
     for song in songs:
    
     # 删除live等不同版本
    
     if song[0] in nolst or 'Live' in song[0] or 'live' in song[0] or 'ver' in song[0] or 'Ver' in song[0] \
    
             or 'Remix' in song[0] or '伴奏' in song[0] or '版' in song[0] or '20th' in song[0] or 'Intro' in song[0] \
    
             or '配乐' in song[0] or 'Talking' in song[0] or 'Mix' in song[0] or '演奏' in song[0] or '表演' in song[
    
         0] \
    
             or '+' in song[0]:  # 删除非五月天演唱歌曲
    
         continue
    
     if re.findall(m, song[0]):  # 删除日文歌曲
    
         continue
    
     if '(' in song[0]:
    
         name = song[0].split('(')[0]  # 取括号前歌名
    
         song_name = name.replace(",", "").replace(" ", "").replace("!", "")  # 删除歌名中的空格,逗号,感叹号
    
         song_dt[song_name] = song_dt.get(song_name, [song[1], song[2]])  # 如果还有遗漏未删除的重复歌曲,取括号前歌名
    
         continue
    
     song_name = song[0].replace(",", "").replace(" ", "").replace("!", "")  # 删除歌名中的空格,逗号,感叹号
    
     song_dt[song_name] = song_dt.get(song_name, [song[1], song[2]])  # 如果歌名重复,取最下面的行数
    
     song_lst = list(song_dt.items())
    
     song_result = []
    
     for name, index in song_lst:
    
     songstr = f"{name},{index[0]},{index[1]}"
    
     song_result.append(songstr)
    
     return song_result

再定义一个函数cleantxt, 基于已经清洗过的歌名提取歌词内容, 用于后续对五月天歌词中的词汇频率进行分析, 函数代码如下:

复制代码
 def cleantxt(text: list, name_result: list) -> list:  # 根据清洗后的歌名筛选歌词数据

    
     result = []
    
     index = 0
    
     while index < len(name_result):  # 取每首歌的歌词
    
     a, b = eval(name_result[index].split(",")[1]), eval(name_result[index].split(",")[2])
    
     for i in range(a, b):
    
         if ':' in text[i] or ':' in text[i] or '声明' in text[i]:
    
             continue
    
         if text[i]:
    
             result.append(text[i])
    
     index += 1
    
     return result

函数定义完之后,开始执行清洗数据动作。

首先利用tosong函数对歌名进行清洗,并将最终的歌曲名称列表保存至maydaysong_result文件中进行记录

复制代码
 # # 清洗歌曲名数据

    
 lrcs = read_txt('maydaylrc.txt')  # 读取歌词TXT文件
    
 songs = tosong(lrcs)  # 导出歌曲名及歌词首末行位置数据
    
 song_result = cleansong(songs)  # 清洗歌曲名及首末行数据
    
 # print(song_result)
    
 print(f"共{len(song_result)}首歌曲")  # 歌曲数量
    
 songname_result=[]
    
 for i in song_result:
    
     songname_result.append(i.split(",")[0])
    
 print(songname_result)
    
 write_txt("maydaysong_result.txt", '\n'.join(songname_result))  # 把最终歌曲列表写入新文件

生成的maydaysong_result文件,每行只包含一首歌名,如截图:

之后清洗歌词数据,生成纯歌词文件,保存到maydaylrc_result文件。

复制代码
 #  清洗歌词数据

    
 # lrcs = read_txt('maydaylrc.txt') # 读取歌词txt源文件
    
 lrc_result = cleantxt(lrcs, song_result)  # 清洗歌词数据
    
 # print(lrc_result)
    
 filename = r"maydaylrc_result.txt"
    
 write_txt(filename, "\n".join(lrc_result))   # 清洗后歌词写入新文件

三、歌词分词 词频统计

采用jieba对上述生成的歌词文件进行歌词分词处理,并对词云出现频率进行分析计算结果保存至maydaylrc_num文件中代码如下:

复制代码
 # 歌词分词,词频统计

    
 lrc_dt = {}  # 歌词字典存放分词后的词语及次数
    
 lrc_text = ""  # 存放所有分词后的歌词
    
 for lrc in lrc_result:
    
     temp = jieba.lcut(lrc)  # 分词
    
     for i in temp:
    
     # 用于去除无意义的单字,比如“都”、我、你、就 等等
    
     if len(i) >= 2:
    
         lrc_text = lrc_text + i + ' '  # 生成词云文本
    
         lrc_dt[i] = lrc_dt.get(i, 0) + 1  # 统计词频
    
 lrc_items = list(lrc_dt.items())
    
 lrc_items.sort(key=lambda x: -x[1])
    
 print(lrc_items)  # 词语按出现次数从大到小输出
    
 lrc_numlst = []
    
 for i in lrc_items:
    
     lrc_numlst.append(f"{i[0]},{i[1]}")
    
 write_txt('maydaylrc_num.txt', '\n'.join(lrc_numlst))  # 把词频数据写入新文件

五月天歌词出现频率前十名的词语如下:

四、词云图制作

用PS简单做了一张五月天成军日329的白底背景图,作为歌名词云的背景图

用wordcloud生成歌名词云,把图片保存到文件,代码如下:

复制代码
 # 生成歌曲名词云

    
 song_text = ""
    
 for line in song_result:
    
     song_text += line.split(",")[0] + " "
    
 font = "SIMLI.TTF"
    
 img1 = Image.open('maydaymask1.jpg')
    
 mask1 = np.array(img1)  # 导入词云形状
    
 word_pic1 = WordCloud(
    
     collocations=False,
    
     font_path=font,
    
     height=1800,
    
     random_state=30,  # 设置有多少种随机生成状态,即有多少种配色方案
    
     width=1800,
    
     margin=2,
    
     background_color='white',
    
     max_font_size=50,
    
     # colormap='GnBu',
    
     # contour_width=1,
    
     # contour_color='blue',
    
     mask=mask1) \
    
     .generate(song_text)  # 生成词云
    
 word_pic1.to_file('mayday_wc1.png')  # 词云保存到图片

从这张演唱会专辑图片中挑选了一张独特的菜头粿配五月天的图案,并用钢笔绘制出一个路径,在填充黑色之后作为歌词词云展示的基础背景

用Wordcloud生成歌词词云图,把图片保存到文件,代码如下:

复制代码
 # 生成歌词词频词云

    
 img2 = Image.open('maydaymask2.jpg')
    
 mask2 = np.array(img2)  # 导入词云形状
    
 word_pic2 = WordCloud(
    
     collocations=False,
    
     font_path=font,
    
     height=1800,
    
     random_state=30,  # 设置有多少种随机生成状态,即有多少种配色方案
    
     width=1800,
    
     margin=2,
    
     background_color='white',
    
     max_font_size=100,
    
     # colormap='GnBu',
    
     # contour_width=1,
    
     # contour_color='blue',
    
     mask=mask2) \
    
     .generate(lrc_text)  # 生成词云
    
 word_pic2.to_file('mayday_wc2.png')  # 词云保存到图片

用plt显示分别显示两张词云图片,代码如下:

复制代码
 # 显示词云图片

    
 plt.figure(figsize=(16,9))
    
 p1=plt.imread('mayday_wc1.png')
    
 plt.imshow(p1)
    
 plt.axis("off")
    
  
    
 plt.figure(figsize=(16,9))
    
 p2=plt.imread('mayday_wc2.png')
    
 plt.imshow(p2)
    
 plt.axis("off")
    
  
    
 plt.show()

结果如下:

歌名词云图:

歌词词云图:

五、从清洗数据到词云图的代码全文

复制代码
 import numpy as np

    
 from PIL import Image
    
 import jieba
    
 import matplotlib.pyplot as plt
    
 from wordcloud import WordCloud
    
 import re
    
  
    
  
    
 def read_txt(filename: str) -> list:  # 读取源文件
    
     with open(filename, "r") as f:
    
     text = f.read().split("\n")
    
     return text
    
  
    
  
    
 def tosong(text: list) -> list:  # 从lrc文件导出歌曲名及对应歌词首末行
    
     songs = []
    
     for line in text:
    
     if '歌曲名:' in line:
    
         index = text.index(line)  # 歌曲名所在行数
    
         lastindex = index + 1
    
         while lastindex < len(text) and '歌曲名:' not in text[lastindex]:
    
             lastindex += 1  # 该歌曲的歌词最后一行
    
         song_name = line.split(":")[1]  # 取歌曲名
    
         songs.append([song_name, index, lastindex])
    
     return songs
    
  
    
  
    
 def cleansong(songs: list) -> list:  # 清洗歌曲名数据
    
     song_dt = {}
    
     nolst = (
    
     '缩影', '女字旁', '爱的轮回', '刚好', '她并非不想', '我还是爱着你', '无人之境', '是什么让我遇见这样的你', '三城记',
    
     '如果我是石头', '青岛巴士', '寻根(带我去三义)', '诚品激突', '武装以后(演奏曲)',
    
     '那一年的花季(探母瑄瑄版配乐)', '灵魂能有多重', '哪一楼的阿?(台北的脚步)', '三义之夏', '轻功(慢摇滚演奏版)',
    
     '寂寞星球 人人寂寞',
    
     '故事都随风(开往哈尔滨的列车)', '爱是绿洲', '鲨鱼摇滚', '火花一般', '旅途上', '企鹅午后', '公路电影',
    
     '年华', '一个人的表情', '金赛的迷惑', '…的愉快', 'What\'s Your Story?', 'Song for you', '成功間近', '人生有限会社',
    
     '頑固',
    
     'Party Animal', '少年漂流記', 'Song for you', 'Buzzin’', 'Dancin\' Dancin\'', '一歩一歩', '恋愛ING', '乾杯',
    
     '孫悟空',
    
     '末日', '明日', 'To Find My Paradise', 'Enrich Your Life', '探母', '五月之恋序曲', '前传', '咖哩鱼蛋', '海豚的歌',
    
     '神的孩子都在跳舞','刻在我心底的名字','干啦干啦','凡人歌','玫瑰少年')
    
     m = u"[\u3040-\u31FF]+"  # 日文字符
    
     for song in songs:
    
     # 删除live等不同版本
    
     if song[0] in nolst or 'Live' in song[0] or 'live' in song[0] or 'ver' in song[0] or 'Ver' in song[0] \
    
             or 'Remix' in song[0] or '伴奏' in song[0] or '版' in song[0] or '20th' in song[0] or 'Intro' in song[0] \
    
             or '配乐' in song[0] or 'Talking' in song[0] or 'Mix' in song[0] or '演奏' in song[0] or '表演' in song[
    
         0] \
    
             or '+' in song[0]:  # 删除非五月天演唱歌曲
    
         continue
    
     if re.findall(m, song[0]):  # 删除日文歌曲
    
         continue
    
     if '(' in song[0]:
    
         name = song[0].split('(')[0]  # 取括号前歌名
    
         song_name = name.replace(",", "").replace(" ", "").replace("!", "")  # 删除歌名中的空格,逗号,感叹号
    
         song_dt[song_name] = song_dt.get(song_name, [song[1], song[2]])  # 如果还有遗漏未删除的重复歌曲,取括号前歌名
    
         continue
    
     song_name = song[0].replace(",", "").replace(" ", "").replace("!", "")  # 删除歌名中的空格,逗号,感叹号
    
     song_dt[song_name] = song_dt.get(song_name, [song[1], song[2]])  # 如果歌名重复,取最下面的行数
    
     song_lst = list(song_dt.items())
    
     song_result = []
    
     for name, index in song_lst:
    
     songstr = f"{name},{index[0]},{index[1]}"
    
     song_result.append(songstr)
    
     return song_result
    
  
    
  
    
 def cleantxt(text: list, name_result: list) -> list:  # 根据清洗后的歌名筛选歌词数据
    
     result = []
    
     index = 0
    
     while index < len(name_result):  # 取每首歌的歌词
    
     a, b = eval(name_result[index].split(",")[1]), eval(name_result[index].split(",")[2])
    
     for i in range(a, b):
    
         if ':' in text[i] or ':' in text[i] or '声明' in text[i]:
    
             continue
    
         if text[i]:
    
             result.append(text[i])
    
     index += 1
    
     return result
    
  
    
  
    
 def write_txt(filename: str, text: str) -> None:  # 将清洗后数据写入新文件
    
     with open(filename, "w", encoding="utf-8") as f:
    
     f.write(text)
    
  
    
  
    
 # # 清洗歌曲名数据
    
 lrcs = read_txt('maydaylrc.txt')  # 读取歌词TXT文件
    
 songs = tosong(lrcs)  # 导出歌曲名及歌词首末行位置数据
    
 song_result = cleansong(songs)  # 清洗歌曲名及首末行数据
    
 # print(song_result)
    
 print(f"共{len(song_result)}首歌曲")  # 歌曲数量
    
 songname_result=[]
    
 for i in song_result:
    
     songname_result.append(i.split(",")[0])
    
 print(songname_result)
    
 write_txt("maydaysong_result.txt", '\n'.join(songname_result))  # 把最终歌曲列表写入新文件
    
  
    
 #  清洗歌词数据
    
 # lrcs = read_txt('maydaylrc.txt') # 读取歌词txt源文件
    
 lrc_result = cleantxt(lrcs, song_result)  # 清洗歌词数据
    
 # print(lrc_result)
    
 filename = r"maydaylrc_result.txt"
    
 write_txt(filename, "\n".join(lrc_result))   # 清洗后歌词写入新文件
    
  
    
 # 歌词分词,词频统计
    
 lrc_dt = {}  # 歌词字典存放分词后的词语及次数
    
 lrc_text = ""  # 存放所有分词后的歌词
    
 for lrc in lrc_result:
    
     temp = jieba.lcut(lrc)  # 分词
    
     for i in temp:
    
     # 用于去除无意义的单字,比如“都”、我、你、就 等等
    
     if len(i) >= 2:
    
         lrc_text = lrc_text + i + ' '  # 生成词云文本
    
         lrc_dt[i] = lrc_dt.get(i, 0) + 1  # 统计词频
    
 lrc_items = list(lrc_dt.items())
    
 lrc_items.sort(key=lambda x: -x[1])
    
 print(lrc_items)  # 词语按出现次数从大到小输出
    
 lrc_numlst = []
    
 for i in lrc_items:
    
     lrc_numlst.append(f"{i[0]},{i[1]}")
    
 write_txt('maydaylrc_num.txt', '\n'.join(lrc_numlst))  # 把词频数据写入新文件
    
  
    
  
    
 # 生成歌曲名词云
    
 song_text = ""
    
 for line in song_result:
    
     song_text += line.split(",")[0] + " "
    
 font = "SIMLI.TTF"
    
 img1 = Image.open('maydaymask1.jpg')
    
 mask1 = np.array(img1)  # 导入词云形状
    
 word_pic1 = WordCloud(
    
     collocations=False,
    
     font_path=font,
    
     height=1800,
    
     random_state=30,  # 设置有多少种随机生成状态,即有多少种配色方案
    
     width=1800,
    
     margin=2,
    
     background_color='white',
    
     max_font_size=50,
    
     # colormap='GnBu',
    
     # contour_width=1,
    
     # contour_color='blue',
    
     mask=mask1) \
    
     .generate(song_text)  # 生成词云
    
 word_pic1.to_file('mayday_wc1.png')  # 词云保存到图片
    
  
    
 # 生成歌词词频词云
    
 img2 = Image.open('maydaymask2.jpg')
    
 mask2 = np.array(img2)  # 导入词云形状
    
 word_pic2 = WordCloud(
    
     collocations=False,
    
     font_path=font,
    
     height=1800,
    
     random_state=30,  # 设置有多少种随机生成状态,即有多少种配色方案
    
     width=1800,
    
     margin=2,
    
     background_color='white',
    
     max_font_size=100,
    
     # colormap='GnBu',
    
     # contour_width=1,
    
     # contour_color='blue',
    
     mask=mask2) \
    
     .generate(lrc_text)  # 生成词云
    
 word_pic2.to_file('mayday_wc2.png')  # 词云保存到图片
    
  
    
 # 显示词云图片
    
 plt.figure(figsize=(16,9))
    
 p1=plt.imread('mayday_wc1.png')
    
 plt.imshow(p1)
    
 plt.axis("off")
    
  
    
 plt.figure(figsize=(16,9))
    
 p2=plt.imread('mayday_wc2.png')
    
 plt.imshow(p2)
    
 plt.axis("off")
    
  
    
 plt.show()

全部评论 (0)

还没有任何评论哟~