最近在看《 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!