疫情词云|基于大数据和自然语言处理的舆情热点分析

  新年伊始,新型冠状病毒(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日新闻词频分布


我们再分别对24-26日新闻的前50个关键词进行词频统计,并与27日新闻进行对比。可以发现自24日起,“疫情”一词一直占据新闻最高频词汇,并且数量急剧攀升,从24日的186次攀升到26日的551次,可以看到疫情升温的趋势。而25、26日武汉、防控晋升到词频前三名,这与25、26两日雷神山防疫站的建设、医疗队驰援武汉的情况,以及日益升温的防控手段相契合。
疫情词云|基于大数据和自然语言处理的舆情热点分析

图 5 24日新闻词频分布TOP50


疫情词云|基于大数据和自然语言处理的舆情热点分析

图 6 25日新闻词频分布TOP50


疫情词云|基于大数据和自然语言处理的舆情热点分析

图 7 26日新闻词频分布TOP50


幂律分布公式为:Y = aX^(-b)。我们利用python开源的数学、科学和工程计算包SciPy (pronounced “Sigh Pie”)来拟合1月27日的词频曲线,得出拟合方程如下:Y = 299.45299X^(-0.6299618),拟合程度高达99%以上。
同时我们可以对比1月24日的新闻报道,拟合方程为:
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观察网页结构


在分析网页结构之后,CTRL + U查看网页源代码,CTRL + F调出搜索框,搜索刚才的关键词class=”con “,结果发现,该标签在源码中是唯一的,但是要爬取的数据在网页源代码中并不存在。
疫情词云|基于大数据和自然语言处理的舆情热点分析

图11 查看网页源代码


很多小伙伴到这里就束手无策了,其实大家不必惊慌,这说明页面采取了动态加载的方式,是一个动态网页。动态网页是与静态网页相对的一种网页编程技术。静态网页,随着html代码生成,页面的内容和显示效果就固定了。而动态网页则不然,其显示的页面则是经过Java Script处理数据后生成的结果,可以随着用户的点击发生改变。目前,越来越多的网站采取的是这种动态加载网页的方式,一来是可以实现web开发的前后端分离,减少服务器直接渲染页面的压力;二来可以作为反爬虫的一种手段。
目前爬虫技术中处理动态网页的主流手段包括逆向工程、模拟浏览器行为、渲染动态网页等。考虑到大家的学习成本,我在这里介绍一下我常用的一种手段,基本可以处理所有类似的动态网页加密问题。我最常用的就是Fiddler抓包(Fiddler官网和下载方式如下https://www.telerik.com/fiddler)。
疫情词云|基于大数据和自然语言处理的舆情热点分析

图 12 Fiddler官网


Fiddler,是一个http协议调试代理工具,它能够记录并检查所有你的电脑和互联网之间的http通讯,查看所有的“进出”Fiddler的数据(指cookie, html, js, css等文件)。它使用代理地址:127.0.0.1,端口:8888。Fiddler默认无法抓取HTTPS协议,需要进行一定的配置,配置方法请大家自行百度,简单来讲就是在菜单栏中点击“工具”——“选项”——“HTTPS”中,勾选“捕获HTTPS连接”下的所有选项,点击“操作”,下载相关证书即可。

疫情词云|基于大数据和自然语言处理的舆情热点分析

图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  



接下来,我们用正则表达式提取刚才获取的“CCTV新闻.txt”文件中的信息。首先打开文件,分析响应文件内容。可以看到每一条新闻保存在一个list列表中([ ]表示list),而新闻中的URL标签和TITLE标签分别储存着新闻的网址和标题。在仔细观察URLhttp://news.cctv.com/2020/01/28/ARTI2Qxlz4WpJkBgMf4rEgDE200128.shtml”。可以发现,发现所有的URL都是由http://news.cctv.com/作为标头,加上“2020/01/28”代表新闻日期,后面的一串英文代表新闻编号,最后由“.shtml”结尾。

疫情词云|基于大数据和自然语言处理的舆情热点分析

图 17 解析响应文本


分析完了响应文件,我们用with open打开该文件,用readlines()方法按行读取文件信息,然后构建正则表达式提取新闻编号,最后将编号加入网址结构中,得到所有新闻URL。代码如下:
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信息


提取列表中的URL信息,使用requests . get方法向网页发送GET命令,得到网页html信息,使用 r. encoding = r. apparent_encoding 方法得到网页的编码格式(一般为UTF-8格式),如果不加这一行,则会因为编码格式不正确而显示乱码。然后用r. raise_for_status方法判断网页的返回码是否正确,如果不正确程序会报错。一般网页的返回码为200即为正确,可用print (r. status_code)查看,如果出现其他返回码,如常见的404, 503等,则需要查看相关信息寻找错误原因。接下来,我们可以用 . text命令查看网页返回的信息。
疫情词云|基于大数据和自然语言处理的舆情热点分析

图 19 使用. text方法提取网页html信息


可以看到,新闻正文放在<div class=”content_area” id=”content_area”>下,每一行信息都放在一个<p>标签下。所以,接下来我们用BeautifulSoup库来提取该div,和<p>标签。用正则表达式库去掉所有的干扰信息,然后用join方法将文本组合成字符串,储存到“新闻.txt”文档中。至此,我们得到了所有的新闻文本。完整爬虫代码如下:
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('失败')


使自然语言处理工具,对得到的新闻文本进行分词处理。分词工具采用JIEBA库,首先我根据新闻内容,制作了一个分词字典,包含了所有专有名词,然后在网上下了一个停用词表。使用jieba. cut 工具对新闻文本进行分词处理。完整代码如下:
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代码文件夹中,这样才能正确的提取字体信息。

首先导入要用到的库,其中collections库可以分析词频,PIL库、scipy.misc 库、numpy库用于读取遮罩图片(使用3种方式都可以),matplotlib库用于画图,wordcloud库用于生成词云,random库用来生成随机颜色。代码如下:
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 遮罩图


此外,wordcloud默认的颜色比较丑,所以我用HSL色彩模型写了一个随机颜色生成器。HSL是一种将RGB色彩模型中的点在圆柱坐标系中的表示法。这两种表示法试图做到比基于笛卡尔坐标系的几何结构RGB更加直观。HSL即色相、饱和度、亮度(英语:Hue, Saturation, Lightness)。色相(H)是色彩的基本属性,就是平常所说的颜色名称,如红色、黄色等;饱和度(S)是指色彩的纯度,越高色彩越纯,低则逐渐变灰,取0-100%的数值;明度(V),亮度(L),取0-100%。代码如下:
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颜色的网址


在写好颜色生成器后,配置词云相关参数,包括font_path字体、background_color背景颜色,如果将该参数设置为None, mode选择”RGBA”模式,则无背景、max_words词云显示的最大词数、mask遮罩、max_font_size最大字号、min_font_size最小字号、color_func色彩等。最后,可以使用wc.generate生成词云和wc.to_file保存至文件,代码如下:
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")


此外,还可以用print (wc.words_)方法查看每一个词的权重和print(wc.layout_)方法查看每一个词的权重、色彩、大小等。词云的完整代码如下:
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 小结


1

确定要爬取的对象网站,通过浏览网页源代码,发现网址是经过了动态加密


2

Fiddler抓包,并保存抓包下来的全部会话


3

通过正则表达式提取所有的URL信息,并将URL信息写入列表,方便提取


4

从列表中一个一个提取URL,然后用requests库提取网址的HTML信息。粗略得到我们想要的新闻内容


5

选择JIEBA库分词。制作分词字典。同时,我们利用停用词字典去掉文本中的杂质(如英文字母)和不想要的词。


6

导入wordcloud库生成词云。在生成词云的过程中通过HSL色彩模式优化了输出的词云色彩,并利用遮罩生成了中国地图形状的词云。


最后,我相信,众志成城、万众一心,武汉一定会在全体中国人民齐心协力和世界各国的密切帮助下挺过难关,消灭疫情。
英雄武汉,加油!

 

注:本文所有方法和技术均为学术探讨,未进行商业利用。



                                                                             撰稿/肖天意

校对/肖天意 王超群 何捷

原文始发于微信公众号(空间人文与场所计算):疫情词云|基于大数据和自然语言处理的舆情热点分析

About the Author: DH