许巍歌词生成器

最近在看《 Python 网络数据采集》,其中有一章是自然语言处理,讲到了根据文本分析生成一个马尔可夫链,然后根据马尔科夫链来生成一篇看上去像是人写的文章。

其实具体来说就是生成下面这样的字典:

{
    'word_a': {'word_b': 3, 'word_c': 6, 'word_d': 1},
    'word_b': {'word_e': 1, 'word_f': 5},
    ......
}

上面的字典中的第一个子字典表示了 word_a 的下一个单词是 word_b ,出现了 3 次;是 word_c ,出现了 6 次;是 word_d ,出现了 1 次。

生成这个字典后,然后从一个单词开始,根据下一个单词出现的权重随机取下一个单词。重复这个过程,就生成了一篇看起来像是"人"写的文章。

受此启发,作为许巍的脑残粉,我打算做一个许巍歌词生成器,哈哈。

抓取歌词

谷歌了一下"歌词下载",找到了一个还算比较容易的歌词下载网站。可惜网站的编码是 gb2312 ,还得用 iconv 来转成 utf8 ;另外就是 182 个歌词中,其实有相当一部分是重复的,还要去重。总体来说,只要是正则玩得溜,写出这个脚本并不难,直接用 十来行的 shell 可以搞定:

#!/bin/bash

PAGES=7  
: > ./songs.list

for p in `seq 1 ${PAGES}`; do  
    html=$(curl -s "http://www.cnlyric.com/geshou/2932_${p}.html" | iconv -f gb2312 -t utf8)
    songs=$(echo "${html}" | /bin/grep -Po '(?<=target="_blank">)[^>]+(?=</a></h1>)')
    for song in ${songs}; do
    if ! /bin/grep "${song}" ./songs.list; then
        echo "${song}" >> ./songs.list
        number=$(echo "${html}" | /bin/grep -Po "\d+(?=\.html\" target=\"_blank\">${song}</a></h1>)")
        curl -s "http://www.cnlyric.com/LrcDown/2932/${number}.lrc" | iconv -f gb2312 -t utf8 >> ./all.txt
    fi
    done
done  

跑完脚本,总共抓了 102 首歌词,共 3319 行中文文本。

文本处理

下载的是 lrc 文件:

[00:00.80] 没有什么能够阻挡
[00:06.53] 你对自由地向往
[00:11.59] 天马行空的生涯
[00:16.53] 你的心了无牵挂

首先得去掉每行前面这些标示时间的字符串。对于我的需求来说,只需要获取中文汉字,于是可以用下面的命令去掉非汉字:

sed -ire 's/[a-zA-Z0-9\u4e00-\u9fa5]//g' all.txt  

当然还有其他的无用字符,比如作词作曲歌手等信息,还有歌词上传者的夹私货等,没有什么有效的办法,只好人肉来处理了,这是最耗时间的。

分词

对于书上的例子,由于是英文单词,空格分隔,没有分词的问题。但中文就需要分词了,将一个句子分隔为有意义的词语。目前最好用的中文分词,当然就是 jieba 了。

使用 pip 安装:

pip install jieba  

测试下分词的效果:

echo "没有什么能够阻挡" > test.txt  
python -m jieba test.txt  

结果如下:

没有 / 什么 / 能够 / 阻挡

非常赞。

脚本

最后的脚本,其实没什么好说的,直接贴代码了:

#coding:utf8

import os  
import sys  
if sys.getdefaultencoding() != 'utf-8':  
    reload(sys)
    sys.setdefaultencoding('utf-8')
from random import randint  
import jieba

def cut_words(filename):  
    with open(filename) as f:
        content = f.read()
        seg_list = jieba.cut(content, cut_all=True)
        word_list = [str(x) for x in seg_list if x not in ['', ' ']]
        with open('./words.txt', 'w') as wf:
            wf.write(' / '.join(word_list))
    return None

def get_word_list(filename):  
    with open(filename) as f:
        content = f.read()
        word_list = content.split(' / ')
        return word_list
    return None

def build_word_dict(word_list):  
    word_dict = {}
    for i in range(0, len(word_list)-2):
        if word_list[i] not in word_dict:
            word_dict[word_list[i]] = {}
        if word_list[i+1] not in word_dict[word_list[i]]:
            word_dict[word_list[i]][word_list[i+1]] = 0
        word_dict[word_list[i]][word_list[i+1]] += 1
    return word_dict

def get_random_word(sub_word_dict):  
    index = randint(0, len(sub_word_dict)-1)
    return sub_word_dict.keys()[index]

if __name__ == "__main__":  
    if not os.path.exists('./words.txt'):
        cut_words('./lrc.txt')
    word_list = get_word_list('./words.txt')
    word_dict = build_word_dict(word_list)
    length = 100
    chain = ""
    current = "我"
    for i in range(0, length):
        chain += current
        current = get_random_word(word_dict[current])
    print chain

效果

跑了几次脚本,生成了下面这个令我惊艳的词:

我独自哭泣
一次的使然
悠闲的月亮

天空

时间就听到这午夜
午夜
是
越过遥远
鲜花那样灿烂
红尘中起舞轻盈

一次抚动琴弦它就这样坐看云开
比我永保赤子的心房
燃烧的朝露
抚动琴弦
经历风雨过后我眩晕的转变
向着灿烂星空多澎湃
只为它悄然结满秋天和清晨里的成长
到如今这光明默默照耀这无限
烦恼

以及这样的:

我就会说
像美丽
溶进灿烂
寒冬
波浪追逐
跃动闪亮的另一天面临崩溃的逍遥自在

青春的天边夕阳是自由和欢乐来临
生命如此爱
面朝沧海早已经干枯不再
遥远无尽星空多遥远

清晨到结束
只是为你衣裙在侧耳倾听着天边夕阳再次走过的深处
徘徊期待
温柔无法挽留
无论相距有多灿烂夕阳
这个世界更精彩
奇异的生活经过着自由地唱

是不是有点许巍的味道?

Have Fun!