文章目录
  1. 1. 关键词
  2. 2. 前言
  3. 3. 获取数据
  4. 4. 统计
  5. 5. 结果分析
  6. 6. 后记

关键词

结巴分词 中文分词 TF-IDF 词频 逆文档频率

前言

自从第一次听到许巍的歌,就深深地迷上了他的歌,感觉每一首都钻到了我的心里,也早已把他的歌听得烂熟了。许巍,我最喜欢的歌手,没有之一!

本文我要解决以下问题:

爬取许巍每首歌的歌词,统计他最喜欢用哪些词(即歌词的关键词)

很简单地想到就是直接统计每个词出现的次数(词频TF,词出现的频率,下文同),这样确实可以得到大部分的信息。但是也有两点不好:

  • 1.有很多诸如”的”,”一个”,”没有”等无实意的助词等可能会得到很高的词频,这些词叫做“停用词”,当然我们可以把它们去掉,像我的代码里就去掉了长度小于2的词,两个词及以上就不能再去了,但是还是会有一些残留。
  • 2.如果出现次数一样呢?当然如果只是为了看下每个词出现的情况,大概了解一下,其实统计下词频就够了,但是为了充分利用数据(好不容易爬来的。。),并得到更多的信息,不妨再多做一步吧。

考虑词频相同的两个词A和B,都出现n次,如果A只在一篇文章出现,出现n次,而B在n篇文章中出现,每篇都出现1次,那么我们认为B这个词很常见,大家都有,所以并不重要,也不能够很好地区分文档;而词A要出现只出现在一篇文章,出现地点比较少,大家不常见到,且能够很好地区分出那篇文档,那么认为它更加重要。自然想到重要性的体现用某个单词在多少篇文档中出现的次数来衡量(sfd),这就是IDF(逆文档频率 Inverse Document Frequency)。
上面两个指标TF和IDF乘起来就得到了著名的TF-IDF算法fd的核心。
什么是TF-IDF算法?
下面是计算方法:
由于每首歌的长短不同,所以我们将词频归一化,即某个词的词频记为该词在每篇文章中的次数除以该篇文章的总词数的累加和。

$$ TF_i = \sum{Freq_i \over Length(D_d)}$$

而IDF记为文档总数除以出现该词的文档数,显然,出现某个词的文档数越多,分母越大,则IDF越小,比重越小。

$$IDF_i = log({|D| \over numDoc_i})$$

$$TF-IDF = TF \times IDF$$

获取数据

获取数据细节不说了,就是查看网页源代码,然后制定爬取策略,我没有用框架,但是代码量也不大。如果不熟悉爬取数据的方法,可以看我这几篇文章:

  • 【Python模拟登录】(二) 加密与重放方式登录 -以模拟登录博客园为例
  • 【Python数据分析】Python3操作Excel-以豆瓣图书Top250为例
  • 【Python数据分析】简单爬虫 爬取知乎神回复
  • 【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
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    import requests
    import re
    import urllib
    from bs4 import BeautifulSoup
    import codecs

    def getPostdata(artistId=1158):
    s = requests.Session()
    postData = {'type': 'artSong',
    'artistId': artistId
    }
    return s,postData

    def PrintLis(lis):
    for j in range(len(lis)):
    print(lis[j])

    def Cleanit(Str):
    Str = Str.replace('</br>','')
    Str = Str.replace('<br/>','')
    Str = Str.replace('</div>','')
    Str = Str.replace('<div class="lrc" id="lrc_yes">','').strip()
    returnLis = Str.split('<br>')
    return returnLis

    def WriteLis(fr,lis):
    for li in lis:
    fr.write(li + '\r\n')

    def Crawl(s,headers,crawlist,crawlist_name):
    path_base = "D:\\Python Workspace\\Data\\LRC_许巍\\"
    geciurl_base = "http://www.kuwo.cn/geci/l_"
    for i in range(len(crawlist)):
    ID = crawlist[i]
    geciurl = geciurl_base + ID
    filename = str(i)
    path = path_base + filename + '.txt'
    print(crawlist_name[i],path,geciurl)
    fr = codecs.open(path,'w')
    req = s.get(geciurl,headers=headers)
    soup = BeautifulSoup(req.text,"html.parser")
    lrc_yes = soup.findAll(attrs = {'id': 'lrc_yes'})
    if len(lrc_yes) == 0:
    continue
    returnLis = Cleanit(str(lrc_yes[0]))
    #PrintLis(returnLis)
    fr.write(crawlist_name[i] + '\r\n')
    WriteLis(fr,returnLis)
    fr.close()
    if i >= 103:
    break

    def Get():
    headers = {
    'Accept': 'application/json, text/javascript, */*; q=0.01',
    'Referer': 'http://passport.cnblogs.com/user/signin',
    'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36'
    }
    url = "http://www.kuwo.cn/geci/wb/getJsonData"
    s,postData = getPostdata()
    idtoname = {}
    Mylis = []
    for i in range(1,23):
    postData['page'] = i
    req = s.post(url,data = postData,headers=headers)
    dic = req.content.decode()
    patids = re.compile('\"rid\":\"(.*)\"')
    patnames = re.compile('\"name\":\"(.*)\"')
    pat2 = re.compile('\[(.*)\]')
    js = pat2.findall(dic)[0]
    pat3 = re.compile('\{(.*?)\}')
    lis = pat3.findall(js)
    print(len(lis))
    for li in lis:
    sonlis = li.split(',')
    ids = patids.findall(sonlis[0])
    names = patnames.findall(sonlis[1])
    Mylis.append([ids[0],names[0].split(' ')[0].split('-')[0].split('(')[0]])
    #print(ids[0],names[0].split(' ')[0].split('-')[0])
    idtoname[ids[0]] = names[0]
    nameset = set([])
    crawlist = []
    crawlist_name = []
    for items in Mylis:
    #print(items)
    if items[1] not in nameset:
    nameset.add(items[1])
    crawlist.append(items[0])
    crawlist_name.append(items[1])

    Crawl(s,headers,crawlist,crawlist_name)

    if __name__ == '__main__':
    Get()

包括了去重等操作,不多次爬取同名歌曲的歌词, 这样下来也有160多个,显然不科学,我自己删掉了一些明显不合理的,剩下不到100个文件,其实他的原创歌曲好像也就几十首来的,这些文件还有一些是空文件,每个歌词文件第一行我都写了歌曲名字,对统计影响不大,其他歌词里或许还有其他信息什么的,影响也不大,就不管了。

统计

然后就是统计,计算:
中文分词用的是结巴分词,还是挺准确的。
安装的话(Python 3.x, Windows)最简便的方法就是: pip3 install jieba3k
一个简单的分词例子:

1
2
3
4
5
6
7
8
>>> import jieba
>>> list(jieba.cut('我爱北京天安门',cut_all=False))
Building prefix dict from C:\Users\Allen\AppData\Local\Programs\Python\Python35-32\lib\site-packages\jieba\dict.txt ...
Loading model from cache C:\Users\Allen\AppData\Local\Temp\jieba.cache
Loading model cost 1.318878412246704 seconds.
Prefix dict has been built succesfully.
['我', '爱', '北京', '天安门']
>>>

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
import jieba
from operator import itemgetter
from math import log

def getpath(i):
path = "D:\\Python Workspace\\Data\\LRC_许巍\\%d.txt" % i
return path

def analysis():
TF = {}
IDF = {}
Count = 0.0
for i in range(103):
filepath = getpath(i)
nowdic = {}
SumWords = 0.0
try:
with open(filepath) as fr:
for line in fr.readlines():
line = line.strip()
lis = list(jieba.cut(line,cut_all=False))
SumWords += float(len(lis))
for li in lis:
if len(li) <= 1:
continue
if li not in IDF.keys():
IDF[li] = [i]
else:
IDF[li].append(i)
if li not in nowdic.keys():
nowdic[li] = 1.0
else:
nowdic[li] += 1.0
Count += 1.0
if SumWords > 0.0:
for key in nowdic.keys():
nowdic[key] = float(nowdic[key] / SumWords)
if key not in TF.keys():
TF[key] = nowdic[key]
else:
TF[key] += nowdic[key]
except IOError as err:
pass
for key in IDF.keys():
numDoc = float(len(set(IDF[key])))
IDF[key] = log(Count / (numDoc + 1.0))
TF_IDF = TF
for key in TF_IDF.keys():
TF_IDF[key] = float(TF_IDF[key] * TF[key])
print(len(TF))
sortedDic = sorted(TF_IDF.items(),key=itemgetter(1),reverse=True)
i = 0
fw = open('D:\\Python Workspace\\Data\\许巍TFIDF.txt','w')
for key in sortedDic:
fw.write(str(key[0])+' '+str(key[1])+'\r\n')
i += 1
if i >= 40:
continue
print(key[0],key[1])
fw.close()

if __name__ == '__main__':
analysis()

结果分析

最后得出TF-IDF值最高的几个词如下:

单词 出现次数 TF-IDF
世界 106 0.364377
温暖 68 0.145753
我们 69 0.141222
春天 55 0.113613
遥远 45 0.079318
永远 53 0.076681
天空 46 0.072888
梦想 48 0.068688
阳光 47 0.064827
自由 41 0.060018

IDF最高的几个词如下:

IDF Top 5 出现次数 IDF
结束 1 3.8712
韵律 1 3.8712
木马 6 3.8712
恍若隔世 2 3.8712
土壤 1 3.8712

由以上两个表可以看出,“世界”这个词的TF-IDF值是最高的,而且比其它词高出很多,说明它出现的多也比较集中,比如:

心中那自由的世界 –《蓝莲花》
让我们的世界绚丽多彩 –《旅行》
那理想世界 就像一道光芒 –《那一年》
想了解这世界 命运 –《灿烂》

等等,看来许巍真的很喜欢用世界这个词啊。
还可以看出IDF高的基本都是出现很少的,因为太集中了,就出现在一首歌里。。

后记

数据拿到了,下一步可以考虑拓展地做一些工作了,比如计算两首歌的相似性,看哪两首歌最像,可以参考余弦相似性,或者自动生成每首歌的摘要,或以词搜歌什么的,读者有什么好的想法欢迎评论哦:D

数据科学 | Data Science