新年伊始,新型冠状病毒(2019-nCoV)肆虐中华,让人倍感心痛。在持续关注疫情的同时,我也不禁思考,怎样才能利用自己的专业知识为疫情贡献一点力量。在浏览有关新型冠状病毒的新闻时,一些关键词不断涌现:口罩、疑似病例、小汤山、驰援武汉、消毒等等,辟谣与反辟谣的新闻屡见不鲜。持续关注疫情资讯的我不禁对舆情的关注热点产生了好奇,在疫情大规模爆发的当下,主流媒体的报道究竟关注哪些热点信息?报道的重点是否有变化?出现最多的关键词是哪些?作为吃瓜群众的我们究竟怎样才能明辨是非,尽到不信谣不传谣的责任?
带着这些疑问,我通过python的网络爬虫和自然语言处理技术,对CCTV央视网的2020年1月27号的新闻进行提取分析,得到了下面的“疫情词云”图。希望通过这种方式窥探舆情热点的重心和变化趋势,从数据维度理解新闻。
图 1 基于27日CCTV新闻的疫情词云
PART1 数据分析
首
先我们对数据进行一些简单的分析。本次提取的新闻共计76条,56967个字。去停用词、分词后,共获得4405个词语。根据词云图,我们可以得到各词的词频排名和占比情况。其中,词频排名前10的词为:武汉市237次、疫情202次、医院195次、防控130次、新型冠状病毒122次、感染108次、工作106次、记者101次、肺炎95次、物资80次。其中,武汉市、疫情、医院三者词频远高于其他词,基本为其他词的一倍以上,这说明大量的新闻关注武汉市的疫情和医院相关情况。典型的新闻包括“必胜!武汉协和医院西院31名护士剪发抗‘疫’”、“解放军医疗队开始收治病人,救治工作全面展开”、“战疫情一线报告”等鼓舞人心和描述救治细节的相关报道,让人第一时间了解疫情最新进展。
图 2 新闻文本中词频排名前20的词
事实上,在抓取的新闻中不乏“美最高法院允许特朗普推动移民新政”等国际新闻,但是占比明显较少。近两日的新闻仍旧以疫情为主,可以从图3看到词频排名前30的词全部与疫情有关,涉及病毒名称、防控手段、患者情况、症状、相关部门与人员等。
图 3 疫情词频排名
接
下来可以对文本词语的分布数据做一个更为细致的探索。从图4可以看到,排名前20的词占据了所有词汇的80%以上,这说明在27日的这70篇报道中,这些词语反复出现。数据整体呈现出一个急速衰减的幂律分布,说明新闻关键词的集中度非常之高,由此可见主流媒体对“疫情、武汉”等相关内容的爆发式报道。
图 4 27日新闻词频分布
图 5 24日新闻词频分布TOP50
图 6 25日新闻词频分布TOP50
图 7 26日新闻词频分布TOP50
Y = 213.310856X^(- 0.56776567)。由方程可知,24号的方程曲率更缓,系数更小,说明24日的报道频次和关键词密度低于1月27日,可见自24日以来政府的重视程度在加大,媒体的报道热点更集中。
图 8 24、27日拟合分析
PART2 Python实战
接下来重点介绍一下爬虫的过程和疫情词云的绘制过程,准备好开始一场技术风暴吧!
图 9 我们即将爬取的央视网CCTV.COM
先,我找到了最权威的央视网新闻版块,URL链接 “http://news.cctv.com/china/”。我们先来分析网页结构。使用谷歌浏览器,打开CCTV新闻,按F12进入开发者调试模式。用调试页面左上角的小箭头图标点击新闻标题,可以看到调试页面中弹出了相关的html结构。通过观察,可以发现所有的新闻标题位于一个class=”con “的< div ></div>的标签下。通过观察该标签树的下行标签,可以看到每一个 <li style=”display: list-item;” class=”borderno”>标签下面就是一个新闻模块,新闻的标题和URL链接都在<h3 class=”title” >标签下。接下来要爬取的就是这些新闻的URL链接了。
图10 按F12观察网页结构
图11 查看网页源代码
图 12 Fiddler官网
图13 Fiddler相关配置
我
们打开Fiddler,如果Fiddler捕捉到了一些会话,则按CTRL + X 清空会话面板,避免干扰。然后找到刚才的新闻页面,按F5刷新。这时候可以看到Fiddler已经抓到相关网页。刚才我们说过网页是通过Java Script动态加载的,所以我们找到news.cctv.com网站,点击状态栏的JSON,就可以看到抓包下来的JS文件。观察蓝框里的信息,正是我们刚才看到的新闻标题和URL信息。但是不要着急,在浏览网页的时候,可以看到在新闻列表的最后,有一个“加载更多”按钮,点击之后,Fiddler又抓到了更多的JS文件,所以在多次点击新闻列表后,我们抓取到了27号、28号的所有新闻网页。在会话面板按住CTRL选择所有的相关网址,右键——“保存”——“所有会话”——“为文本”,将文件保存到“CCTV新闻.txt”文件。
图 14 Fiddler抓取第一个网页的JS文件
图 15 Fiddler抓取更多加载出来的JS文件
图 16 将所有会话保存成txt格式
进
入python中分析和提取刚才获取到的新闻信息,我使用的是WING PRO的IDE。首先导入所需要的库,分别是requests库、BeautifulSoup库和Re库。其中RE库是正则表达式库,用来提取文件信息。requests + BeautifulSoup技术路线,用于爬取我们接下来获取到的新闻URL。代码如下:
1. #-*- coding:utf-8 -*-
2. import requests
3. import re
4. from bs4 import BeautifulSoup
图 17 解析响应文本
1. urllst = []
2. titlelst = []
3. out2 = open('新闻.txt','a',encoding='utf-8')
4. with open (r'E:pyguanzhuangbingducctvCCTV新闻.txt','r',encoding='utf-8') as f:
5. text = f.readlines()
6. for line in text:
7. title = re.findall('"title":"(.+?)"',line)
8. url = re.findall(r'http://(.+?).shtml"',line)
9. if url:
10. urllst.append(url)
11. #print(urllst)
12.
13. for i in range(len(urllst)):
14. for w in urllst[i]:
15. url = 'http://'+ w + '.shtml'
16. print(url)
17. titlelst.append(url)
打印一下,可以看到我们获取到的所有新闻网址,复制到浏览器打开,果然没错。使用append方法将所有url写入一个列表。
图 18 打印所有URL信息
图 19 使用. text方法提取网页html信息
1. #-*- coding:utf-8 -*-
2. import requests
3. import re
4. from bs4 import BeautifulSoup
5.
6. urllst = []
7. titlelst = []
8. out2 = open('新闻.txt','a',encoding='utf-8')
9. with open (r'E:pyguanzhuangbingducctvCCTV新闻.txt','r',encoding='utf-8') as f:
10. text = f.readlines()
11. for line in text:
12. title = re.findall('"title":"(.+?)"',line)
13. url = re.findall(r'http://(.+?).shtml"',line)
14. if url:
15. urllst.append(url)
16. #print(urllst)
17.
18. for i in range(len(urllst)):
19. for w in urllst[i]:
20. url = 'http://'+ w + '.shtml'
21. #print(url)
22. titlelst.append(url)
23. for urll in titlelst:
24. #print(urll)
25. try:
26. r=requests.get(urll)
27. r.encoding = r.apparent_encoding
28. r.raise_for_status
29. print(r.text)
30. soup = BeautifulSoup(r.text,'lxml')
31. div = soup.find_all('div',class_="content_area")
32. soup2 = BeautifulSoup(str(div),'lxml')
33. p = soup2.find_all('p')
34. contents = re.findall('[^<p></p>]',str(p))
35. print(''.join(contents))
36. print(''.join(contents),file = out2 )
37. except:
38. print('失败')
1. # -*- coding: utf-8 -*-
2. import jieba
3. import jieba.posseg as pseg
4. # 创建停用词list
5. def stopwordslist(filepath):
6. stopwords = [line.strip() for line in open(filepath, 'r', encoding='utf-8').readlines()]
7. return stopwords
8. # 对句子进行分词
9. def seg_sentence(sentence):
10. jieba.load_userdict('字典.txt')
11. #jieba.analyse.set_stop_words('changan_stopwords.txt')
12. sentence_seged =pseg.cut(sentence.strip())
13. sentence_seged = jieba.cut(sentence.strip(),cut_all=False)
14. stopwords = stopwordslist('最全中文停用词.txt') # 这里加载停用词的路径
15. outstr = ''
16. for word in sentence_seged:
17. if word not in stopwords:
18. if word != 't':
19. outstr += word
20. outstr += " "
21. return outstr
22. inputs = open('新闻.txt', 'r', encoding='utf-8') # 这里加载分词后文本的路径
23. outputs = open('新闻_分词去停用词.txt', 'w',encoding='utf-8') # 这里加载输出文本的路径
24. for line in inputs:
25. line_seg = seg_sentence(line) # 这里的返回值是字符串
26. outputs.write(line_seg + 'n')
27. outputs.close()
28. inputs.close()
29. print('success')
字典和停用词典如下:
图 20 字典与停用词典
图 21 去停用词、分好词的新闻
分
好词之后,我们使用一个非常强大的词云生成软件,wordcloud库,生成词云。需要说明的是,wordcloud对中文并不友好,所以要先下载好中文字体,注意字体名称不要出现中文。然后一定要将字体分别保存到python插件目录下的PLT和WORDCLOUD插件的目录下,然后要将该中文字体同时放到python代码文件夹中,这样才能正确的提取字体信息。
1. # -*- coding: utf-8 -*-
2. #!/usr/bin/env python
3. import collections
4. from PIL import Image
5. import numpy as np
6. import matplotlib.pyplot as plt
7. from scipy.misc import imread
8. from wordcloud import WordCloud,ImageColorGenerator
9. import random
图 22 遮罩图
1. def random_color_func(word=None, font_size=None, position=None, orientation=None, font_path=None, random_state=None):
2. h = random.randint(330,360)
3. s = int(100.0 * float(random.randint(40, 200)) / 255.0)
4. l = int(100.0 * float(random.randint(20, 150)) / 255.0)
5. return "hsl({}, {}%, {}%)".format(h, s, l)
在这里推荐一个可以查看HSL颜色的网站(https://www.yansedaquan.com/HSLDaQuan?S=10&H=220),该网站可以在线调试合适的颜色。在上文的代码中,我们使用random工具随机生成h(色相)亮度(s)和纯度(l)百分数。例如,如果将h区间取在261-279之间,则以紫色为主。
图23 调试HSL颜色的网址
1. mask= imread('中国地图.png')
2. wc = WordCloud(font_path=font_path2, # 设置字体
3. background_color=None, mode="RGBA", # 背景颜色
4. max_words=500, # 词云显示的最大词数
5. mask=mask, # 设置遮罩
6. max_font_size=130 , # 最大字号
7. random_state=40,
8. font_step= 1,
9. min_font_size=16,
10. collocations=False,
11. color_func = random_color_func)
12. wc.generate(text)
13. wc.to_file("temp.png")
1. # -*- coding: utf-8 -*-
2. #!/usr/bin/env python
3. import collections
4. from os import path
5. from PIL import Image
6. import numpy as np
7. import matplotlib.pyplot as plt
8. from scipy.misc import imread
9. from wordcloud import WordCloud,ImageColorGenerator
10. import random
11. d = path.dirname(__file__)
12. font_path2='121.TTF'
13. # 读取整个文本.
14. text = open('新闻处理_分词去停用词.txt','r',encoding= 'utf-8').read()
15.
16. def random_color_func(word=None, font_size=None, position=None, orientation=None, font_path=None, random_state=None):
17. h = random.randint(330,360)
18. s = int(100.0 * float(random.randint(40, 200)) / 255.0)
19. l = int(100.0 * float(random.randint(20, 150)) / 255.0)
20. return "hsl({}, {}%, {}%)".format(h, s, l)
21.
22. mask= imread('中国地图2.png')
23. wc = WordCloud(font_path=font_path2, # 设置字体
24. background_color=None, mode="RGBA", # 背景颜色
25. max_words=500, # 词云显示的最大词数
26. mask=mask, # 设置背景图片
27. max_font_size=130 ,
28. random_state=40,
29. font_step= 1,
30. min_font_size=16,
31. collocations=False,
32. color_func = random_color_func)
33. # 生成词云
34. wc.generate(text)
35. wc.to_file("temp.png")
36. print (wc.words_)
37. print(wc.layout_)
38.
39. plt.figure()
40. plt.imshow(wc, interpolation='bilinear')
41. plt.axis("off")
42. plt.show()
PART3 小结
确定要爬取的对象网站,通过浏览网页源代码,发现网址是经过了动态加密
2
用Fiddler抓包,并保存抓包下来的全部会话
3
通过正则表达式提取所有的URL信息,并将URL信息写入列表,方便提取
4
从列表中一个一个提取URL,然后用requests库提取网址的HTML信息。粗略得到我们想要的新闻内容
5
选择JIEBA库分词。制作分词字典。同时,我们利用停用词字典去掉文本中的杂质(如英文字母)和不想要的词。
6
导入wordcloud库生成词云。在生成词云的过程中通过HSL色彩模式优化了输出的词云色彩,并利用遮罩生成了中国地图形状的词云。
注:本文所有方法和技术均为学术探讨,未进行商业利用。
撰稿/肖天意
校对/肖天意 王超群 何捷
原文始发于微信公众号(空间人文与场所计算):疫情词云|基于大数据和自然语言处理的舆情热点分析