Google Plus 文本提取与分析

数据提取、Bosen分词、NLTK、TFIDF、余弦相似度等

Posted by T.L on October 19, 2016

本文所有数据源自google+,全篇围绕五个方面来进行文本提取和分析:

  1. 数据获取
  2. 中文分词
  3. NLTK
  4. 特征词提取
  5. 文本相似度

除此之外本文还设计到情感词分析,齐普夫定律等。其他方法像摘要自动提取、意见挖掘、文本聚类、新闻分类等常规文本分析内容并不适合google+的数据集,因此本文没有涉及。

获取数据

准备

google+ api 获取授权: 左侧栏第三个,点“凭据”,创建api密钥。这里用api密钥就可以,毕竟只采集公共数据信息,不是用户隐私数据(需要用户认证)。
Google+ API文档中具体列出了使用方法。此外,有专门的API浏览器可以根据查询条件快速生成RESTful对应的url地址,得到查询结果。(使用之前先设置右上角的API id)

RESTful API

以查询某个用户为例。

  1. 使用API浏览器
  2. 直接浏览器抓取
1
curl "https://www.googleapis.com/plus/v1/people?query=Trucy+Luce&key=$api_key"

{
“kind”: “plus#peopleFeed”,
“etag”: “"xw0en60W6-NurXn4VBU-CMjSPEw/ycgzanUve1bCMxC0NXlP6C4MSTM"”,
“selfLink”: “https://www.googleapis.com/plus/v1/people?query=Trucy+Luce&key=AIzaSyB2_mjqVwT5IpqGO8wfASMdnHUuvxwQqlI”,
“title”: “Google+ People Search Results”,
“nextPageToken”: “CAESFTExMjAzMzk2OTk4NDI0NTc3MzQ3NQ”,
“items”: [
{
“kind”: “plus#person”,
“etag”: “"xw0en60W6-NurXn4VBU-CMjSPEw/xLkYpT8h4zlkDrhCUQcTzEZDl1U"”,
“objectType”: “person”,
“id”: “112033969984245773475”,
“displayName”: “Trucy Luce”,
“url”: “https://plus.google.com/112033969984245773475”,
“image”: {
“url”: “https://lh3.googleusercontent.com/-L0oIZ63NyHk/AAAAAAAAAAI/AAAAAAAAABg/xr0lpRdSDLM/photo.jpg?sz=50”
}
}
]
}

关于json中key的解释,可以在API文档中找到。

利用python SDK

google api for python 有两个版本,一个是oauth2授权的,另一个就是简单的api_key授权的。我们使用第二个,这是其API文档。该客户端已经支持python3版本,这里本文代码都以python3为基础。

1
2
3
4
5
6
7
8
9
import httplib2
import json
import apiclient.discovery # pip install google-api-python-client

Q = "Trucy Luce"
API_KEY = '自己输入' 
service = apiclient.discovery.build('plus', 'v1', http=httplib2.Http(), developerKey=API_KEY)
people_feed = service.people().search(query=Q).execute()
print (json.dumps(people_feed['items'], indent=2))

build方法详细查看代码中的api说明,返回一个与服务交互的资源对象(” A Resource object with methods for interacting with the service. “ )。而这个对象实际上是调动了 build_from_documen这个方法,这个方法返回的是googleapiclient.discovery.Resource资源对象,定位到这个Resource资源对象,但仔细查找发现其方法下面没有people()方法,但有三个生成方法的方法:

  1. _add_basic_methods
  2. _add_nested_resources
  3. _add_next_methods

实际上这三个方法是根据response的schem层次结构来生成的,这里的代码值得学习。 最后只打印items部分,程序执行结果如下:

[
{
“etag”: “"xw0en60W6-NurXn4VBU-CMjSPEw/xLkYpT8h4zlkDrhCUQcTzEZDl1U"”,
“displayName”: “Trucy Luce”,
“objectType”: “person”,
“image”: {
“url”: “https://lh3.googleusercontent.com/-L0oIZ63NyHk/AAAAAAAAAAI/AAAAAAAAABg/ xr0lpRdSDLM/photo.jpg?sz=50”
},
“id”: “112033969984245773475”,
“kind”: “plus#person”,
“url”: “https://plus.google.com/112033969984245773475”
}
]

接下来挖掘下某个用户的近期活动,实际上就像推特或者一条说说。这次挑个说中文的“Mulin Hong”。

1
2
3
4
5
6
7
8
USER_ID = '102121409478764742904' # Mulin Hong
service = apiclient.discovery.build('plus', 'v1', http=httplib2.Http(), developerKey=API_KEY)
activity_feed = service.activities().list(
  userId=USER_ID,
  collection='public',
  maxResults='100' # 最大允许记录条数
).execute()
print (json.dumps(activity_feed, ensure_ascii=False,indent=2))

service.activities().list方法有几个必要参数,userId:活动发起者Id。可选参数:collection:活动归属的集,默认为public,还是看下面图片吧:

其他参数可以通过命令help(service.activities().list)来查看。 由于共7000多行,不方便直接放入文章,可以点击查看内容。

如果仔细观察内容,你会发现content中有许多html标签,像<br>之类的在文本中应该去除,这里用到python Beautifulsoup4包1,(Beautiful Soup)已成为和lxml、html6lib一样出色的python解释器,为用户灵活地提供不同的解析策略或强劲的速度,提供一些简单的、python式的函数用来处理导航、搜索、修改分析树等功能。它是一个工具箱,通过解析文档为用户提供需要抓取的数据,因为简单,所以不需要多少代码就可以写出一个完整的应用程序。简单来说,Beautiful Soup是python的一个库,最主要的功能是从网页抓取数据。更多关于Beautiful Soup的内容可以参见学习教程2

随便拿条内容: 可以发现里面除了含有html标签还有BOM字符(\ufeff),使用bs4去除:

我们上面只拿了100条记录,能不能拿全部记录呢?我们在看一下爬下的json文件,发现第一行显示nextPageToken,也就是说还有下一页,我们可以通过service.activities().list_next方法拿到下一页的resopnse。最后我们拿到所有json并用上面方法清洗整理。

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
import os
import httplib2
import json
import apiclient.discovery
from bs4 import BeautifulSoup

USER_ID = '102121409478764742904' # Mulin Hong
MAX_RESULTS = 400 # 最大纪录数,每页100条,也就是说最多5页(0~4页)
API_KEY = '你自己的api_key' 

def cleanHtml(html):
  if html == "": return ""
  return BeautifulSoup(html,'lxml').get_text()[:-1]

service = apiclient.discovery.build('plus', 'v1', http=httplib2.Http(), 
                                    developerKey=API_KEY)
activity_feed = service.activities().list(userId=USER_ID,
  collection='public',
  maxResults='100' 
)

activity_results = []

while activity_feed != None and len(activity_results) < MAX_RESULTS:
  activities = activity_feed.execute()
  if 'items' in activities:
    for activity in activities['items']:
      if activity['object']['objectType'] == 'note' and activity['object']['content'] != '':        
        activity['title'] = cleanHtml(activity['title'])
        activity['object']['content'] = cleanHtml(activity['object']['content'])
        activity_results += [activity]
#查看下一页
  activity_feed = service.activities().list_next(activity_feed, activities)
# 写到一个json文件中去
f = open(os.path.join('resources', USER_ID + '.json'), 'w')
f.write(json.dumps(activity_results,ensure_ascii=False, indent=2))
f.close()
print (str(len(activity_results)), "activities written to", f.name)

最后输出结果”422 activities written to resources/102121409478764742904.json”,共422条被输出。查看

中文分词

为了进一步对每条记录分析,有必要进行中文分词。文章3中提到11款开放中文分词引擎,从分词效果和调用难度角度考虑,这里采用商业化的BosonNLP工具(关键被他一句广告吸引“现在加入BosonNLP,可获得分词与词性标注引擎不限量调用额度!”)。 注册波森后会给你一个api_key,和google+一个原理。安装python版SDK: pip install -U bosonnlp 。api文档很简单,看一遍就直接用了。

引用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
In [1]: from bosonnlp import BosonNLP

In [2]: nlp = BosonNLP('你自己的api_key')

In [3]: s='当年北京市长夸下海口: 2017年治不了雾霾就提头来见      2016年10月16日,北京市空气污染指数读数    嗯,
   ...: 北京市长同志,你只有两个多月的时间了……'

In [4]: nlp.tag(s)[0]['word']
Out[6]:
['当年',
 '北京',
 '市长',
 '夸',
 '下',
 '海口',
 ':',
 '2017年',
 '治',
 '不',
 '了',
 '雾霾',
 ...

直接使用里面的api进行情感分析:

1
2
3
4
5
from bosonnlp import BosonNLP
nlp = BosonNLP('你的api_key')
all_content = [a['object']['content'] for a in activity_results ]
emos=nlp.sentiment(all_content[:100], model='weibo')#单词最多只能分析100个,每日最多500个
drawdata=list(zip(*emos))[0]

绘制箱线图和统计直方图:

从图中可以看出,整体情感偏上,这里只计算了100条,有兴趣把一天的免费记录数500条全计算完了看看结果。当然情感算法用的很普遍了,自己实现个也非难事。 hrKOofx5.10057.yUUmemEd43Dm

NLTK

斯坦福大学自然语言处理组是世界知名的NLP研究小组,他们提供了一系列开源的Java文本分析工具,包括分词器(Word Segmenter),词性标注工具(Part-Of-Speech Tagger),命名实体识别工具(Named Entity Recognizer),句法分析器(Parser)等,可喜的事,他们还为这些工具训练了相应的中文模型,支持中文文本处理。NLTK4是针对python的一个自然语言处理平台,在windows、mac、linux平台上通用,并且是开源免费的,国内好多项目都是在它基础上做的。 下面用nltk分析下抓到的数据。

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
import nltk
from bosonnlp import BosonNLP

nlp = BosonNLP('你自己的api_key')

#将所有记录转化为一个词语数组tokens
contents='。'.join(all_content)
tokens=nlp.tag(contents)[0]['word']
text = nltk.Text(tokens)

text.concordance("三星")#查看出现‘三星’的语句,可选参数包括语句数目和语句长度
'''
Displaying 4 of 4 matches:
 北京 市长 准备 如何 收场 ? 。 # 看 图 不 说话 1016 : 三星 手机 名不虚传 。 How to mess up # StarWars a
假 论文 , 那 就 是 丢脸 了 。 # 看 图 不 说话 0923 : 三星 一生 黑 http://cinacn.blogspot.com/2016/
 : 心里 想 说 没事 了 。 # 看 图 不 说话 0917 : 使用 三星 手机 的 后果 。 这个 错误 有点 大 了 。 看看 朝鲜 战争 大事记
 你们 开学 一次性 交 多少 钱 。 # 看 图 不 说话 0906 : 三星 手机 的 中东 版 。 舆情 监控 , 已经 是 大陆 媒界 不用 公开 
'''
text.collocations()#最搭配的用词
'''
Displaying 4 of 4 matches:
 北京 市长 准备 如何 收场 ? 。 # 看 图 不 说话 1016 : 三星 手机 名不虚传 。 How to mess up # StarWars a
假 论文 , 那 就 是 丢脸 了 。 # 看 图 不 说话 0923 : 三星 一生 黑 http://cinacn.blogspot.com/2016/
 : 心里 想 说 没事 了 。 # 看 图 不 说话 0917 : 使用 三星 手机 的 后果 。 这个 错误 有点 大 了 。 看看 朝鲜 战争 大事记
 你们 开学 一次性 交 多少 钱 。 # 看 图 不 说话 0906 : 三星 手机 的 中东 版 。 舆情 监控 , 已经 是 大陆 媒界 不用 公开 
'''
text.collocations()#最搭配的用词
'''
@Homebaby Wen; little pink; sounds cute; 核废料 处理厂; 诺贝尔 文学奖; 连云港 核废料;
创业者 越来越
'''
fdist = text.vocab()#{词1:词1出现的个数,词2:词2出现的个数,...}
print(fdist["大陆"])
print(fdist["老百姓"])
print(fdist["的"])
print(fdist["不"])
'''
25
27
1250
351
'''
print(len(tokens)) #总共的词数
print(len(fdist.keys())) #不重复词
print(fdist.freq('的'))#1250/27232
print(fdist.most_common(20))# 出现次数最大的前20个
'''
27232
6465
0.04590188014101058
[(',', 1651), ('的', 1250), ('。', 959), (':', 568), ('是', 495), ('了', 380), ('不', 351), ('”', 257), ('“', 255), ('#', 242), ('一', 222), ('就', 207), ('在', 200), ('有', 162), ('?', 150), ('你', 130), ('我', 129), ('这', 129), ('人', 127), ('都', 124)]
'''
nltk.download('stopwords')
'''
[nltk_data] Downloading package stopwords to /Users/Trucy/nltk_data...
[nltk_data]   Unzipping corpora/stopwords.zip.
然后在解压的停词目录中添加china文件,里面添加停词,这里用的哈工大停词库
'''
comm=list(zip(*fdist.most_common(100)))[0]#100个热词中非停词
print([w for w in comm if w not in nltk.corpus.stopwords.words('china')])
'''
['中国', '说', '国家', '毛', '微', '语录', '精选', '图', '说话', '中共', '日', '政府', '年', '爱国', '美国', '天', '钱', '荟萃', '段子', '里', '想']
'''
# 非url的长词语
print([w for w in fdist.keys() if len(w) > 12 and not w.startswith("http")])
'''
['@jianshenbiao', '@Jackstraw_PRC', '@Andrew19751110', '@boattractor_cj', '@_markhorton搞得后者不得不', '@Weaponmagazine-肖宁)', '@YE5MQ5Vtp2jlWX7', '@Menghuanlangqi2', '@szstupidcool', '@zhifangnvren', 'HappyBirthday', '@freedomandlaw', '@_mackhorton结果攻击了一个字母之差的无辜鬼佬', '@和菜头)——中国人能接受么?丝毫没有对他国的尊重', '@BattlerHenry', '@c338ki_selina', '@shangguanluan', '@zhanghui8964', '@fedsneighbor']
'''

相信你肯定听过二八原则,如果把所有的单词(字)放在一起看呢?会不会20%的词(字)占了80%的出现次数?答案是肯定的。

早在上个世纪30年代,就有人(Zipf)对此作出了研究,并给出了量化的表达——齐普夫定律(Zipf’s Law):一个词在一个有相当长度的语篇中的等级序号(该词在按出现次数排列的词表中的位置,他称之为rank,简称r)与该词的出现次数(他称为frequency,简称f)的乘积几乎是一个常数(constant,简称C)。用公式表示,就是 r × f = C 。(此处的C一般认为取0.1)。Zipf定律是文献计量学的重要定律之一,它和洛特卡定律、布拉德福定律一起被并称为文献计量学的三大定律。

我们直观地验证下,100个热词频度和排名的规律:

1
2
3
4
5
6
7
8
9
10
11
#前100热词统计
comm=list(zip(*fdist.most_common(400)))
top=[(w,comm[1][i]) for i,w in enumerate(comm[0]) \
if w not in nltk.corpus.stopwords.words('china')]
dd=list(zip(*top[:100]))
plt.plot(range(1,101),dd[1],'-',linewidth=5,alpha=0.6)
plt.xticks(arange(1,101,2),dd[0][::2],rotation=90)
plt.xlabel('前100热词',fontsize='x-large')
plt.ylabel('频数',fontsize='x-large')
plt.title('前100热词统计',fontsize='x-large')
plt.show()

很明显如果去拟合的话是反比例曲线,有兴趣的使用最小二乘法拟合下,看下c等于多少。同样可以看到,热词中涉及政治词汇比较多,实际上好多中文说说都涉及敏感词汇,这已经是我挑的不怎么敏感的了,万恶腐朽的google+,天朝早晚取缔你😁。

特征词提取

这节主要介绍TFIDF,高手直接跳过。

特征词/关键词提取最简单最基础的就是TFIDF,记得5年前我同学让我帮做DI-TFIDF的论文,也就只多了个类内离散度(DI),今年阿里校招笔试题都有,用mapreduce实现的,很是广泛。实际上tfidf算法的岁数估计比我还大,目前应用还是很广。其他特征提取方法还有TF、MI、ECE、QEMI、IG、OR、GA、SA、PCA、N-Gram等,各有好坏,个人喜欢SA(模拟退火算法)。

简单介绍下TFIDF,TFIDF的主要思想是:如果某个词或短语在一篇文章中出现的频率TF高,并且在其他文章中很少出现,则认为此词或者短语具有很好的类别区分能力,适合用来分类。TFIDF实际上是:TF * IDF,TF词频(Term Frequency),IDF反文档频率(Inverse Document Frequency)。TF词频(Term Frequency)指的是某一个给定的词语在该文件中出现的次数)。IDF反文档频率(Inverse Document Frequency)是指果包含词条的文档越少,IDF越大,则说明词条具有很好的类别区分能力。公式看看阮一峰的文章5就懂。

先用波森来玩一下。

1
2
#第100到109条记录各前五个关键字分析
nlp.extract_keywords(all_content[100:110],top_k=5)

[[[0.7099039005468386, ‘林志颖’], [0.6618791162771835, ‘说话’], [0.16548951440531323, ‘图’], [0.1391678971188951, ‘真’], [0.08401698377794771, ‘看’]],
[[0.4023478548923522, ‘面试’], [0.37301990285926334, ‘秋月’], [0.3067035017321436, ‘程序员’], [0.3039517495852464, ‘月饼’], [0.30299584457854634, ‘语录’]],
[[0.47362866866222336, ‘朝鲜’], [0.3352703984048265, ‘兼听则明’], [0.2612360542645001, ‘志愿军’], [0.25878030672041646, ‘战争’], [0.21926520954291415, ‘脸红’]],
[[0.49721835098126743, ‘战争’], [0.32820426275864795, ‘共产党’], [0.3033420749749608, ‘朝鲜’], [0.22718247058147462, ‘亲华派’], [0.21472859448533965, ‘保家’]],
[[0.2454776400796074, ‘老头’], [0.2404142762073897, ‘导游’], [0.18403709160053647, ‘平壤’], [0.1753006540352852, ‘首都’], [0.1462342959779542, ‘坦克’]],
[[0.611209874316231, ‘语录’], [0.47971562974594106, ‘精选’], [0.3914587127344426, ‘女生’], [0.32629224154255926, ‘复杂’], [0.24636184499215347, ‘到底’]],
[[0.49698250604754796, ‘响吧’], [0.41302611057797883, ‘老百姓’], [0.34093105823385067, ‘太监’], [0.3321228504052302, ‘大陆’], [0.2893273489670021, ‘税负’]],
[[0.40325177993397743, ‘导游’], [0.3096728358372618, ‘韩战’], [0.2929205806460173, ‘游客’], [0.2480119777270853, ‘带劲’], [0.22704285072733602, ‘自以为是’]],
[[0.7242722832662252, ‘哥们儿’], [0.5061338668467491, ‘着急’], [0.4404639544540359, ‘说话’], [0.11012912198474084, ‘图’], [0.07352170001676955, ‘太’]],
[[0.39101364028081176, ‘鸦片’], [0.27683827573410325, ‘种植’], [0.22542041392575338, ‘zhuo’], [0.22542041392575338, ‘nanjun’], [0.22461629498500293, ‘缩头’]]]

用tfidf来做一下,分四步走:

  1. 分词
  2. 去停词
  3. 计算tfidf
  4. 排序,topK
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
cts=[]
#1.十条记录的分词
for c in nlp.tag(all_content[100:110]):
    cts.append(c['word'])
tfidfC=[]
#2.去停词
for ac in cts: 
    tfidfC.append([w for w in ac 
                   if w not in nltk.corpus.stopwords.words('china')])
tc = nltk.TextCollection(tfidfC)
tfidfR=[]
topK=5
#3.计算tfidf
for c in tfidfC:
    res=[]
    for sc in list(set(c)):
        val=tc.tf_idf(sc,c)
        res.append([val,sc])
    #4.topK
    sres=sorted(res, key=lambda p: p[0], reverse=True)[:topK]
    tfidfR.append(sres)
print(tfidfR)

结果:

[[[0.4605170185988092, ‘林志颖’], [0.3218875824868201, ‘说话’], [0.3218875824868201, ‘真’], [0.3218875824868201, ‘0913’], [0.3218875824868201, ‘图’]],
[[0.24237737820989955, ‘面试’], [0.24237737820989955, ‘官’], [0.12118868910494977, ‘从前’], [0.12118868910494977, ‘秋月’], [0.12118868910494977, ‘原因’]],
[[0.1485538769673578, ‘中’], [0.11651349719283252, ‘朝鲜’], [0.07767566479522169, ‘战争’], [0.0742769384836789, ‘贡献’], [0.0742769384836789, ‘话’]],
[[0.15703993099903515, ‘战争’], [0.10496334211526741, ‘共产党’], [0.1001123953475672, ‘场’], [0.07851996549951758, ‘朝鲜’], [0.0500561976737836, ‘课本’]],
[[0.03868841135658895, ‘说’], [0.033210361918183356, ‘健康’], [0.033210361918183356, ‘老头’], [0.033210361918183356, ‘首都’], [0.03095072908527116, ‘抢’]],
[[0.3837641821656743, ‘关系’], [0.3837641821656743, ‘0912’], [0.3837641821656743, ‘女生’], [0.26823965207235, ‘微’], [0.26823965207235, ‘精选’]], [[0.14631253749400913, ‘大陆’], [0.14631253749400913, ‘老百姓’], [0.10466295877245664, ‘总得’], [0.10466295877245664, ‘倒数’], [0.10466295877245664, ‘毛’]],
[[0.10233711524417982, ‘想’], [0.07153057388596001, ‘导游’], [0.07153057388596001, ‘说’], [0.07153057388596001, ‘游客’], [0.07153057388596001, ‘年’]],
[[0.3837641821656743, ‘着急’], [0.3837641821656743, ‘哥们儿’], [0.3837641821656743, ‘0911’], [0.26823965207235, ‘说话’], [0.26823965207235, ‘图’]],
[[0.1151292546497023, ‘鸦片’], [0.1151292546497023, ‘种植’], [0.05756462732485115, ‘zhuo’], [0.05756462732485115, ‘补习’], [0.05756462732485115, ‘充当’]]]

两者比较下波森要稍好一些,底层估计是改进的tfidf。

文本相似度

我们看今日头条新闻,看完后下面会有推荐相似的文章,提供你更多阅读的机会。相似文本推荐最基本的方法用到”余弦相似性“(cosine similiarity)。举个简单例子: 两个文本,先分词:

文本1:google/和/百度/相似
文本2:百度/比不了/谷歌

计算词频

文本1:google:1,和:1,百度:1,相似:1
文本2:百度:1,比不了:1,谷歌:1

列出所有词,按词频排序:

文本1:google:1,和:1,百度:1,相似:1,比不了:0
文本2:google:1,和:0,百度:1,相似:0,比不了:1

提取词频向量:

文本1:[1,1,1,1,0] 文本2:[1,0,1,0,1]

接下来计算余弦相似度:

\[CosineDistance(u, v)=\frac{u\cdot v}{|u||v|}\]

这个公式代表两个向量的夹角,二维向量夹角高中不就是这个公式么,推广到三维甚至多维也一样。

余弦值越接近1,就表明夹角越接近0度,也就是两个向量越相似,这就叫”余弦相似性”。所以上面两文本相似度为:

\[\frac{2}{\sqrt{1^2+1^2+1^2+1^2+0^2}\times \sqrt{1^2+0^2+1^2+0^2+1^2}}=\frac{\sqrt{3}}{3}\]

下面我们来统计下内容较长的记录相似文本(内容较短相似度不明显)。 第一步:找出内容长度大于700的记录,并做好分词和停词

1
2
3
4
5
6
7
8
9
#筛选要比较的文本数据,共24条记录
data=[]#保存object.content内容大于700的所有数据
all_posts=[]#只保留发布的内容数据
for ac in activity_results:
    if len(ac['object']['content'])>700:
        c=ac['object']['content']
        data.append(ac)
        #分词和停词
        all_posts.append([w for w in nlp.tag(c)[0]['word'] if w not in nltk.corpus.stopwords.words('china')])

第二步:构造词条文档矩阵。形式如$td_matrix={(title_i,url_i):{term_j:tfidf_j,…},…}$

1
2
3
4
5
6
7
8
9
10
11
12
13
tc = nltk.TextCollection(all_posts)
#构造词条文档矩阵
td_matrix = {}
for idx in range(len(all_posts)):
    post = all_posts[idx]
    fdist = nltk.FreqDist(post)

    doc_title = data[idx]['title']
    url = data[idx]['url']
    td_matrix[(doc_title, url)] = {}

    for term in fdist.keys():
        td_matrix[(doc_title, url)][term] = tc.tf_idf(term, post)

第三步:计算相似度
这里用到nltk.cluster.util.cosine_distance,这个函数说明如下: nltk.cluster.util.cosine_distance(u, v)[source] Returns 1 minus the cosine of the angle between vectors v and u. This is equal to 1 - (u.v / |u||v|). 可以看到返回的是1-余弦相似度,表示的是余弦距离,也就是说余弦距离越小相似度越高。

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
distances = {}
for (title1, url1) in td_matrix.keys():

    distances[(title1, url1)] = {}
    (min_dist, most_similar) = (1.0, ('', ''))

    for (title2, url2) in td_matrix.keys():

        terms1 = td_matrix[(title1, url1)].copy()
        terms2 = td_matrix[(title2, url2)].copy()

        # 填充0使两向量长度相同
        for term1 in terms1:
            if term1 not in terms2:
                terms2[term1] = 0

        for term2 in terms2:
            if term2 not in terms1:
                terms1[term2] = 0

        # 产生v1,v2向量,向量元素映射一致
        v1 = [score for (term, score) in sorted(terms1.items())]
        v2 = [score for (term, score) in sorted(terms2.items())]

        # 计算余弦相似度
        distances[(title1, url1)][(title2, url2)] = \
            nltk.cluster.util.cosine_distance(v1, v2)

        if url1 == url2:
            continue
        # 标记余弦距离最小的(相似度最大)
        if distances[(title1, url1)][(title2, url2)] < min_dist:
            (min_dist, most_similar) = (distances[(title1, url1)][(title2,
                                         url2)], (title2, url2))
    
    print ("%s\n(%s) \n 最相似的是:\n %s\n(%s) \n 相似度: %f \n ------------------------------\n"
           % (title1, url1,most_similar[0], most_similar[1], 1-min_dist))

查看部分结果:

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
#一日段子荟萃
2016-9-5

@kxxj: 新歇后语:包子宽衣——露馅儿了。

@CasperYang:通商宽衣已加入敏感词豪华午餐。

@szeyan1220:宽衣解带新白娘。轻关易道寻佳人,..
(https://plus.google.com/+MulinHong/posts/FJuFWhykUhU) 
 最相似的是:
 #一日段子荟萃
2016-9-7

@boattractor_cj:“吃饱了没事干”,代表了习帝的外交思路,反映了他对洋夷的了解水平;“吃饭砸锅”代表了习帝的内政思路,反映了他蔑视屁民的霸气;“打断骨头..
(https://plus.google.com/+MulinHong/posts/BfAsHEk3Dfs) 
 相似度: 0.018108 
 ------------------------------

#一日段子荟萃
2016-8-9

@blogtd:我奇怪的是,不管作为一个国家,还是一个族群,中国从未向世界展现过羞愧这种情绪。

@hnjhj:各类辱华事件频发,但因星星角度不对而辱华还是始料未及..
(https://plus.google.com/+MulinHong/posts/Y4DMUPtSSTB) 
 最相似的是:
 #一日段子荟萃
2016-9-26

@zhanghui8964:就目前形态而言,我一直不认为渐进改良是一种政治存在,也不认为激进革命是一种政治存在。在一定意义上大家都是口炮党。区别只在于部分努力者搞了..
(https://plus.google.com/+MulinHong/posts/BNqkLxd9YCP) 
 相似度: 0.026590 
 ------------------------------

#一日段子荟萃
2016-8-29

@侯虹斌:警力配置吧。主要精力放在维稳上,这类诈骗案几乎不会投入警力资源;到全社会都关注了,就变成维稳的事件了,警方马上就有资源对接了。

@baidu冷兵器吧:..
(https://plus.google.com/+MulinHong/posts/RTDgVPKvbyM) 
 最相似的是:
 #一日段子荟萃
2016-8-23

@MyDF:昨天下午路过洪都南大道,发现沿途增加了不少交警,诡异的是,大热天的街边还站着或坐着不少可疑的群众,几公里都是,象是在等什么大人物。今天看了新闻,才知ß道..
(https://plus.google.com/+MulinHong/posts/UVZVyshut8R) 
 相似度: 0.082137 
 ------------------------------

为查看方便,绘制相似度矩阵图:

REF

  • Russell, Matthew A. Mining the Social Web: Data Mining Facebook, Twitter, LinkedIn, Google+, GitHub, and More. O’Reilly Media, Inc. 2013.
  1. python3.4学习笔记(十七) 网络爬虫使用Beautifulsoup4抓取内容.http://www.cnblogs.com/zdz8207/p/python_learn_note_17.html 

  2. Beautiful Soup 4.2.0 文档.https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html 

  3. 11 款开放中文分词引擎评测.http://www.cnblogs.com/croso/p/5349517.html 

  4. Natural Language Toolkit.http://www.nltk.org/index.html 

  5. TF-IDF与余弦相似性的应用(一):自动提取关键词.http://www.ruanyifeng.com/blog/2013/03/tf-idf.html