新冠肺炎終于在舉國上下的努力下得以控制,大家的工作生活也慢慢開始走向正規(guī),疫情帶來巨大的災難,但同時也給研究者留下許多珍貴的數據。針對這次肺炎疫情,本文作者將抓取疫情相關微博話題及評論信息,采用SnowNLP進行簡單的情感分析及文本挖掘,包括隨時間的情感分布,希望對這一領域的學習者有幫助。
首先放上代碼下載地址:https://github.com/eastmountyxz/Wuhan-data-analysis
CSDN下載地址:
https://download.csdn.net/download/Eastmount/12239638
微博話題數據抓取
該部分內容參考及修改我的學生兼朋友“楊友”的文章,也推薦博友們閱讀他的博客,給予支持。作為老師,最開心的事就是看到學生成長和收獲。他的博客地址:python爬蟲爬取微博之戰(zhàn)疫情用戶評論及詳情
微博網址:https://m.weibo.cn/
1.爬蟲解析
第一步,進入微博審查元素,定位評論對應節(jié)點,后續(xù)抓取評論信息。
進入微博后,點擊《戰(zhàn)疫情》主題下,并隨便選擇一個動態(tài)進行分析,我就選擇了“央視新聞網”的一條動態(tài)態(tài)“https://m.weibo.cn/detail/4471652190688865”進行分析。
我們剛打開該話題的時候,它顯示的是187條評論,但是在審查時可以看到文章中的20個div,并且每個div中裝載一條評論,每個頁面原始就只能顯示20條評論。
當我們把鼠標不斷向下滑動的過程中,網頁元素中的div也不斷隨評論的增加而增加,當活動到底部時,所有評論都加載出來了。初步判斷該網頁屬于ajax加載類型,所以先就不要考慮用requests請求服務器了。
第二步,獲取Ajax加載的動態(tài)鏈接數據,通過發(fā)布id定位每條話題。
這些數據都是通過Ajax動態(tài)加載的,點擊到《戰(zhàn)疫情》主題,發(fā)現它的URL并沒有變化,具體瀏覽幾篇文章后發(fā)現,它的的部分URL都是統(tǒng)一的,文章鏈接 = ‘https://m.weibo.cn/detail/’+發(fā)布時的id,可以通過剛找到的 id 在瀏覽器中拼接試試。
比如下圖所示的微博內容。比如:https://m.weibo.cn/detail/4472846740547511
第三步,下拉網頁動態(tài)刷新數據,發(fā)現獲取多個page的規(guī)律。
接下來是獲取它下一個加載數據的通道,同樣是通過抓包的方式獲取,不斷的下拉網頁,加載出其他的Ajax數據傳輸通道,再進行對比??梢院苊黠@的看出,它的當前鏈接就只是帶上了 “&page=當前數字” 的標簽,并且每次加載出18篇動態(tài)文章。
查看元素信息如下圖所示,每個page顯示18個微博話題。
第四步,調用json.loads()函數或在線網站解析Json數據。
拿到的數據是json格式,再提取信息前需要把str文本轉化為json數據,進行查找,可以使用json庫查看它的結構 ,也可以在線json解析查看它的結構,更推薦在線解析,方法結構比較清晰。
在線解析后的結果,簡單的給它打上標簽,每一個等級為一塊,一級包括二級和三級,二級包括三級… 然后通過前面的標簽進行迭代輸出,索引出來。在線網站:https://www.json.cn/
第五步,獲取每條微博的ID值。
調用方法如下,然后把拿到的id加在https://m.weibo.cn/detail/ 的后面就可以訪問具體的文章了。
import requests
api_url = 'https://m.weibo.cn/api/feed/trendtop?containerid=102803_ctg1_600059_-_ctg1_600059'
reponse = requests.get(api_url)
for json in reponse.json()['data']['statuses']:
comment_ID = json['id']
print (comment_ID)
此時提取所有鏈接代碼如下:
import requests,time
from fake_useragent import UserAgent
comment_urls = []
def get_title_id():
'''爬取戰(zhàn)疫情首頁的每個主題的ID'''
for page in range(1,3):# 這是控制ajax通道的量
headers = {
'User-Agent' : UserAgent().chrome #chrome瀏覽器隨機代理
}
time.sleep(2)
# 該鏈接通過抓包獲得
api_url = 'https://m.weibo.cn/api/feed/trendtop?containerid=102803_ctg1_600059_-_ctg1_600059&page=' + str(page)
print (api_url)
rep = requests.get(url=api_url, headers=headers)
for json in rep.json()['data']['statuses']:
comment_url = 'https://m.weibo.cn/detail/' + json['id']
print (comment_url)
comment_urls.append(comment_url)
get_title_id()
輸出結果如下:
https://m.weibo.cn/api/feed/trendtop?containerid=102803_ctg1_600059_-_ctg1_600059&page=1
https://m.weibo.cn/detail/4472725286834498
https://m.weibo.cn/detail/4472896510211624
https://m.weibo.cn/detail/4472846892243445
https://m.weibo.cn/detail/4472901455185821
https://m.weibo.cn/detail/4472856669039437
https://m.weibo.cn/detail/4472897055545751
https://m.weibo.cn/detail/4472891342667233
https://m.weibo.cn/detail/4472879381479272
https://m.weibo.cn/detail/4472889565122923
https://m.weibo.cn/detail/4472884950738226
https://m.weibo.cn/detail/4472883461527008
https://m.weibo.cn/detail/4472904014106917
......
第六步,調用requests ajax 爬取更多信息。
現在需要獲取更多的信息,如用戶id、性別之類的,這不是selenium可以完成的操作了,還得使用ajax的方式獲取json數據,提取詳細的信息。這里有個字段是max_id, 我們需要在上一個json文件底部找到該值。
目標:話題鏈接、話題內容、樓主ID、樓主昵稱、樓主性別、發(fā)布日期、發(fā)布時間、轉發(fā)量、評論量、點贊量、評論者ID、評論者昵稱、評論者性別、評論日期、評論時間、評論內容
第一個通道
現在可以預測下一個max_id
成功地通過上一個通道拿到了下一個通道的max_id,現在就可以使用ajax加載數據了。
2.爬蟲完整代碼
# -*- coding: utf-8 -*-
import requests,random,re
import time
import os
import csv
import sys
import json
import importlib
from fake_useragent import UserAgent
from lxml import etree
importlib.reload(sys)
startTime = time.time() #記錄起始時間
#--------------------------------------------文件存儲-----------------------------------------------------
path = os.getcwd() + '/weiboComments.csv'
csvfile = open(path, 'a', newline='', encoding = 'utf-8-sig')
writer = csv.writer(csvfile)
#csv頭部
writer.writerow(('話題鏈接','話題內容','樓主ID', '樓主昵稱', '樓主性別','發(fā)布日期',
'發(fā)布時間', '轉發(fā)量','評論量','點贊量', '評論者ID', '評論者昵稱',
'評論者性別', '評論日期', '評論時間','評論內容'))
#設置heades
headers = {
'Cookie': '_T_WM=22822641575; H5_wentry=H5; backURL=https%3A%2F%2Fm.weibo.cn%2F; ALF=1584226439; MLOGIN=1; SUBP=0033WrSXqPxfM725Ws9jqgMF55529P9D9W5RJaVYrb.BEuOvUQ8Ca2OO5JpX5K-hUgL.FoqESh-7eKzpShM2dJLoIp7LxKML1KBLBKnLxKqL1hnLBoMceoBfeh2EeKBN; SCF=AnRSOFp6QbWzfH1BqL4HB8my8eWNC5C33KhDq4Ko43RUIzs6rjJC49kIvz5_RcOJV2pVAQKvK2UbAd1Uh6j0pyo.; SUB=_2A25zQaQBDeRhGeBM71cR8SzNzzuIHXVQzcxJrDV6PUJbktAKLXD-kW1NRPYJXhsrLRnku_WvhsXi81eY0FM2oTtt; SUHB=0mxU9Kb_Ce6s6S; SSOLoginState=1581634641; WEIBOCN_FROM=1110106030; XSRF-TOKEN=dc7c27; M_WEIBOCN_PARAMS=oid%3D4471980021481431%26luicode%3D20000061%26lfid%3D4471980021481431%26uicode%3D20000061%26fid%3D4471980021481431',
'Referer': 'https://m.weibo.cn/detail/4312409864846621',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.100 Safari/537.36',
'X-Requested-With': 'XMLHttpRequest'
}
#-----------------------------------爬取戰(zhàn)疫情首頁的每個主題的ID------------------------------------------
comments_ID = []
def get_title_id():
for page in range(1,21): #每個頁面大約有18個話題
headers = {
'User-Agent' : UserAgent().chrome #chrome瀏覽器隨機代理
}
time.sleep(1)
#該鏈接通過抓包獲得
api_url = 'https://m.weibo.cn/api/feed/trendtop?containerid=102803_ctg1_600059_-_ctg1_600059&page=' + str(page)
print(api_url)
rep = requests.get(url=api_url, headers=headers)
#獲取ID值并寫入列表comment_ID中
for json in rep.json()['data']['statuses']:
comment_ID = json['id']
comments_ID.append(comment_ID)
#-----------------------------------爬取戰(zhàn)疫情每個主題的詳情頁面------------------------------------------
def spider_title(comment_ID):
try:
article_url = 'https://m.weibo.cn/detail/'+ comment_ID
print ('article_url = ', article_url)
html_text = requests.get(url=article_url, headers=headers).text
#話題內容
find_title = re.findall('.*?'text': '(.*?)',.*?', html_text)[0]
title_text = re.sub('<(S*?)[^>]*>.*?|<.*? />', '', find_title) #正則匹配掉html標簽
print ('title_text = ', title_text)
#樓主ID
title_user_id = re.findall('.*?'id': (.*?),.*?', html_text)[1]
print ('title_user_id = ', title_user_id)
#樓主昵稱
title_user_NicName = re.findall('.*?'screen_name': '(.*?)',.*?', html_text)[0]
print ('title_user_NicName = ', title_user_NicName)
#樓主性別
title_user_gender = re.findall('.*?'gender': '(.*?)',.*?', html_text)[0]
print ('title_user_gender = ', title_user_gender)
#發(fā)布時間
created_title_time = re.findall('.*?'created_at': '(.*?)'.*?', html_text)[0].split(' ')
#日期
if 'Mar' in created_title_time:
title_created_YMD = '{}/{}/{}'.format(created_title_time[-1], '03', created_title_time[2])
elif 'Feb' in created_title_time:
title_created_YMD = '{}/{}/{}'.format(created_title_time[-1], '02', created_title_time[2])
elif 'Jan' in created_title_time:
title_created_YMD = '{}/{}/{}'.format(created_title_time[-1], '01', created_title_time[2])
else:
print ('該時間不在疫情范圍內,估計數據有誤!URL = ')
pass
print ('title_created_YMD = ', title_created_YMD)
#發(fā)布時間
add_title_time = created_title_time[3]
print ('add_title_time = ', add_title_time)
#轉發(fā)量
reposts_count = re.findall('.*?'reposts_count': (.*?),.*?', html_text)[0]
print ('reposts_count = ', reposts_count)
#評論量
comments_count = re.findall('.*?'comments_count': (.*?),.*?', html_text)[0]
print ('comments_count = ', comments_count)
#點贊量
attitudes_count = re.findall('.*?'attitudes_count': (.*?),.*?', html_text)[0]
print ('attitudes_count = ', attitudes_count)
comment_count = int(int(comments_count) / 20) #每個ajax一次加載20條數據
position1 = (article_url, title_text, title_user_id, title_user_NicName,title_user_gender, title_created_YMD, add_title_time, reposts_count, comments_count, attitudes_count, ' ', ' ', ' ', ' ',' ', ' ')
#寫入數據
writer.writerow((position1))
return comment_count
except:
pass
#-------------------------------------------------抓取評論信息---------------------------------------------------
#comment_ID話題編號
def get_page(comment_ID, max_id, id_type):
params = {
'max_id': max_id,
'max_id_type': id_type
}
url = ' https://m.weibo.cn/comments/hotflow?id={}&mid={}&max_id'.format(comment_ID, comment_ID)
try:
r = requests.get(url, params=params, headers=headers)
if r.status_code == 200:
return r.json()
except requests.ConnectionError as e:
print('error', e.args)
pass
#-------------------------------------------------抓取評論item最大值---------------------------------------------------
def parse_page(jsondata):
if jsondata:
items = jsondata.get('data')
item_max_id = {}
item_max_id['max_id'] = items['max_id']
item_max_id['max_id_type'] = items['max_id_type']
return item_max_id
#-------------------------------------------------抓取評論信息---------------------------------------------------
def write_csv(jsondata):
for json in jsondata['data']['data']:
#用戶ID
user_id = json['user']['id']
# 用戶昵稱
user_name = json['user']['screen_name']
# 用戶性別,m表示男性,表示女性
user_gender = json['user']['gender']
#獲取評論
comments_text = json['text']
comment_text = re.sub('<(S*?)[^>]*>.*?|<.*? />', '', comments_text) #正則匹配掉html標簽
# 評論時間
created_times = json['created_at'].split(' ')
if 'Feb' in created_times:
created_YMD = '{}/{}/{}'.format(created_times[-1], '02', created_times[2])
elif 'Jan' in created_times:
created_YMD = '{}/{}/{}'.format(created_times[-1], '01', created_times[2])
else:
print ('該時間不在疫情范圍內,估計數據有誤!')
pass
created_time = created_times[3] #評論時間時分秒
#if len(comment_text) != 0:
position2 = (' ', ' ', ' ', ' ',' ', ' ', ' ', ' ', ' ', ' ', user_id, user_name, user_gender, created_YMD, created_time, comment_text)
writer.writerow((position2))#寫入數據
#print (user_id, user_name, user_gender, created_YMD, created_time)
#-------------------------------------------------主函數---------------------------------------------------
def main():
count_title = len(comments_ID)
for count, comment_ID in enumerate(comments_ID):
print ('正在爬取第%s個話題,一共找到個%s話題需要爬取'%(count+1, count_title))
#maxPage獲取返回的最大評論數量
maxPage = spider_title(comment_ID)
print ('maxPage = ', maxPage)
m_id = 0
id_type = 0
if maxPage != 0: #小于20條評論的不需要循環(huán)
try:
#用評論數量控制循環(huán)
for page in range(0, maxPage):
#自定義函數-抓取網頁評論信息
jsondata = get_page(comment_ID, m_id, id_type)
#自定義函數-寫入CSV文件
write_csv(jsondata)
#自定義函數-獲取評論item最大值
results = parse_page(jsondata)
time.sleep(1)
m_id = results['max_id']
id_type = results['max_id_type']
except:
pass
print ('--------------------------分隔符---------------------------')
csvfile.close()
if __name__ == '__main__':
#獲取話題ID
get_title_id()
#主函數操作
main()
#計算使用時間
endTime = time.time()
useTime = (endTime-startTime) / 60
print('該次所獲的信息一共使用%s分鐘'%useTime)
保存數據截圖如下圖所示:
下圖時抓取的話題頁面網址,每個頁面包括18個話題。
接著抓取每個話題的內容,如下所示:
正在爬取第1個話題,一共找到個361話題需要爬取
article_url = https://m.weibo.cn/detail/4484575189181757
title_text = 【#國家衛(wèi)健委回應健康碼互通互認#】國家衛(wèi)生健康委規(guī)劃司司長毛群安:目前全國低風險縣域已占98%,各省份正在按照統(tǒng)一的數據格式標準和內容要求,加快向全國一體化平臺匯聚本地區(qū)防疫健康信息的目錄。截至目前,#全國絕大多數健康碼可實現一碼通行#。微博視頻
title_user_id = 2803301701
title_user_NicName =
title_user_gender = m
該時間不在疫情范圍內,估計數據有誤!URL =
maxPage = None
--------------------------分隔符---------------------------
正在爬取第2個話題,一共找到個361話題需要爬取
article_url = https://m.weibo.cn/detail/4484288164243251
title_text = 法國網友自稱自己成了長發(fā)公主,度過了居家隔離后的第三天.....#全球疫情##法國疫情# 法國囧事的微博視頻
title_user_id = 2981906842
title_user_NicName = 法國囧事
title_user_gender = m
該時間不在疫情范圍內,估計數據有誤!URL =
maxPage = None
--------------------------分隔符---------------------------
正在爬取第3個話題,一共找到個361話題需要爬取
article_url = https://m.weibo.cn/detail/4484492666507389
title_text = #全球疫情# #意大利疫情# #意大利# “羅馬還有其他四處的藥店都遭到了搶劫。我們遭受到的是持械搶劫?!斑@是一位羅馬藥店藥劑師的陳述。她說,在當前疫情的危機情況下,我們處在兩難困境之中:受到搶劫和疾病的雙重威脅。疫情之下,意大利口罩告急,價格飆高。市民認為是藥店不賣,而真實情況是藥店真的沒有,而供貨商又抬高了價格。藥店處在兩難境地。這位藥劑師道出了自己的苦衷,冒著危險還在工作,與醫(yī)護人員一樣,都是奮斗在一線做出犧牲的人。呼吁民眾理解,也請求大家的幫助。Nita大呵呵的微博視頻
title_user_id = 6476189426
title_user_NicName = Nita大呵呵
title_user_gender = f
該時間不在疫情范圍內,估計數據有誤!URL =
maxPage = None
最終抓取360個疫情話題內容。
注意:該爬蟲評論寫入功能需要改進下,且只能抓取當天的“戰(zhàn)疫情”話題及評論,如果想針對某個突發(fā)事件進行一段時間的分析,建議每天定時運行該程序,從而形成所需的數據集。也可以根據需求修改為熱點話題的抓取,增加搜索功能等。
微博話題詞云分析
首先,我們對文本進行簡單的詞云可視化分析。
1.基本用法
詞云分析主要包括兩種方法:
調用WordCloud擴展包畫圖(兼容性極強,之前介紹過)
調用PyEcharts中的WordCloud子包畫圖(本文推薦新方法)
PyEcharts繪制詞云的基礎代碼如下:
# 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),
('狀態(tài)樹遍歷', 2244),
('剪枝', 1868),
('Gale-shapley', 1484),
('最大匹配與匈牙利算法', 1112),
('線索模型', 865),
('關鍵路徑算法', 847),
('最小二乘法曲線擬合', 582),
('二分逼近法', 555),
('牛頓迭代法', 550),
('Bresenham算法', 462),
('粒子群優(yōu)化', 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.疫情詞云
接著我們將3月20日疫情內容復制至“data.txt”文本,經過中文分詞后顯示前1000個高頻詞的詞云。代碼如下:
# coding=utf-8
import jieba
import re
import time
from collections import Counter
#------------------------------------中文分詞------------------------------------
cut_words = ''
all_words = ''
f = open('C-class-fenci.txt', 'w')
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)
# 詞頻統(tǒng)計
c = Counter()
for x in all_words:
if len(x)>1 and x != '\r\n':
c[x] += 1
# 輸出詞頻最高的前10個詞
print('\n詞頻統(tǒng)計結果:')
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')
輸出結果如下圖所示,僅3月20日的熱點話題內容。
3.WordCloud
另一種方法的代碼如下:
# coding=utf-8
import jieba
import re
import sys
import time
from collections import Counter
import matplotlib.pyplot as plt
from wordcloud import WordCloud
#------------------------------------中文分詞------------------------------------
cut_words = ''
all_words = ''
f = open('data-fenci.txt', 'w')
for line in open('data.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)
# 詞頻統(tǒng)計
c = Counter()
for x in all_words:
if len(x)>1 and x != '\r\n':
c[x] += 1
# 輸出詞頻最高的前10個詞
print('\n詞頻統(tǒng)計結果:')
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()
#------------------------------------詞云分析------------------------------------
#打開本體TXT文件
text = open('data.txt').read()
#結巴分詞 cut_all=True 設置為精準模式
wordlist = jieba.cut(text, cut_all = False)
#使用空格連接 進行中文分詞
wl_space_split = ' '.join(wordlist)
#print(wl_space_split)
#對分詞后的文本生成詞云
my_wordcloud = WordCloud().generate(wl_space_split)
#顯示詞云圖
plt.imshow(my_wordcloud)
#是否顯示x軸、y軸下標
plt.axis('off')
plt.show()
SnowNLP情感分析用法
情感分析的基本流程如下圖所示,通常包括:
自定義爬蟲抓取文本信息;
使用Jieba工具進行中文分詞、詞性標注;
定義情感詞典提取每行文本的情感詞;
通過情感詞構建情感矩陣,并計算情感分數;
結果評估,包括將情感分數置于0.5到-0.5之間,并可視化顯示。
1.SnowNLP
SnowNLP是一個常用的Python文本分析庫,是受到TextBlob啟發(fā)而發(fā)明的。由于當前自然語言處理庫基本都是針對英文的,而中文沒有空格分割特征詞,Python做中文文本挖掘較難,后續(xù)開發(fā)了一些針對中文處理的庫,例如SnowNLP、Jieba、BosonNLP等。注意SnowNLP處理的是unicode編碼,所以使用時請自行decode成unicode。
Snownlp主要功能包括:
中文分詞(算法是Character-Based Generative Model)
詞性標注(原理是TnT、3-gram 隱馬)
情感分析
文本分類(原理是樸素貝葉斯)
轉換拼音、繁體轉簡體
提取文本關鍵詞(原理是TextRank)
提取摘要(原理是TextRank)、分割句子
文本相似(原理是BM25)
推薦官網給大家學習。
安裝和其他庫一樣,使用pip安裝即可。
pip install snownlp
2.中文分詞
下面是最簡單的實例,使用SnowNLP進行中文分詞,同時比較了SnowNLP和Jieba庫的分詞效果。
# -*- coding: utf-8 -*-
from snownlp import SnowNLP
s1 = SnowNLP(u'這本書質量真不太好!')
print('SnowNLP:')
print(' '.join(s1.words))
import jieba
s2 = jieba.cut(u'這本書質量真不太好!', cut_all=False)
print('jieba:')
print(' '.join(s2))
輸出結果如下所示:
總體感覺是SnowNLP分詞速度比較慢,準確度較低,比如“不太好”這個詞組,但也不影響我們后續(xù)的情感分析。
3.常見功能
代碼如下:
# -*- coding: utf-8 -*-
from snownlp import SnowNLP
s = SnowNLP(u'這本書質量真不太好!')
print(u'\n中文分詞:')
print( ' '.join(s.words))
print(u'\n詞性標注:')
print(s.tags)
for k in s.tags:
print(k)
print(u'\n情感分數:')
print(s.sentiments)
print(u'\n轉換拼音:')
print(s.pinyin)
print(u'\n輸出前4個關鍵詞:')
print(s.keywords(4))
for k in s.keywords(4):
print(k)
print(u'\n輸出關鍵句子:')
print(s.summary(1))
for k in s.summary(1):
print(k)
print(u'\n輸出tf和idf:')
print(s.tf)
print(s.idf)
n = SnowNLP(u'「繁體字」「繁體中文」的叫法在臺灣亦很常見。')
print(u'\n繁簡體轉換:')
print(n.han)
s.words 輸出分詞后的結果,詞性標注主要通過 s.tags,s.sentiments 計算情感分數,s.pinyin 轉換為拼音,s.keywords(4) 提取4個關鍵詞,s.summary(1) 輸出一個關鍵句子,s.tf 計算TF值(頻率),s.idf 計算IDF值(倒文檔)。
輸出結果如下所示:
>>>
中文分詞:
這 本書 質量 真 不 太 好 !
詞性標注:
[(u'\u8fd9', u'r'), (u'\u672c\u4e66', u'r'), (u'\u8d28\u91cf', u'n'),
(u'\u771f', u'd'), (u'\u4e0d', u'd'), (u'\u592a', u'd'),
(u'\u597d', u'a'), (u'\uff01', u'w')]
(u'\u8fd9', u'r')
(u'\u672c\u4e66', u'r')
(u'\u8d28\u91cf', u'n')
(u'\u771f', u'd')
(u'\u4e0d', u'd')
(u'\u592a', u'd')
(u'\u597d', u'a')
(u'\uff01', u'w')
情感分數:
0.420002029202
轉換拼音:
[u'zhe', u'ben', u'shu', u'zhi', u'liang', u'zhen', u'bu', u'tai', u'hao', u'\uff01']
輸出前4個關鍵詞:
[u'\u592a', u'\u4e0d', u'\u8d28\u91cf', u'\u771f']
太
不
質量
真
輸出關鍵句子:
[u'\u8fd9\u672c\u4e66\u8d28\u91cf\u771f\u4e0d\u592a\u597d']
這本書質量真不太好
輸出tf和idf:
[{u'\u8fd9': 1}, {u'\u672c': 1}, {u'\u4e66': 1},
{u'\u8d28': 1}, {u'\u91cf': 1}, {u'\u771f': 1},
{u'\u4e0d': 1}, {u'\u592a': 1}, {u'\u597d': 1}, {u'\uff01': 1}]
{u'\uff01': 1.845826690498331, u'\u4e66': 1.845826690498331, u'\u8d28': 1.845826690498331,
u'\u592a': 1.845826690498331, u'\u4e0d': 1.845826690498331, u'\u672c': 1.845826690498331,
u'\u91cf': 1.845826690498331, u'\u8fd9': 1.845826690498331, u'\u597d': 1.845826690498331, u'\u771f': 1.845826690498331}
繁簡體轉換:
「繁體字」「繁體中文」的叫法在臺灣亦很常見。
>>>
同樣可以進行文本相似度計算,代碼參考下圖所示:
4.情感分析
SnowNLP情感分析也是基于情感詞典實現的,其簡單的將文本分為兩類,積極和消極,返回值為情緒的概率,越接近1為積極,接近0為消極。
下面簡單給出一個情感分析的例子:
# -*- coding: utf-8 -*-
from snownlp import SnowNLP
s1 = SnowNLP(u'我今天很開心')
print(u's1情感分數:')
print(s1.sentiments)
s2 = SnowNLP(u'我今天很沮喪')
print(u's2情感分數:')
print(s2.sentiments)
s3 = SnowNLP(u'大傻瓜,你脾氣真差,動不動就打人')
print(u's3情感分數:')
print(s3.sentiments)
輸出結果如下所示,當負面情感特征詞越多,比如“傻瓜”、“差”、“打人”等,分數就會很低,同樣當正免情感詞多分數就高。
s1情感分數:
0.84204018979
s2情感分數:
0.648537121839
s3情感分數:
0.0533215596706
而在真實項目中,通常需要根據實際的數據重新訓練情感分析的模型,導入正面樣本和負面樣本,再訓練新模型。
sentiment.train(’./neg.txt’, ‘./pos.txt’)
sentiment.save(‘sentiment.marshal’)
SnowNLP微博情感分析實例
下面的代碼是對爬取的疫情話題進行情感分析。本文將抓取的356條(其中4條僅圖片)微博疫情話題信息復制至TXT文件中 ,每一行為一條話題,再對其進行中文分詞處理。注意,這里僅僅獲取序號1-356的情感分數,而其他情感分析可以進行時間對比、主題對比等,其方法和此篇文章類似,希望讀者學會舉一反三。
1.情感各分數段出現頻率
首先統(tǒng)計各情感分數段出現的評率并繪制對應的柱狀圖,代碼如下:
# -*- coding: utf-8 -*-
from snownlp import SnowNLP
import codecs
import os
source = open('data.txt','r', encoding='utf-8')
line = source.readlines()
sentimentslist = []
for i in line:
s = SnowNLP(i)
print(s.sentiments)
sentimentslist.append(s.sentiments)
import matplotlib.pyplot as plt
import numpy as np
plt.hist(sentimentslist, bins = np.arange(0, 1, 0.01), facecolor = 'g')
plt.xlabel('Sentiments Probability')
plt.ylabel('Quantity')
plt.title('Analysis of Sentiments')
plt.show()
輸出結果如下圖所示,可以看到
對應的分數如下:
>>>
4.440892098500626e-16
0.49055395607520824
0.9999999999972635
0.9999998677093149
0.9979627586368516
0.9999999990959509
0.9999830199233769
0.9998699310812647
0.9999954477924106
...
2.情感波動分析
接下來分析每條評論的波動情況,代碼如下所示:
# -*- coding: utf-8 -*-
from snownlp import SnowNLP
import codecs
import os
source = open('data.txt','r', encoding='utf-8')
line = source.readlines()
sentimentslist = []
for i in line:
s = SnowNLP(i)
print(s.sentiments)
sentimentslist.append(s.sentiments)
import matplotlib.pyplot as plt
import numpy as np
plt.plot(np.arange(0, 356, 1), sentimentslist, 'k-')
plt.xlabel('Number')
plt.ylabel('Sentiment')
plt.title('Analysis of Sentiments')
plt.show()
輸出結果如下所示,呈現一條曲線,因為抓取的評論基本都是好評,所以分數基本接近于1.0,而真實分析過程中存在好評、中評和差評,曲線更加規(guī)律。
同時,在做情感分析的時候,我看到很多論文都是將情感區(qū)間從[0, 1.0]轉換為[-0.5, 0.5],這樣的曲線更加好看,位于0以上的是積極評論,反之消極評論。修改代碼如下:
# -*- coding: utf-8 -*-
from snownlp import SnowNLP
import codecs
import os
#獲取情感分數
source = open('data.txt','r', encoding='utf-8')
line = source.readlines()
sentimentslist = []
for i in line:
s = SnowNLP(i)
print(s.sentiments)
sentimentslist.append(s.sentiments)
#區(qū)間轉換為[-0.5, 0.5]
result = []
i = 0
while i<len(sentimentslist):
result.append(sentimentslist[i]-0.5)
i = i + 1
#可視化畫圖
import matplotlib.pyplot as plt
import numpy as np
plt.plot(np.arange(0, 356, 1), result, 'k-')
plt.xlabel('Number')
plt.ylabel('Sentiment')
plt.title('Analysis of Sentiments')
plt.show()
繪制圖形如下所示:
3.情感時間分布
最后補充隨時間分布的情感分數相關建議,讀者可能也發(fā)現抓取的博客存在重復、時間不均衡等現象。微博數據還是非常不好抓取,數據卡住了很多人,也請讀者深入分析下。
(1) 情感分析通常需要和評論時間結合起來,并進行輿情預測等,建議讀者嘗試將時間結合。比如王樹義老師的文章《基于情感分類的競爭企業(yè)新聞文本主題挖掘》。
(2) 情感分析也是可以進行評價的,我們前面抓取的分為5星評分,假設0-0.2位一星,0.2-0.4位二星,0.4-0.6為三星,0.6-0.8為四星,0.8-1.0為五星,這樣我們可以計算它的準確率,召回率,F值,從而評論我的算法好壞。
(3) 作者還有很多情感分析結合冪率分布的知識,因為需要寫文章,這里暫時不進行分享,但是這篇基礎文章對初學者仍然有一定的幫助。
(4) BosonNLP也是一個比較不錯的情感分析包,建議感興趣的讀者學習,它提供了相關的詞典,如下:https://bosonnlp.com/dev/resource。
(5) 讀者如果不太擅長寫代碼,可以嘗試使用情感分析系統(tǒng)。http://ictclas.nlpir.org/nlpir/
原文鏈接:
https://blog.csdn.net/Eastmount/article/details/104995419
【END】