新闻信息抓取及数据分析

一.数据抓取

本次爬虫目标网站是中国社会组织公共服务平台。

第一步 分析网站

通过浏览器“审查元素”查看源代码并获取新闻的标题、URL、时间等。不同网站有不同分析方法,本文重点是文本挖掘分析。

第二步,进入具体的新闻页面抓取相关的文本信息。

  • article_title = text_html.xpath(’//*[@id=“fontinfo”]/p[2]/b[1]//text()’)
  • publish_time = text_html.xpath(’/html/body/div[2]/div/ul[1]/li[3]/strong/text()’)[0][5:]
  • source_text = text_html.xpath(’//*[@id=“fontinfo”]/p[last()]//text()’)[0]
  • text_list = text_html.xpath(’//*[@id=“fontinfo”]//text()’)

第三步,本爬虫存在一个技巧,每条新闻的URL非常相似,这里仅变换参数来抓取新闻。

我们只需要每次抓取数据时,通过“下一页”定位下次需要抓取的URL即可,核心代码为:

第四步,数据抓取完整代码如下所示。

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
import requests,re, csv, sys, time
from lxml import html
from fake_useragent import UserAgent

# 记录起始时间
startTime = time.time()

# 创建CSV文件,并写入表头信息
fp = open('中国社会组织_疫情防控.csv','a',newline='',encoding='utf-8-sig')
writer = csv.writer(fp)
writer.writerow(("标题", "时间", "URL", "正文内容", "来源"))

#----------------------------------------------抓取数据----------------------------------------------
def spider_html_info(url):
try:
headers = {
"User-Agent" : UserAgent().chrome #chrome浏览器随机代理
}
response = requests.get(url=url, headers=headers).text
text_html = html.fromstring(response)

# 获取下一页链接,先其他元素获取一页链接,保证程序的强壮性
next_url = "http://www.chinanpo.gov.cn" + text_html.xpath('/html/body/div[2]/div/ul[1]/li[2]/a[2]/@href')[0]
print("next_url", next_url)

# 获取文章标题
article_title = text_html.xpath('//*[@id="fontinfo"]/p[2]/b[1]//text()')
title = "".join(article_title)
if title == "":
title = "".join(text_html.xpath('//*[@id="fontinfo"]/p[3]/b[1]//text()'))
print ("title = ",title)

# 获取发布时间
publish_time = text_html.xpath('/html/body/div[2]/div/ul[1]/li[3]/strong/text()')[0][5:]
print ("publish_time = ", publish_time)
print ("url = ", url)

# 获取来源
source_text = text_html.xpath('//*[@id="fontinfo"]/p[last()]//text()')[0]
source = source_text[3:]

# 爬取文本
text_list = text_html.xpath('//*[@id="fontinfo"]//text()')
article_text = "".join(text_list).replace('\r\n','').replace("\xa0", "").replace("\t", "").replace(source_text,"").replace(title, "")
# print ("article_text", article_text)
# print ("source = ", source)
writer.writerow((title, publish_time, url, article_text, source))
except:
pass

if url == 'http://www.chinanpo.gov.cn/1944/123496/index.html':
fp.close()
# 获取结束时的时间
endTime =time.time()
useTime =(endTime-startTime)/60
print ("该次所获的信息一共使用%s分钟"%useTime)
# 正常退出程序
sys.exit(0)
else:
return next_url

#----------------------------------------------主函数----------------------------------------------
def main():
url = "http://www.chinanpo.gov.cn/1944/125177/nextindex.html" # 125177第一篇文章
count = 1
while True:
print ("正在爬取第%s篇:"%count, url)
next_url = spider_html_info(url)
url = next_url
count = count + 1

if __name__ == '__main__':
main()

1.爬取运行截图

可能需要安装扩展包lxml和fake_useragent。

二.导入MongoDB

python中csv文件中数据添加到MongoDB数据库,使用csv中的DictReader函数读取。

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
from pymongo import MongoClient
import csv
# 创建连接MongoDB数据库函数
def connection():
# 1:连接本地MongoDB数据库服务
conn=MongoClient("mongodb://localhost:27017/")
# 2:连接本地数据库(guazidata)。没有时会自动创建
db=conn.cncovdf
# 3:创建集合
set1=db.data
# 4:看情况是否选择清空(两种清空方式,第一种不行的情况下,选择第二种)
#第一种直接remove
set1.remove(None)
#第二种remove不好用的时候
# set1.delete_many({})
return set1
def insertToMongoDB(set1):
# 打开文件中国社会组织_疫情防控.csv
with open('中国社会组织_疫情防控.csv','r',encoding='utf-8',errors='ignore')as csvfile:
# 调用csv中的DictReader函数直接获取数据为字典形式
reader=csv.DictReader(csvfile)
# 创建一个counts计数一下 看自己一共添加了了多少条数据
counts=0
for each in reader:
set1.insert(each)
counts+=1
print('成功添加了'+str(counts)+'条数据 ')
# 创建主函数
def main():
set1=connection()
insertToMongoDB(set1)
# 判断是不是调用的main函数。这样以后调用的时候就可以防止不会多次调用 或者函数调用错误
if __name__=='__main__':
main()

2.运行截图

三.中文分词及高频词统计

1.结巴分词

数据预处理是指在进行数据分析之前,对数据进行的一些初步处理,包括缺失值填写、噪声处理、不一致数据修正、中文分词等,其目标是得到更标准、高质量的数据,纠正错误异常数据,从而提升分析的结果。中文文本预处理的基本步骤,包括中文分词、词性标注、数据清洗、特征提取(向量空间模型存储)、权重计算(TF-IDF)等。

“结巴”(Jieba)工具是最常用的中文文本分词和处理的工具之一,它能实现中文分词、词性标注、关键词抽取、获取词语位置等功能。其在Github网站上的介绍及下载地址为:https://github.com/fxsjy/jieba

调用命令“pip install jieba”安装jieba中文分词包如下图所示。

1
pip install jieba

Jieba具有以下特点:

  • 支持三种分词模式,包括精确模式、全模式和搜索引擎模式
  • 支持繁体分词
  • 支持自定义词典
  • 代码对Python2和Python3均兼容
  • 支持多种编程语言,包括Java、C++、Rust、PHP、R、Node.js等

2.基本用法

首先读者看一段简单的结巴分词代码,主要调用两个函数实现。

  • jieba.cut(text, cut_all=True)
    分词函数,第一个参数是需要分词的字符串,第二个参数表示是否为全模式。分词返回的结果是一个可迭代的生成器(generator),可使用for循环来获取分词后的每个词语,更推荐读者转换为list列表再使用。
  • jieba.cut_for_search(text)
    搜索引擎模式分词,参数为分词的字符串,该方法适合用于搜索引擎构造倒排索引的分词,粒度比较细。
    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
    #encoding=utf-8  
    import jieba

    text = "小杨毕业于北京理工大学,从事Python人工智能相关工作。"

    #全模式
    data = jieba.cut(text,cut_all=True)
    print(type(data))
    print(u"[全模式]: ", "/".join(data))

    #精确模式
    data = jieba.cut(text,cut_all=False)
    print(u"[精确模式]: ", "/".join(data))

    #默认是精确模式
    data = jieba.cut(text)
    print(u"[默认模式]: ", "/".join(data))

    #搜索引擎模式
    data = jieba.cut_for_search(text)
    print(u"[搜索引擎模式]: ", "/".join(data))

    #返回列表
    seg_list = jieba.lcut(text, cut_all=False)
    print("[返回列表]: {0}".format(seg_list))
    输出结果如下图所示:

3.获取疫情文本高频词

接着我们将新闻正文文本“C-class.txt”数据进行中文分词,每行代表一条新闻,并生成对应的内容。

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
# coding=utf-8
import jieba
import re
import time
from collections import Counter

#------------------------------------中文分词------------------------------------
cut_words = ""
all_words = ""
f = open('C-class-fenci.txt', 'w' , encoding='utf-8')
for line in open('C-class.txt', encoding='utf-8'):
line.strip('\n')
seg_list = jieba.cut(line,cut_all=False)
# print(" ".join(seg_list))
cut_words = (" ".join(seg_list))
f.write(cut_words)
all_words += cut_words
else:
f.close()

# 输出结果
all_words = all_words.split()
print(all_words)

# 词频统计
c = Counter()
for x in all_words:
if len(x)>1 and x != '\r\n':
c[x] += 1

# 输出词频最高的前10个词
print('\n词频统计结果:')
for (k,v) in c.most_common(10):
print("%s:%d"%(k,v))

# 存储数据
name = time.strftime("%Y-%m-%d") + "-fc.csv"
fw = open(name, 'w', encoding='utf-8')
i = 1
for (k,v) in c.most_common(len(c)):
fw.write(str(i)+','+str(k)+','+str(v)+'\n')
i = i + 1
else:
print("Over write file!")
fw.close()

输出结果如下图所示,采用空格连接的分词结果。

同时生成高频特征词,并保存至CSV文件中。

对应的特征词及词频排序如表“2020-12-29-fc.csv”所示,如果我们撰写图情论文,可以尝试建立Top50的特征词表。

四.WordCloud可视化分析

1.基本用法

词云分析主要包括两种方法:

  • 调用WordCloud扩展包画图(兼容性极强,之前介绍过)
  • 调用PyEcharts中的WordCloud子包画图(本文推荐新方法)

这里使用PyEcharts可视化,需要通过它来绘制词云,基础代码如下:

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
# coding=utf-8
from pyecharts import options as opts
from pyecharts.charts import WordCloud
from pyecharts.globals import SymbolType

# 数据
words = [
('背包问题', 10000),
('大整数', 6181),
('Karatsuba乘法算法', 4386),
('穷举搜索', 4055),
('傅里叶变换', 2467),
('状态树遍历', 2244),
('剪枝', 1868),
('Gale-shapley', 1484),
('最大匹配与匈牙利算法', 1112),
('线索模型', 865),
('关键路径算法', 847),
('最小二乘法曲线拟合', 582),
('二分逼近法', 555),
('牛顿迭代法', 550),
('Bresenham算法', 462),
('粒子群优化', 366),
('Dijkstra', 360),
('A*算法', 282),
('负极大极搜索算法', 273),
('估值函数', 265)
]

# 渲染图
def wordcloud_base() -> WordCloud:
c = (
WordCloud()
.add("", words, word_size_range=[20, 100], shape='diamond') # SymbolType.ROUND_RECT
.set_global_opts(title_opts=opts.TitleOpts(title='WordCloud词云'))
)
return c

# 生成图
wordcloud_base().render('词云图.html')

输出结果如下图所示,出现词频越高显示越大。

核心代码为:

add(name, attr, value, shape=“circle”, word_gap=20, word_size_range=None, rotate_step=45)

  • name -> str: 图例名称
  • attr -> list: 属性名称
  • value -> list: 属性所对应的值
  • shape -> list: 词云图轮廓,有’circle’, ‘cardioid’, ‘diamond’, ‘triangleforward’, ‘triangle’, ‘pentagon’, ‘star’可选
  • word_gap -> int: 单词间隔,默认为20
  • word_size_range -> list: 单词字体大小范围,默认为[12,60]
  • rotate_step -> int: 旋转单词角度,默认为45

2.疫情词云

接着我们显示经过中文分词的疫情新闻文本信息,前1000个高频词的词云绘制代码如下:

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
# coding=utf-8
# coding=utf-8
import jieba
import re
import time
from collections import Counter

#------------------------------------中文分词------------------------------------
cut_words = ""
all_words = ""
f = open('C-class-fenci.txt', 'w' , encoding='utf-8')
for line in open('C-class.txt', encoding='utf-8'):
line.strip('\n')
seg_list = jieba.cut(line,cut_all=False)
# print(" ".join(seg_list))
cut_words = (" ".join(seg_list))
f.write(cut_words)
all_words += cut_words
else:
f.close()

# 输出结果
all_words = all_words.split()
print(all_words)

# 词频统计
c = Counter()
for x in all_words:
if len(x)>1 and x != '\r\n':
c[x] += 1

# 输出词频最高的前10个词
print('\n词频统计结果:')
for (k,v) in c.most_common(10):
print("%s:%d"%(k,v))

# 存储数据
name = time.strftime("%Y-%m-%d") + "-fc.csv"
fw = open(name, 'w', encoding='utf-8')
i = 1
for (k,v) in c.most_common(len(c)):
fw.write(str(i)+','+str(k)+','+str(v)+'\n')
i = i + 1
else:
print("Over write file!")
fw.close()


#------------------------------------词云分析------------------------------------
from pyecharts import options as opts
from pyecharts.charts import WordCloud
from pyecharts.globals import SymbolType

# 生成数据 word = [('A',10), ('B',9), ('C',8)] 列表+Tuple
words = []
for (k,v) in c.most_common(1000):
# print(k, v)
words.append((k,v))

# 渲染图
def wordcloud_base() -> WordCloud:
c = (
WordCloud()
.add("", words, word_size_range=[20, 100], shape=SymbolType.ROUND_RECT)
.set_global_opts(title_opts=opts.TitleOpts(title='全国新型冠状病毒疫情词云图'))
)
return c

# 生成图
wordcloud_base().render('疫情词云图.html')

运行结果如下图所示:

输出结果如下图所示:

五.TF-IDF计算及KMeans文本聚类

1.TF-IDF计算

TF-IDF(Term Frequency-InversDocument Frequency)是一种常用于信息处理和数据挖掘的加权技术。该技术采用一种统计方法,根据字词的在文本中出现的次数和在整个语料中出现的文档频率来计算一个字词在整个语料中的重要程度。它的优点是能过滤掉一些常见的却无关紧要本的词语,同时保留影响整个文本的重要字词。计算方法如下面公式所示:

$$
T F − I D F = T F ∗ I D F TF-IDF = TF* IDF
TF−IDF=TF∗IDF
$$

TF(Term Frequency)表示某个关键词在整篇文章中出现的频率。IDF(InversDocument Frequency)表示计算倒文本频率。文本频率是指某个关键词在整个语料所有文章中出现的次数。倒文档频率又称为逆文档频率,它是文档频率的倒数,主要用于降低所有文档中一些常见却对文档影响不大的词语的作用。

TF-IDF统计可视化完整代码如下:

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
# coding=utf-8 
import os
import time
import pandas as pd
import numpy as np
import jieba
import jieba.analyse
import matplotlib.pyplot as plt
from PIL import Image
from datetime import datetime
from matplotlib.font_manager import FontProperties

#------------------------------------中文分词------------------------------------
cut_words = ""
for line in open('C-class.txt', encoding='utf-8'):
line.strip('\n')
seg_list = jieba.cut(line,cut_all=False)
# print(" ".join(seg_list))
cut_words += (" ".join(seg_list))

# jieba.load_userdict("userdict.txt") # 自定义词典
# jieba.analyse.set_stop_words('stop_words.txt') # 停用词词典

# 提取主题词 返回的词频其实就是TF-IDF
keywords = jieba.analyse.extract_tags(cut_words,
topK=50,
withWeight=True,
allowPOS=('a','e','n','nr','ns', 'v')) #词性 形容词 叹词 名词 动词

# 以列表形式返回
print(keywords)

# 数据存储
pd.DataFrame(keywords, columns=['词语','重要性']).to_excel('TF_IDF关键词前50.xlsx')

# keyword本身包含两列数据
ss = pd.DataFrame(keywords,columns = ['词语','重要性'])
# print(ss)

#------------------------------------数据可视化------------------------------------
plt.figure(figsize=(10,6))
plt.title('TF-IDF Ranking')
fig = plt.axes()
plt.barh(range(len(ss.重要性[:25][::-1])),ss.重要性[:25][::-1])
fig.set_yticks(np.arange(len(ss.重要性[:25][::-1])))
font = FontProperties(fname=r'c:\windows\fonts\simsun.ttc')
fig.set_yticklabels(ss.词语[:25][::-1],fontproperties=font)
fig.set_xlabel('Importance')
plt.show()

输出结果如下图所示,可以看到“疫情”、“组织”、“企业”、“复工”、“社会”、“新冠”、“慈善”等都是高频词,也是大众普遍关心的主题。

注意:可能需要安装openpyxl扩展包,to_excel()函数要用到。

2.文本聚类

同样,在Scikit-Learn包中也能计算TF-IDF权重值,此时需要用到两个类:CountVectorizer和TfidfTransformer。

(1) CountVectorizer

CountVectorizer类会将文本中的词语转换为词频矩阵,例如矩阵中包含一个元素a[i][j],它表示j词在i类文本下的词频。它通过fit_transform函数计算各个词语出现的次数,通过get_feature_names()可获取词袋中所有文本的关键字,通过toarray()可看到词频矩阵的结果。

(2) TfidfTransformer

TfidfTransformer用于统计vectorizer中每个词语的TF-IDF值。具体用法如下:

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
# coding:utf-8
from sklearn.feature_extraction.text import CountVectorizer

#语料
corpus = [
'This is the first document.',
'This is the second second document.',
'And the third one.',
'Is this the first document?',
]
#将文本中的词语转换为词频矩阵
vectorizer = CountVectorizer()
#计算个词语出现的次数
X = vectorizer.fit_transform(corpus)
#获取词袋中所有文本关键词
word = vectorizer.get_feature_names()
print word
#查看词频结果
print X.toarray()

from sklearn.feature_extraction.text import TfidfTransformer

#类调用
transformer = TfidfTransformer()
print transformer
#将词频矩阵X统计成TF-IDF值
tfidf = transformer.fit_transform(X)
#查看数据结构 tfidf[i][j]表示i类文本中的tf-idf权重
print tfidf.toarray()

输出结果入下所示,从结果中可以看到,总共包括9个特征词,即:
[u’and’, u’document’, u’first’, u’is’, u’one’, u’second’, u’the’, u’third’, u’this’]

同时在输出每个句子中包含特征词的个数。例如,第一句“This is the first document.”,它对应的词频为[0, 1, 1, 1, 0, 0, 1, 0, 1],假设初始序号从1开始计数,则该词频表示存在第2个位置的单词“document”共1次、第3个位置的单词“first”共1次、第4个位置的单词“is”共1次、第9个位置的单词“this”共1词。所以,每个句子都会得到一个词频向量,TF-IDF对应向量类似。

(3) 文本聚类

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
# coding=utf-8  
import time
import re
import os
import sys
import codecs
import shutil
import numpy as np
import matplotlib
import scipy
import matplotlib.pyplot as plt
from sklearn import feature_extraction
from sklearn.feature_extraction.text import TfidfTransformer
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import HashingVectorizer

if __name__ == "__main__":

#########################################################################
# 第一步 计算TFIDF

# 文档预料 空格连接
corpus = []

# 读取预料 一行预料为一个文档
for line in open('C-class-fenci.txt', 'r', encoding='utf-8').readlines():
corpus.append(line.strip())

# 将文本中的词语转换为词频矩阵 矩阵元素a[i][j] 表示j词在i类文本下的词频
vectorizer = CountVectorizer()

# 该类会统计每个词语的tf-idf权值
transformer = TfidfTransformer()

# 第一个fit_transform是计算tf-idf 第二个fit_transform是将文本转为词频矩阵
tfidf = transformer.fit_transform(vectorizer.fit_transform(corpus))

# 获取词袋模型中的所有词语
word = vectorizer.get_feature_names()

# 将tf-idf矩阵抽取出来 元素w[i][j]表示j词在i类文本中的tf-idf权重
weight = tfidf.toarray()

# 打印特征向量文本内容
print('Features length: ' + str(len(word)))

"""
# 输出单词
for j in range(len(word)):
print(word[j] + ' ')

# 打印每类文本的tf-idf词语权重 第一个for遍历所有文本 第二个for便利某一类文本下的词语权重
for i in range(len(weight)):
print u"-------这里输出第", i, u"类文本的词语tf-idf权重------"
for j in range(len(word)):
print weight[i][j],
"""

########################################################################
# 第二步 聚类Kmeans

print('Start Kmeans:')
from sklearn.cluster import KMeans
clf = KMeans(n_clusters=2)
print(clf)
pre = clf.fit_predict(weight)
print(pre)

#中心点
print(clf.cluster_centers_)
print(clf.inertia_)

########################################################################
# 第三步 图形输出 降维

from sklearn.decomposition import PCA
pca = PCA(n_components=2) #输出两维
newData = pca.fit_transform(weight) #载入N维
print(newData)

x = [n[0] for n in newData]
y = [n[1] for n in newData]

plt.scatter(x, y, c=pre, s=100)
plt.legend()
plt.title("Cluster with Text Mining")
plt.show()

输出结果如下图所示。需要注意,简单的聚类我们无法进行深入的分析,你可以理解为积极主题的一类(黄色)、消极主题的一类(黑色),也可以有其他理解,需要结合具体数据集进行分析,但其解释性始终不是很好。而真实的数据分析中会引入类标或标注,所以接着我们引入主题关键词聚类和LDA主题模型的分析,更能帮助大家理解文本挖掘和主题分析。

六.主题词层次聚类分析

1.层次聚类

层次聚类算法又称为树聚类算法,它根据数据之间的距离,透过一种层次架构方式,反复将数据进行聚合,创建一个层次以分解给定的数据集。主题词层次聚类主要调用scipy.cluster.hierarchy实现,推荐文章:层次聚类。

  • scipy.cluster.hierarchy.linkage(y, method=‘single’, metric=‘euclidean’, optimal_ordering=False)

层次聚类编码为一个linkage矩阵。假设代码如下,Z共有四列组成,第一字段与第二字段分别为聚类簇的编号,在初始距离前每个初始值被从0~n-1进行标识,每生成一个新的聚类簇就在此基础上增加一对新的聚类簇进行标识,第三个字段表示前两个聚类簇之间的距离,第四个字段表示新生成聚类簇所包含的元素的个数。

1
2
3
4
5
6
7
8
9
from scipy.cluster.hierarchy import dendrogram, linkage,fcluster
from matplotlib import pyplot as plt
X = [[i] for i in [2, 8, 0, 4, 1, 9, 9, 0]]
print(X)
Z = linkage(X, 'ward')
f = fcluster(Z, 4, 'distance')
fig = plt.figure(figsize=(5, 3))
dn = dendrogram(Z)
plt.show()

下面是聚类结果的可视化聚类树:

下面是返回值的解析:

2.疫情分析

由于层次聚类绘制的树状图主题词太多,所以这里采用中文分词提取每条新闻(对应一行数据)的Top100特征词,再存储至TXT中进行层次聚类分析。完整代码如下:

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
95
96
97
98
99
100
101
102
103
104
105
# -*- coding: utf-8 -*-
import os
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import jieba
import matplotlib.pyplot as plt
from pylab import mpl
from collections import Counter
from sklearn.metrics.pairwise import cosine_similarity
from scipy.cluster.hierarchy import ward, dendrogram
from sklearn.feature_extraction.text import CountVectorizer, TfidfTransformer

mpl.rcParams['font.sans-serif'] = ['SimHei']

#------------------------------ 第一步 计算TOP100 ------------------------------
# 计算中文分词词频TOP100
cut_words = ""
all_words = ""
for line in open('C-class.txt', encoding='utf-8'):
line.strip('\n')
seg_list = jieba.cut(line,cut_all=False)
# print(" ".join(seg_list))
cut_words = (" ".join(seg_list))
all_words += cut_words

# 输出结果
all_words = all_words.split()
print(all_words)

# 词频统计
c = Counter()
for x in all_words:
if len(x)>1 and x != '\r\n':
c[x] += 1

# 输出词频最高的前10个词
top_word = []
print('\n词频统计结果:')
for (k,v) in c.most_common(100):
print("%s:%d"%(k,v))
top_word.append(k)
print(top_word)
# ['疫情', '防控', '组织', '工作'...]

#------------------------------ 第二步 中文分词过滤 ------------------------------
# 过滤
cut_words = ""
f = open('C-key.txt', 'w')
for line in open('C-class.txt', encoding='utf-8'):
line.strip('\n')
seg_list = jieba.cut(line,cut_all=False)
final = ""
for seg in seg_list:
if seg in top_word:
final += seg + "|"
cut_words += final
f.write(final+"\n")
print(cut_words)
f.close

#------------------------------ 第三步 相相关计算 ------------------------------
text = open('C-key.txt').read()
list1 = text.split("\n")
print(list1)

# 数据第一行、第二行数据
# print(list1[0])
# print(list1[1])
mytext_list = list1

# min_df用于删除不经常出现的术语
# max_df用于删除过于频繁出现的术语,也称为语料库特定的停用词
# count_vec = CountVectorizer(min_df=3, max_df=3)
count_vec = CountVectorizer(min_df=3)
xx1 = count_vec.fit_transform(list1).toarray()
word = count_vec.get_feature_names()
print("word feature length: {}".format(len(word)))
print(word)
print(xx1.shape)
print(xx1[0])
titles = word

#------------------------------ 第四步 相似度计算 ------------------------------
df = pd.DataFrame(xx1)
print(df.corr())
print(df.corr('spearman'))
print(df.corr('kendall'))

dist = df.corr()
print(dist)
print(type(dist))
print(dist.shape)

#------------------------------ 第五步 可视化分析 ------------------------------
# define the linkage_matrix using ward clustering pre-computed distances
linkage_matrix = ward(dist)
fig, ax = plt.subplots(figsize=(15, 20)) # set size
ax = dendrogram(linkage_matrix, orientation="right", labels=titles);

# how plot with tight layout
plt.tight_layout()

# save figure as ward_clusters
plt.savefig('Tree_word.png', dpi=200)

最终生成图像如下所示:

运行结果如下图所示:

注意:该方法更推荐大家在进行论文关键词共现分析、主题词聚类分析等领域。

七.LDA主题分布分析

1.LDA主题模型

文档主题生成模型(Latent Dirichlet Allocation,简称LDA)通常由包含词、主题和文档三层结构组成。LDA模型属于无监督学习技术,它是将一篇文档的每个词都以一定概率分布在某个主题上,并从这个主题中选择某个词语。文档到主题的过程是服从多项分布的,主题到词的过程也是服从多项分布的。

文档主题生成模型(Latent Dirichlet Allocation,简称LDA)又称为盘子表示法(Plate Notation),下图是模型的标示图,其中双圆圈表示可测变量,单圆圈表示潜在变量,箭头表示两个变量之间的依赖关系,矩形框表示重复抽样,对应的重复次数在矩形框的右下角显示。LDA模型的具体实现步骤如下:

  • 从每篇网页D对应的多项分布θ中抽取每个单词对应的一个主题z。
  • 从主题z对应的多项分布φ中抽取一个单词w。
  • 重复步骤1和2,共计Nd次,直至遍历网页中每一个单词。

可以从gensim中下载ldamodel扩展包安装,也可以使用Sklearn机器学习包的LDA子扩展包,亦可从github中下载开源的LDA工具。下载地址详见列表所示。

来源 下载地址
gensim https://radimrehurek.com/gensim/models/ldamodel.html
scikit-learn 利用pip install sklearn命令安装扩展包,LatentDirichletAllocation函数即LDA原型
github https://github.com/ariddell/lda

本文和之前介绍的LDA算法略有不同,它主要采用sklearn中的LatentDirichletAllocation包实现主题分布研究,并调用pyLDAvis绘制相关图形。安装过程如下所示:

2.完整代码

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
#coding: utf-8
import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer

#--------------------- 第一步 读取数据(已分词) ----------------------
corpus = []

# 读取预料 一行预料为一个文档
for line in open('C-class-fenci.txt', 'r', encoding='utf-8').readlines():
corpus.append(line.strip())

#----------------------- 第二步 计算TF-IDF值 -----------------------
# 设置特征数
n_features = 2000

tf_vectorizer = TfidfVectorizer(strip_accents = 'unicode',
max_features=n_features,
stop_words=['的','或','等','是','有','之','与','可以','还是','比较','这里',
'一个','和','也','被','吗','于','中','最','但是','图片','大家',
'一下','几天','200','还有','一看','300','50','哈哈哈哈',
'“','”','。',',','?','、',';','怎么','本来','发现',
'and','in','of','the','我们','一直','真的','18','一次',
'了','有些','已经','不是','这么','一一','一天','这个','这种',
'一种','位于','之一','天空','没有','很多','有点','什么','五个',
'特别'],
max_df = 0.99,
min_df = 0.002) #去除文档内出现几率过大或过小的词汇

tf = tf_vectorizer.fit_transform(corpus)

print(tf.shape)
print(tf)

#------------------------- 第三步 LDA分析 ------------------------
from sklearn.decomposition import LatentDirichletAllocation

# 设置主题数
n_topics = 2

lda = LatentDirichletAllocation(n_components=n_topics,
max_iter=100,
learning_method='online',
learning_offset=50,
random_state=0)
lda.fit(tf)

# 显示主题数 model.topic_word_
print(lda.components_)
# 几个主题就是几行 多少个关键词就是几列
print(lda.components_.shape)

# 计算困惑度
print(u'困惑度:')
print(lda.perplexity(tf,sub_sampling = False))

# 主题-关键词分布
def print_top_words(model, tf_feature_names, n_top_words):
for topic_idx,topic in enumerate(model.components_): # lda.component相当于model.topic_word_
print('Topic #%d:' % topic_idx)
print(' '.join([tf_feature_names[i] for i in topic.argsort()[:-n_top_words-1:-1]]))
print("")

# 定义好函数之后 暂定每个主题输出前20个关键词
n_top_words = 20
tf_feature_names = tf_vectorizer.get_feature_names()
# 调用函数
print_top_words(lda, tf_feature_names, n_top_words)


#------------------------ 第四步 可视化分析 -------------------------
import pyLDAvis
import pyLDAvis.sklearn

#pyLDAvis.enable_notebook()

data = pyLDAvis.sklearn.prepare(lda,tf,tf_vectorizer)
print(data)

#显示图形
pyLDAvis.show(data)

#pyLDAvis.save_json(data,' fileobj.html')

困惑度及各个主题下的关键词通过for循环显示如下,Topic1是疫情相关的主题词,Topic0是其他相关的主题。

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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
D:\桌面\新建文件夹>python blog03_08_lda.py
(138, 2000)
(0, 403) 1.0
(1, 914) 0.03179002916835669
(1, 916) 0.0234264095629235
(1, 965) 0.03491201537733099
(1, 873) 0.02309606736903826
(1, 1475) 0.032712252841322786
(1, 959) 0.030194163252955503
(1, 158) 0.027145424577929348
(1, 1473) 0.024499551271543872
(1, 1144) 0.058987094186498806
(1, 436) 0.023769709611473348
(1, 273) 0.030194163252955503
(1, 1662) 0.027145424577929348
(1, 912) 0.02160893613426232
(1, 1612) 0.03491201537733099
(1, 1942) 0.03491201537733099
(1, 331) 0.027145424577929348
(1, 712) 0.02884487784700509
(1, 696) 0.03374321119491412
(1, 1375) 0.03491201537733099
(1, 431) 0.02247058496874336
(1, 1295) 0.03491201537733099
(1, 210) 0.017702572081147446
(1, 979) 0.027145424577929348
(1, 1323) 0.03491201537733099
: :
(137, 233) 0.032457418878176596
(137, 282) 0.02615992004194761
(137, 70) 0.052619347740271036
(137, 21) 0.04204615129757714
(137, 559) 0.05879281708622848
(137, 1065) 0.030850260311284926
(137, 1910) 0.02615992004194761
(137, 5) 0.0376578782866175
(137, 1098) 0.02154895843261173
(137, 1847) 0.022401353249301426
(137, 575) 0.023491381002374374
(137, 1866) 0.03969475253942053
(137, 1896) 0.07915772355142488
(137, 919) 0.023872748317938564
(137, 1500) 0.07728468461982171
(137, 753) 0.043373413973474426
(137, 235) 0.036290943551754246
(137, 1043) 0.02885156855169558
(137, 1576) 0.08423794621227862
(137, 240) 0.09461262585467081
(137, 1908) 0.12539855047313608
(137, 1435) 0.1301998889606912
(137, 854) 0.08359903364875737
(137, 1484) 0.09174653065996949
(137, 1044) 0.02885156855169558
[[1.47998182 1.1938479 0.93516535 ... 0.64108976 0.91951438 0.71990474]
[0.50918993 0.56496607 0.50559418 ... 0.50385102 0.50605441 0.50574252]]
(2, 2000)
困惑度:
4095.2085280249858
Topic #0:
企业 疫情 防控 服务 复工 协会 工作 社会 复产 组织 会员 心理 抗疫 及时 提供 饲料 社区 积极 就业 行业协会

Topic #1:
内容 美国 饭店 球队 赛区 酒店 客源 希腊 雅典 实验室 中超联赛 转会 广州 开幕式 猛犸 民众 日期 赛季 大连 苏州

PreparedData(topic_coordinates= x y topics cluster Freq
topic
0 0.024078 0.0 1 1 89.66411
1 -0.024078 0.0 2 1 10.33589, topic_info= Term Freq Total Category logprob loglift
403 内容 0.000000 0.000000 Default 30.0000 30.0000
1604 美国 0.000000 0.000000 Default 29.0000 29.0000
1975 饭店 0.000000 0.000000 Default 28.0000 28.0000
1865 酒店 0.000000 0.000000 Default 27.0000 27.0000
1404 球队 0.000000 0.000000 Default 26.0000 26.0000
... ... ... ... ... ... ...
1403 球员 0.093992 0.367312 Topic2 -7.2237 0.9065
886 平等 0.089363 0.349664 Topic2 -7.2742 0.9053
895 广州 0.104099 0.468188 Topic2 -7.1215 0.7660
1529 窗口 0.096892 0.421790 Topic2 -7.1933 0.7986
1304 民众 0.099578 0.576540 Topic2 -7.1659 0.5134

[94 rows x 6 columns], token_table= Topic Freq Term
term
134 1 0.810396 中国
241 1 1.023200 企业
255 1 1.017123 会员
324 1 0.898946 做好
528 1 1.054671 协会
565 1 1.055105 及时
715 1 1.000085 复产
717 1 0.927915 复工
736 1 0.886561 大学生
800 1 0.942634 宣传
833 1 1.133821 就业
854 1 0.886084 工作
919 1 0.872452 开展
954 1 0.917835 心理
1044 1 1.030462 抗疫
1098 1 1.068630 提供
1161 1 0.929940 新冠
1229 1 1.078302 服务
1383 1 0.851659 物业
1386 1 0.894247 物资
1435 1 0.981779 疫情
1484 1 0.977973 社会
1491 1 1.099239 社区
1512 1 1.128643 积极
1576 1 1.006787 组织
1637 1 0.923467 肺炎
1685 1 0.825705 行业
1686 1 0.797830 行业协会
1780 1 1.125439 赛事
1908 1 1.065603 防控
1910 1 0.941367 防疫
1976 1 1.075012 饲料, R=30, lambda_step=0.01, plot_opts={'xlab': 'PC1', 'ylab': 'PC2'}, topic_order=[1, 2])
Serving to http://127.0.0.1:8888/ [Ctrl-C to exit]
127.0.0.1 - - [29/Dec/2020 22:23:43] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [29/Dec/2020 22:23:44] "GET /LDAvis.css HTTP/1.1" 200 -
127.0.0.1 - - [29/Dec/2020 22:23:44] "GET /d3.js HTTP/1.1" 200 -
127.0.0.1 - - [29/Dec/2020 22:23:44] "GET /LDAvis.js HTTP/1.1" 200 -
127.0.0.1 - - [29/Dec/2020 22:23:44] code 404, message Not Found
127.0.0.1 - - [29/Dec/2020 22:23:44] "GET /favicon.ico HTTP/1.1" 404 -

生成对应的图形浏览器会打开,如下图所示:



注意:LDA主题分布分析需要设置不同的主题值,这里是2,也可以是3、4、5等等。那么如何确定最佳主题数呢?困惑数又有什么用呢?如果存在语义知识又怎么处理呢?主题如何能更加准确定位呢?读者可以带着这些思考去探索。加油~

八.总结

  • 实时数据爬取
  • 中文文本分词及高频词提取
  • 词云可视化分析
  • TF-IDF权重计算和文本聚类分析
  • 层次聚类分析
  • LDA主题模型分布

参考文献:


新闻信息抓取及数据分析
https://blog.baixf.tk/2020/12/29/python/新闻信息抓取及数据分析/
作者
白小飞
发布于
2020年12月29日
许可协议