很多时候我们会看到像如图这样的词语组成的图片,这就是词云图。词云以词语为基本单位,更加直观和艺术的展示。
wordcloud是优秀的词云展示的第三方库,我们可以借助wordcloud轻松实现词云图。使用Wordcloud之前需要先了解它的以下几个特点:
- 在wordcloud库把词云当作一个wordcloud对象
- 根据文本中词语出现的次数绘制词云
- 支持自定义设置词云的形状、颜色、尺寸
WordCloud使用
使用之前wordcloud库之前需要先安装一下,直接在线安装使用pip安装,安装命令:
>>>pip install wordcloud
Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple
Collecting wordcloud
……
Installing collected packages: wordcloud
Running setup.py install for wordcloud ... done
Successfully installed wordcloud-1.8.1
下面我们就可以使用wordcloud库来生成一张词云图。先来一个简单的demo感受一下。
from wordcloud import WordCloud
#待生成词云图的词语
text = "cherry peach pear apple banana pear lemon grape pear apple banana lemon grape pear cherry peach"
#创建WordCloud对象
wc = WordCloud(
background_color='white'
)
# 生成词云
wc.generate(text)
# 保存图片
wc.to_file('word.png')
代码运行结果如图所示,我们实现了一个简单的词云图。这个示例中的text自行编写了几个水果类的英语单词,单词出现的次数越多,在词云图中显示的会越大。代码每次运行都会生成不同的图片,单词的颜色可能不同,但是显示的大小是一致。
在WordCloud对象创建,默认的画布背景颜色是黑色的,这是为了方便展示,已经设置了background_color='white'。
WordCloud的全部参数如下所示:
WordCloud (self, font_path=None, width=400, height=200, margin=2, ranks_only=None, prefer_horizontal=.9, mask=None, scale=1, color_func=None, max_words=200, min_font_size=4, stopwords=None, random_state=None, background_color='black', max_font_size=None, font_step=1, mode="RGB", relative_scaling='auto', regexp=None, collocations=True, colormap=None, normalize_plurals=True, contour_width=0, contour_color='black', repeat=False, include_numbers=False, min_word_length=0, collocation_threshold=30)
主要参数详细说明:
font_path:字体路径。在win10系统中字体文件夹为C:\Windows\Fonts
width:输出的画布高度宽度,默认为400像素
height:输出的画布高度,默认为200像素
prefer_horizontal:词语水平方向排版出现的频率,默认 0.9。设置词语垂直方向排版出现频率为 0.1
mask : 用于设置自定义画布的背景
scale : 按照比例进行放大画布,如设置为1.5,则长和宽都是原来画布的1.5倍
min_font_size :显示的最小的字体大小,默认为4font_step :字体步长,默认为1,如果步长大于1,会加快运算但是可能导致结果出现较大的误差
mode:模式,默认为'RGB',当为'RGBA'时,如果背景颜色为None,则会得到透明的背景
max_words :要显示的词的最大个数,默认为200
stopwords :停用词,设置需要屏蔽的词,标点符号、语气词等,如果为空,则使用内置的STOPWORDS
background_color : 背景颜色,默认是black(黑色)
max_font_size :显示的最大的字体大小
relative_scaling :词频和字体大小的关联性
regexp : 使用正则表达式分隔输入的文本
collocations :是否包括两个词的搭配,默认是True
在上面例子中,text文本是英文,英文按照每个单词来统计词频,但如果是中文呢,这就是涉及到一个断句分词的问题。
比如说,“我比较喜欢用Python来做数据分析。”这句话需要分词后的结果有很多种,比较符合语意的分词是:我、比较、喜欢、用、Python、来、做、数据分析。当然“数据分析”也可以拆分成“数据”和“分析”,显然这里合起来更能体现原本的意义,这就是分词的作用。可以看出来这句话的核心词语就是“喜欢”,“Python”,“数据分析”。而像“我”,“来”,“做”以及句号等标点符号其实对于数据分析和统计并没有作用,在制作词云的时候,这些就是停用词,在wordcloud对象中通过stopwords参数设置停用词。如果不对停用词进行过滤,在一篇1万字的中文文章中,出现最多的可能就是“我”,“的”,“是”等这些词,这些词并不能反映文章的重点内容。
除了停用词,还有一点就是我们把这句话的重点词语提取为“喜欢”,“Python”,“数据分析”,而不是“欢用”,“剧分”等。跟英文不同的是,中文需要断句。如果分词不准确,那么制作词云将毫无意义。
Jieba分词
分词是自然语言处理的基础,分词的准确性直接决定了后面的词性标注、句法分析、词向量以及文本分析的质量。
在英文中可以使用空格将单词进行分隔,除了固定的搭配之外,大部分情况下不需要考虑分词问题。但是中文没有分隔符,即便是长句中有标点符号,但也需要读者自行分词和断句。所以在做中文自然语言处理时,我们需要先进行分词。
当前的分词算法主要分为两类,一种是基于词典的规则匹配方法,另外一种是基于统计的机器学习方法。而我们接下来用的jieba分词是综合了基于字符串匹配的算法和基于统计的算法。
Jieba分词是一个开源的Python 中文分词组件。源码地址:https://github.com/fxsjy/jieba它实现了对中文文本进行分词、词性标注、关键词抽取等功能,并且支持自定义词典。
通过Jieba库,使得一句中文分成了各个词语。然后我们才用这些词语来进行制作词云图。Jieba分词有四种分词模式:
1.精确模式:试图将句子最精确地切开,适合文本分析
2.全模式:把句子中所有的可以成词的词语都扫描出来, 速度非常快,但是不能避免歧义
3.搜索引擎模式:在精确模式的基础上,对长词再次切分,适合用于搜索引擎分词
4.paddle模式:利用PaddlePaddle深度学习框架,训练序列标注(双向GRU)网络模型实现分词
import jieba
text = "我比较喜欢用Python来做数据分析。"
# cut()直接得到generator形式的分词结果,默认是精准模式
result1 = jieba.cut(text)
print("精准模式:" + ' '.join(result1))
# 使用全模式
result2 = jieba.cut(text, cut_all=True)
print("全模式:" + ' '.join(result2))
# 使用搜索引擎模式
result3 = jieba.cut_for_search(text)
print("搜索引擎模式:" + ' '.join(result3))
# lcut()得到分词结果的list
result4 = jieba.lcut(text)
print(result4)
#代码运行结果:
精准模式:我 比较 喜欢 用 Python 来 做 数据分析 。
全模式:我 比较 喜欢 用 Python 来 做 数据 数据分析 分析
搜索引擎模式:我 比较 喜欢 用 Python 来 做 数据 分析 数据分析 。
['我', '比较', '喜欢', '用', 'Python', '来', '做', '数据分析', '。']
可以看出全模式会把句子中的所有词语都会扫描出来。“数据分析”不管是合起来还是拆分出来,意思都可以,但是在当前语境下,我们要的结果就是“数据分析”,所以我们在做词云的这种自然语言分析的时候直接选择默认切分模式就可以。
Jieba的除了分词,还有很多其他的功能,比说加载自定义词典、基于TF-IDF算法进行关键词抽取、词性标注、并行分词等功能,感兴趣的同学可以查阅源码了解。
制作自定义词云
在前面两个小节中,我们已经熟悉了wordcloud和中文分词库jieba的基本用法,下面我们就来动手做一个酷炫的词云图。
首先为了能更有针对性的体现出来词云图在数据分析和可视化中的作用。我们就使用知乎问题:有哪些是你去山东才知道的事情?如图所示,问题id:310830182,问题链接:https://www.zhihu.com/question/310830182
【注:回答可能被删除或者隐藏,这个数据是切片数据,不是实时数据】
这个问题目前有3967个回答,如果我们想知道非山东人来到山东后的第一印象是什么,传统的方法可能是需要每个回答逐一统计,分析一下答主对山东的印象,这样做非常耗费时间和精力。
所以我们采用把这3967个回答的文本通过爬虫的方式抓取到本地,然后对这个文本做词云图,就可以直观的出这3967的回答中山东的认识。
需求明确好了,我们开始规划操作步骤:
- 编写爬虫,爬取该问题下的4211下回答。
- 根据抓取下来的文本,生成wordcloud对象
- 下载并设置停用词
- 生成词云图
首先通过分析,可以发现知乎的回答的内容是通过json数据返回的,以这个问题为例子,请求url为:
https://www.zhihu.com/api/v4/questions/310830182/answers?include=data%5B%2A%5D.is_normal%2Cadmin_closed_comment%2Creward_info%2Cis_collapsed%2Cannotation_action%2Cannotation_detail%2Ccollapse_reason%2Cis_sticky%2Ccollapsed_by%2Csuggest_edit%2Ccomment_count%2Ccan_comment%2Ccontent%2Ceditable_content%2Cattachment%2Cvoteup_count%2Creshipment_settings%2Ccomment_permission%2Ccreated_time%2Cupdated_time%2Creview_info%2Crelevant_info%2Cquestion%2Cexcerpt%2Cis_labeled%2Cpaid_info%2Cpaid_info_content%2Crelationship.is_authorized%2Cis_author%2Cvoting%2Cis_thanked%2Cis_nothelp%2Cis_recognized%3Bdata%5B%2A%5D.mark_infos%5B%2A%5D.url%3Bdata%5B%2A%5D.author.follower_count%2Cbadge%5B%2A%5D.topics%3Bdata%5B%2A%5D.settings.table_of_content.enabled&limit=5&offset=15&platform=desktop&sort_by=default
从返回Boby中可以得到data中是5个回答内容,object下的excerpt属性值就是回答正文。paging对象中的next属性值是下一个5个回答的链接。
这里需要注意的是next的offset参数需要自己修改。
import json
import time
import requests
from copyheaders import headers_raw_to_dict
# 直接从浏览器复制请求头,包装成爬虫的请求头,这里的Cookie值容易过期,所以使用的时候先去浏览器浏览下网页,再copy出来
headers = b'''
Host: www.zhihu.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Referer: https://www.zhihu.com/question/310830182
x-ab-param:
x-ab-pb: CgQnB/gMEgIAAA==
x-requested-with: fetch
x-zse-93: 101_3_3.0
x-zse-96: 2.0_FuI9tIZy236tlhso3fvmW9pIw7HWA=jmdc64JZryeCKtJuiCgA+ki4R=FxqnvbFn
Connection: keep-alive
Cookie: _zap=51af6a70-c5f8-4bbd-bd3b-c6eb2fd88c21; d_c0="AJBQCuF9PRSPTmlTCbfwd82b9LSXIA6Qisg=|1640504022"; Hm_lvt_98beee57fd2ef70ccdd5ca52b9740c49=1690614757,1690623876,1691332580,1691415254; gdxidpyhxdE=ng2BtO50WP5f1kO140ICYowox9x7T2t1boLCrtc1tQfizy0ySBtoo9%2Fe6gLN44QJb%2BUfTkKRB5nPg1O7XBOBOKCoLEX14QZ3La198l5VaoQagrAs4XSCxmh%2BcZ%2BvbJUjm%2FWYTuaYWMNe9cX6kw15te5W6PSVEU%5CbNmEE%2B6vD4YVBHNyV%3A1649087636127; _9755xjdesxxd_=32; YD00517437729195%3AWM_NI=yTg0VEo5M8BFSv8HCBMQ5L5MMyNHGE4%2BnPHD4RfXWFucBfkH%2FeXsI7RcwrGQCuRnVIEnSg6KNd5RgTCqDP5TAjKECAfTynJyMckofYq1R9dGX9dOS0reGIJMTcGC%2FEAkcTU%3D; YD00517437729195%3AWM_NIKE=9ca17ae2e6ffcda170e2e6eed0fb64bb9c818aef7ced8e8aa2d44b969b8abaf43ef5ada3a7d780fb9b878ff92af0fea7c3b92abb8ca9d1d54fadeb83d9c14e91aaf8a9c24a8f8ffbadb8478fa782a9c64ff8f5a2b1cb50f88eaab9d64eb7bd8e87ea44edbb9894cc3d92bdb993c65b81ab9e8fd54387b29d88b5418ebcfbb3e66081af8cb1b86083b685a6c24ba68cb78fc9738fbda590e553b3ef9edae74d9ce7acb0d23cb2bb89aac94798bd89a4ae4f83b5adb5d837e2a3; YD00517437729195%3AWM_TID=bP9hVCpO%2FS5EEAAAQFZ%2B9r4myIS4B9Gs; _xsrf=qqx1cO4CmlT2OubRdAgSWyjC8Gf7SASD; q_c1=dd06905d0dbf45c3a2230f78f569f68f|1655103183000|1655103183000; KLBRSID=0a401b23e8a71b70de2f4b37f5b4e379|1691415960|1691415251; Hm_lpvt_98beee57fd2ef70ccdd5ca52b9740c49=1691415682; captcha_session_v2=2|1:0|10:1691415685|18:captcha_session_v2|88:MmdYaFhwSnZLOStpanpCbHExQnNCNGpnT293emtKdXRqRkk1UENiTGJESUFmOG5hckFPYkZUa1VVR1J4WmM2eg==|c368d801b82e797e9f8dbf1859423259f67a22b5689eb487e0141b48a23d972f
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
TE: trailers'''
# 解析数据
def parse_json_data(url):
response = requests.get(url, headers=headers_raw_to_dict(headers))
response.content.decode('utf-8')
contents = response.content
if contents != None:
# 编码格式转换
# api接口返回的数据是json格式,把json转换为字典结构,获取评论信息
comments = json.loads(contents)['data']
next = json.loads(contents)['paging']["next"]
comm = []
# encoding=utf_8_sig只能转换中文乱码和字母乱码,不能支持数字的乱码
with open("commentsd.txt", 'a+', encoding='utf_8', newline='')as f:
for comment in comments:
content = comment['target']['excerpt']
f.writelines(content + "\n")
f.flush()
f.close()
return next
next_url = ''
# 爬虫主入口
def main(url):
next = parse_json_data(url)
if next != None:
time.sleep(2)
next_url = next
# 主程序
# https://www.zhihu.com/api/v4/questions/310830182/feeds?cursor=a197e94058ca8982358221d5121b132b&include=data%5B%2A%5D.is_normal%2Cadmin_closed_comment%2Creward_info%2Cis_collapsed%2Cannotation_action%2Cannotation_detail%2Ccollapse_reason%2Cis_sticky%2Ccollapsed_by%2Csuggest_edit%2Ccomment_count%2Ccan_comment%2Ccontent%2Ceditable_content%2Cattachment%2Cvoteup_count%2Creshipment_settings%2Ccomment_permission%2Ccreated_time%2Cupdated_time%2Creview_info%2Crelevant_info%2Cquestion%2Cexcerpt%2Cis_labeled%2Cpaid_info%2Cpaid_info_content%2Creaction_instruction%2Crelationship.is_authorized%2Cis_author%2Cvoting%2Cis_thanked%2Cis_nothelp%3Bdata%5B%2A%5D.mark_infos%5B%2A%5D.url%3Bdata%5B%2A%5D.author.follower_count%2Cvip_info%2Cbadge%5B%2A%5D.topics%3Bdata%5B%2A%5D.settings.table_of_content.enabled&limit=5&offset=0&order=default&platform=desktop&session_id=1691415445655365450
if __name__ == '__main__':
url = "https://www.zhihu.com/api/v4/questions/310830182/feeds?cursor=bad0218cc2ad79439e0125b6a36464ce&include=data%5B%2A%5D.is_normal%2Cadmin_closed_comment%2Creward_info%2Cis_collapsed%2Cannotation_action%2Cannotation_detail%2Ccollapse_reason%2Cis_sticky%2Ccollapsed_by%2Csuggest_edit%2Ccomment_count%2Ccan_comment%2Ccontent%2Ceditable_content%2Cattachment%2Cvoteup_count%2Creshipment_settings%2Ccomment_permission%2Ccreated_time%2Cupdated_time%2Creview_info%2Crelevant_info%2Cquestion%2Cexcerpt%2Cis_labeled%2Cpaid_info%2Cpaid_info_content%2Creaction_instruction%2Crelationship.is_authorized%2Cis_author%2Cvoting%2Cis_thanked%2Cis_nothelp%3Bdata%5B%2A%5D.mark_infos%5B%2A%5D.url%3Bdata%5B%2A%5D.author.follower_count%2Cvip_info%2Cbadge%5B%2A%5D.topics%3Bdata%5B%2A%5D.settings.table_of_content.enabled&limit=5&offset=0&order=default&platform=desktop&session_id=1691415957851578837"
next_url = url
for i in range(600):
# 运行代码
if next_url != None:
next = next_url.replace("offset=0", "offset=" + str(5 * (i)))
print(next)
main(next)
因为知乎有Cookie验证,所以需要先将浏览器中Cookie复制到代码中,再运行。经过一段时间运行,我们在当前目录下可以得到commentsd.txt的文件。
现在已经拿到了评论的内容,下面就可准备制作词云图。在开始之前我们先去下载一份常用的停用词,这样更方便我们编写代码。
下载地址:https://github.com/goto456/stopwords
其中包含了四份常用的停用词,几份停用词各不一样,我们选择一个内容多点的百度停用词表。
接下来开始进行词云绘制编码:
from wordcloud import WordCloud
import jieba
# 待生成词云图的评论文本文件
fn = open("commentsd.txt", encoding='utf8')
# 读出整个文件
string_data = fn.read()
# 停用词文件
stopwords = open("baidu_stopwords.txt", encoding='utf8')
# 将停用词文件转成list
stopwords_data = stopwords.read().split("\n")
# 使用jieba分词将评论内容分词
comment_text = jieba.cut(string_data)
# 分词内容转成字符串
text = ' '.join(comment_text)
# 添加自定义停用词
stopwords_self = ['一个', '说', '没', '会', '很', '真的', '人', '很多', '才', '还', '去', '上', '图片', '不', '太', '我', '走', '最', '的', ',' '是', '了', '在', '就', '都', '山东人']
stopwords_data.extend(stopwords_self)
# 创建WordCloud对象,设置中文字体和配置停用词
wc =
WordCloud(font_path='C:/Windows/Fonts/simhei.ttf', background_color="white",
stopwords=stopwords_data
)
# 生成词云
wc.generate(text)
# 保存图片
wc.to_file('word.png')
在制图词云图时候,肯定会根据当下的文本环境来设置自定义停用词库,比如在以上代码中stopwords_self的内容,这些词语在文本中大量的出现,但是并没有什么实际的含义,所以我们通过添加自定义停用词的方式将这些词语去掉,这样才能最大程度的反应出文本的真实的内容。
先看下生成的效果图
从图中可以看出,外省人第一次来山东,或者对山东的感受就是:喝酒、大、好、吃、煎饼、大葱等关键词以及最代表山东的城市青岛和济南。如果是一个外省人,能快速地了解山东,或者说给山东几个关键词,那么这就是结果,这也是数据分析和可视化的价值和意义的所在。
虽然现在已经制作出来词云,但是如果还能更个性一点,会给人更新鲜的感觉。还可以设置自定义背景图片,生成的词云按照背景图的形状。
在代码中添加上背景图片的处理,将词云对象的参数修改如下:
import numpy
from wordcloud import WordCloud
import jieba
# 待生成词云图的评论文本文件
fn = open("commentsd.txt", encoding='utf8')
# 读出整个文件
string_data = fn.read()
# 停用词文件
stopwords = open("baidu_stopwords.txt", encoding='utf8')
# 将停用词文件转成list
stopwords_data = stopwords.read().split("\n")
# 使用jieba分词将评论内容分词
comment_text = jieba.cut(string_data)
# 分词内容转成字符串
text = ' '.join(comment_text)
# 添加自定义停用词
stopwords_self = ['一个', '说', '没', '会', '很', '真的', '人', '很多', '才', '还', '去', '上', '图片', '不', '太', '我', '走', '最', '的',
',' '是', '了', '在', '就', '都', '山东人', '差不多', '感觉', '区', '评论', '感受', '发现', '福建', '小']
stopwords_data.extend(stopwords_self)
from PIL import Image
# 设置背景图片
bg = numpy.array(Image.open('view.png'))
# 创建WordCloud对象,设置中文字体和配置停用词
wc = WordCloud(font_path='C:/Windows/Fonts/simhei.ttf', background_color="white", scale=2.5,
stopwords=stopwords_data, mask=bg)
# 生成词云
wc.generate(text)
# 保存图片
wc.to_file('new_word.png')
重新运行代码,得到新的词云图。
这样我们根据山东省的地图的形状,生成了一个关于其他省份的人们对于山东的印象的特色词云图。同样感兴趣的读者可以选择知乎上的其他的问题,进行词云绘制,这样可以帮你快速的了解某一个热门问题下的回答的人都说了什么。
以上文中所示例的爬虫代码,仅仅是代表当时数据,也就是说,源网站的返回体json结构发生变化时,那么此代码是不能通用的,仅供演示。