【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”的歌词文件,在每首歌名前面加了“歌曲名:”,方便后面清洗数据。

二、清洗歌词数据

定义一个函数tosong,从“maydaylrc.txt”歌词文件里导出歌曲名和歌词,返回一个列表songs,列表里元素是【歌名,歌词首行位置,歌词末行位置】,函数代码如下:

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,cleansong函数清洗歌名,把最终歌曲名列表写入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文件,每行只包含一首歌名,如截图:

【Python练习】生成五月天歌名词云图和歌词词频词云图_第1张图片

  之后清洗歌词数据,生成纯歌词文件,保存到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的白底背景图,作为歌名词云的背景图

【Python练习】生成五月天歌名词云图和歌词词频词云图_第2张图片

 用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')  # 词云保存到图片

再从演唱会图集里面选了一张菜头粿驼着五月天的图,用钢笔勾了个路径,填充黑色,作为歌词词云的背景图

【Python练习】生成五月天歌名词云图和歌词词频词云图_第3张图片

 用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()

结果如下:

歌名词云图:

【Python练习】生成五月天歌名词云图和歌词词频词云图_第4张图片

歌词词云图:

【Python练习】生成五月天歌名词云图和歌词词频词云图_第5张图片

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

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()

你可能感兴趣的:(python)