突发奇想,研究一下 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 requestsimport jsonimport reimport timedef 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):.2 f} %)" ) 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 (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 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 MeCabfrom wordcloud import WordCloudfrom collections import Counterdef 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("名詞" )): 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 歌曲的用词确实有一些特点。