突发奇想,研究一下 ACG 歌词的用词特点。

获取歌词

找到了一个共有 4572 首歌(2023-5-10)的歌单,https://music.163.com/#/playlist?id=66652611 ,尝试获取其中所有歌的歌曲 id。

首先尝试查找有没有合适的 API,失败,尝试从歌单页面入手。

可以看到,网易云网页查看别人创建的歌单只可以看到前 20 首歌曲。但是如果我们将歌单复制到自己的自建歌单,则可以显示 1000 首。理论上可以创建 5 个歌单,然后进行获取。

分析网页结构,可以发现每个歌曲都是一个 a 标签,里面的 href 就是我们要的内容。

尝试在控制台编写一个 JS 来获取所有个歌曲 id。

1
document.querySelectorAll("div.f-cb span.txt > a")

尝试使用这个选择器,发现能够成功获取所有 a 标签,接下来就是获取所有 a 标签的 href 属性。

1
2
let result = ""
document.querySelectorAll("div.f-cb span.txt > a").forEach((x)=>{result+=x.href+"\n"})

使用 .forEach 进行遍历,得到 result 后复制,则得到了一份歌曲列表。

接下来,使用 python 获取所有歌词。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import requests
import json
import re
import time

def get_lyric(song_id):
url = "http://music.163.com/api/song/lyric?id={}+&lv=1&tv=-1"
r = requests.get(url.format(song_id))
jsonObj = json.loads(r.text)
lrc = jsonObj["lrc"]["lyric"]
result = ""
for line in lrc.split("\n"):
if '作词' in line or '作曲' in line or '編曲' in line or '詞:' in line:
continue
result += line[line.find(']')+1:]+"\n"
return result

songs = open("songlist.txt","r").read().split("\n")

unique_songs = list(set(songs))

print(f"{len(songs)} Songs, {len(unique_songs)} Unique. ({100*len(unique_songs)/len(songs):.2f}%)")


pat = r"https://music.163.com/song\?id=([\d]+)"
now = 34
tot = len(unique_songs)


with open("dataNew.txt","a",encoding="utf-8") as f:
while now != tot:
try:
song = unique_songs[now]
# print(song)
print(f"\tnow #{now}: ",re.search(pat,song).group(1))
lrc = get_lyric(re.search(pat,song).group(1))
f.write(lrc)
except KeyboardInterrupt:
break
except:
print("FAILED!!!!!!!!!!!!!!!!")
now += 1

分词

搜索对日语进行分词,得到了 MeCab 库。

1
2
pip install mecab-python3
pip install unidic-lite
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import MeCab

text = """残酷な天使のように
少年よ 神話になれ
蒼い風がいま
胸のドアを叩いても
私だけをただ見つめて
微笑んでるあなた
そっとふれるもの
もとめることに夢中で
運命さえまだ知らない
いたいけな瞳
"""

mecab_tagger = MeCab.Tagger()
x = mecab_tagger.parse(text)
open("out.txt","w",encoding="utf-8").write(x)

得到的结果是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
残酷	ザンコク	ザンコク	残酷	名詞-普通名詞-形状詞可能			0
な ナ ダ だ 助動詞 助動詞-ダ 連体形-一般
天使 テンシ テンシ 天使 名詞-普通名詞-一般 1
の ノ ノ の 助詞-格助詞
よう ヨー ヨウ 様 形状詞-助動詞語幹 1
に ニ ダ だ 助動詞 助動詞-ダ 連用形-ニ
少年 ショーネン ショウネン 少年 名詞-普通名詞-一般 0
よ ヨ ヨ よ 助詞-終助詞
神話 シンワ シンワ 神話 名詞-普通名詞-一般 0
に ニ ニ に 助詞-格助詞
なれ ナレ ナル 成る 動詞-非自立可能 下一段-ラ行 連用形-一般 2
蒼い アオイ アオイ 青い 形容詞-一般 形容詞 連体形-一般 2
風 カゼ カゼ 風 名詞-普通名詞-一般 0
が ガ ガ が 助詞-格助詞
いま イマ イマ 今 名詞-普通名詞-副詞可能 1
胸 ムネ ムネ 胸 名詞-普通名詞-一般 2
の ノ ノ の 助詞-格助詞
ドア ドア ドア ドア-door 名詞-普通名詞-一般 1
を オ ヲ を 助詞-格助詞
叩い タタイ タタク 叩く 動詞-一般 五段-カ行 連用形-イ音便 2
て テ テ て 助詞-接続助詞
も モ モ も 助詞-係助詞
...
...
EOS

我们只保留实词(名词,形容词,动词),同时还原回原形。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def process(src):
result = ""
for line in src.split("\n"):
if line == "EOS":
break

word, hin = line.split("\t")[3],line.split("\t")[4]

if not (hin.startswith("名詞") or hin.startswith("動詞") or hin.startswith("形容詞")):
continue
if word[0].isascii(): # 英语词汇
continue
result += word.split('-')[0] # 外来语的英语部分
result += " "
return result

特别的,我们删去了所有英语词汇以及外来语的英语部分。

这样函数返回的结果就可以直接用来绘制词云。

绘制词云

使用 wordcloud 库。

1
pip install wordcloud

通过指定字体,使其支持中文。

1
2
3
font_path = "C:\\WINDOWS\\FONTS\\MEIRYO.TTC" 
wc = WordCloud(width=1920,height=1080,font_path=font_path,background_color="white").generate(result)
wc.to_file("wc.png")

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import MeCab
from wordcloud import WordCloud
from collections import Counter

def process(src):
result = ""
for line in src.split("\n"):
if line == "EOS":
break

word, hin = line.split("\t")[3],line.split("\t")[4]

if not (hin.startswith("名詞")): # or hin.startswith("動詞") or hin.startswith("形容詞")):
continue
if word[0].isascii():
continue
result += word.split('-')[0]
result += " "
return result




mecab_tagger = MeCab.Tagger()
parse_result = mecab_tagger.parse(open("dataNew.txt",encoding="utf-8").read())


result = process(parse_result)
open("words.txt","w",encoding="utf-8").write(result)
print(len(result.split()),"words")

counter = Counter(result.split())

with open("frequency2.csv","w",encoding="utf-8") as f:
for word,freq in counter.most_common():
f.write(f"{word},{freq}\n")

font_path = "C:\\WINDOWS\\FONTS\\MEIRYO.TTC"
wc = WordCloud(width=1920,height=1080,font_path=font_path,background_color="white").generate(result)
wc.to_file("wc-noun.png")

结论

所有实词

可以看到,出现最多的 5 个是日语中很常用的几个动词和表示否定的 ない。

词语 出现次数
為る 5083
居る 4006
無い 3851
行く 3076
成る 2478

其中 なる(為る、成る) 共出现了 5083+2478=7561 次,这是因为日语中 なる 承担了很多功能。(~になる、~Aくなる)。

いる(居る) 共出现了 4006 次,いる 除了表示有生命的物体存在,还可以作补助动词表示动作的进行、习惯等,也非常常用。

行く 可以表示去,还可以作补助动词表示动作的持续。

名词

尝试查看歌曲中名词的词云。

最多的是 こと(事),猜测因为 こと 可以作为形式体言以及部分表达方式中的一部分,导致频率极高。

以下是其他出现频数超过 100 的名词(按出现频率排序)。

今、心、夢、世界、一、手、未来、時、空、明日、中、日、度、物、光、胸、侭、思い、涙、自分、声、今日、全て、人、為、二人、目、風、何、言葉、愛、前、星、道、本当、笑顔、一人、恋、場所、気持ち、夜、先、日々、皆、花、编曲、気、運命、側、希望、全部、瞳、一緒、歌、終わり、意味、色、勇気、闇、痛み、幸せ、力、二、月、嘘、回、雨、永遠、答え、願い、時間、悲しみ、筈、約束、記憶、见、奇跡、景色、後、街、音、顔、命、朝、孤独、瞬間、向こう、最後、鼓動、過去、昨日、共、影、毎日、思い出、果て、夏、傷、感情、真実、背中、下、駄目、自由、翼、始まり、絶対、絆、身、頃、奥、神、上、所、間、方、現実、不安、太陽、扉、理由、九、欠片、温もり、爱、大人、幾、体、物語、出会い、息、季節、遠く、年、是、雲、秘密、隣り、梦、最高、次、名前、゙、海、形、仆、旅、ハート、风、アニメ、虹、期待、歩、訳、魂、指、頬、强、宇宙、後悔、其々、迷い、輝き、存在、笑い、振り、限界、時代、羽根、言、理想、テーマ、魔法、人生、壁、青春、姿、夜明け、一瞬、数、祈り、桜、不思議、以上、一杯

可以发现,ACG 歌曲的用词确实有一些特点。