自然語言處理:wordcloud+snownlp《西虹市首富》影評情感分析
前言
最近看了沈騰主演的電影《西虹市首富》,心想怎麼沒有十個億砸我頭上,我保證比王多魚還敗家,但是細細一想,要是真的砸腦袋上,估計就隨給王多魚他二爺去了。
閒話少說,言歸正傳,電影上映一段時間,王多魚花光的十個億早就又賺回來了,不過這個不是咱們關注的重點,今天咱們就來看看使用者對這部電影的評價,並且藉助機器學習來簡單分析下,看看這裡有什麼好玩的東西。
分析工具:
分析之前先介紹下資料來源和工具:
資料來源:豆瓣影評(471條《西虹市首富》評論資料)
訓練資料:好評資料(豆瓣四星以上評論28000+),差評資料(豆瓣兩星以下資料13000+),訓練資料不多,三星的評論資料暫時沒用,為了爬取這些資料可費了不少事(水平有限),賬號和IP被豆瓣封禁多次,不過可以理解,尊重人家的勞動成果。
語言:python
分詞工具:jieba分詞
詞雲:wordcloud
情感分析:snownlp
正文
先看下目前豆瓣上對這部電影的評價:
整體評分不怎麼高,看看實際的評論內容,確實是有不少批評。
要想看看觀眾的評論裡都有什麼內容,這裡用wordcloud製作了一個詞語圖(這裡只採用了詞頻為2以上的詞語並且去掉了單個字,後面會貼出程式碼),在這裡可以看到一些關鍵點:
最明顯的詞就是麻花和喜劇了,畢竟是開心麻花團隊的喜劇作品,其中有一個詞也比較明顯,夏洛特,看來觀眾喜歡把《首富》和《夏洛特》放在一起對比,後者可是沈騰的代表作,多數使用者也認為認為《夏洛特》更好看。
觀眾雖然對該電影給出了星級評價,但是評價裡的感情色彩是怎樣的,下面咱就看看通過情感分析你能看到什麼。
先看看觀眾的評分情況:
5星:10.8%
4星:32.0%
3星:41.3%
2星:11.9%
1星:3.9%
在進行情感分析時,訓練資料採用的是豆瓣影評資料,其中好評資料(豆瓣四星以上評論)28000+條,差評資料(豆瓣兩星以下資料)13000+條,三星的評論資料暫時沒用,所以對結果還是有影響,這裡我們就是簡單分析,不做過多地細節處理。並且這裡我們只把評論分為兩類:好評、差評。
來看看情感分析的看結果:
(注:數值越接近0表明情感上評價越低,越接近1情感上評價越高)
好評:207條(43.9%)
差評:264條(56.1%)
從資料分佈圖中看,竟然有一百多觀眾對這部電影是如此的不滿,
看來觀眾給出的星級評分還是有所保守,也並那麼可靠。多數觀眾在情感上表現的十分不滿,畢竟語言是真實的。
對這部電影來說,觀眾言辭犀利的批評也許是件好事,幫助國產電影更進一步,而不是隻為圈錢而生。
這部電影影評簡單分析完了,分析的內容也許不準確,但是比單看評分有趣多了。下面就把程式碼一起分享出來,程式碼分為兩部分,一部分是爬蟲程式碼,一部分是製作圖雲和情感分析,程式碼比較簡單,也有註釋,就不在詳解了。訓練資料如有需要在評論區留下郵箱,看到定會分享。
以上內容屬平時練習所做,記錄學習過程,方法簡單,有很多不足,歡迎大佬指正,以後分享出更有水平的文章。
爬蟲程式碼
爬蟲程式碼根據自己需求寫的,若有需要自行修改
# -*-coding: utf-8 -*-
'''
Created on 2018-8-9
@author: xubaifu
'''
from urllib import request
from bs4 import BeautifulSoup #Beautiful Soup是一個可以從HTML或XML檔案中提取結構化資料的Python庫
from sqlalchemy.sql.expression import except_
import requests
import re
import random
import time
import unicodedata
import emoji
import sys
#使用session來儲存登陸資訊
s = requests.session()
#獲取動態ip,防止ip被封
def get_ip_list(url, headers):
web_data = requests.get(url, headers=headers)
soup = BeautifulSoup(web_data.text, 'lxml')
ips = soup.find_all('tr')
ip_list = []
for i in range(1, len(ips)):
ip_info = ips[i]
tds = ip_info.find_all('td')
ip_list.append(tds[1].text + ':' + tds[2].text)
return ip_list
#隨機從動態ip連結串列中選擇一條ip
def get_random_ip(ip_list):
proxy_list = []
for ip in ip_list:
proxy_list.append('http://' + ip)
proxy_ip = random.choice(proxy_list)
proxies = {'http': proxy_ip}
print(proxies)
return proxies
#實現模擬登陸
def Login(headers,loginUrl,formData):
r = s.post(loginUrl, data=formData, headers=headers)
print( r.url)
print( formData["redir"])
if r.url == formData["redir"]:
print( "登陸成功")
else:
print( "第一次登陸失敗")
page = r.text
soup = BeautifulSoup(page, "html.parser")
captchaAddr = soup.find('img', id='captcha_image')['src']
print( captchaAddr)
reCaptchaID = r'<input type="hidden" name="captcha-id" value="(.*?)"/'
captchaID = re.findall(reCaptchaID, page)
# captcha = raw_input('輸入驗證碼:')
captcha = input('輸入驗證碼:')
formData['captcha-solution'] = captcha
formData['captcha-id'] = captchaID
r = s.post(loginUrl, data=formData, headers=headers)
print (r.status_code)
return r.cookies
#獲取評論內容和下一頁連結
def get_data(html):
soup = BeautifulSoup(html,"lxml")
comment_list = soup.select('.comment > p')
next_page = soup.select('.next')[0].get('href')
return comment_list,next_page
if __name__ =="__main__":
absolute = 'https://movie.douban.com/subject/27605698/comments'
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36'}
loginUrl = 'https://www.douban.com/accounts/login?source=movie'
formData = {
"redir":"https://movie.douban.com/subject/27605698/comments?start=0&limit=20&sort=new_score&status=P",
"form_email":"**********",
"form_password":"**********",
"login":u'登入'
}
#獲取動態ip
url = 'http://www.xicidaili.com/nn/'
cookies = Login(headers,loginUrl,formData)
ip_list = get_ip_list(url, headers=headers)
proxies = get_random_ip(ip_list)
current_page = absolute
next_page = ""
comment_list = []
temp_list = []
num = 0
with open('movie.txt','r', encoding='UTF-8') as fileId:
for movie_id in fileId:
movie_id = movie_id.strip()
#根據豆瓣網的地址連結規則生成start值
page_num = [20 * (x - 1) for x in range(1,11)]#預設值抓取前二十頁評論(質量較高)
for start in page_num:
request_url = 'https://movie.douban.com/subject/%s/comments?start=%d&limit=20&sort=new_score&status=P&percent_type=h' %(movie_id, start)
print(request_url)
html = s.get(request_url, cookies=cookies, headers=headers, proxies=proxies).content
#temp_list,next_page = get_data(html)
soup = BeautifulSoup(html,"lxml")
comment_list = soup.select('.comment > p')
#next_page = soup.select('.next')[0].get('href')
comment_list = comment_list + temp_list
time.sleep(float(random.randint(1, 10)) / 10)
num = num + 1
#每20次更新一次ip
if num % 20 == 0:
proxies = get_random_ip(ip_list)
with open("C:\\Users\\xubaifu\\Desktop\\h.txt", 'a')as f:
for node in comment_list:
comment = node.get_text().strip().replace("\n", "")
# 中文標點轉英文標點
# comment = unicodedata.normalize('NFKC', comment)
# 去除emoji表情
emoji.demojize(comment)
try:
f.write(comment + "\n")
except:
print("寫入檔案出錯")
f.close()
fileId.close()
詞雲與情感分析
詞雲和情感分析皆使用的第三方庫,原理需要自己理解。由於snownlp 在做情感分析的時候使用的是網購商品評論資料,我這裡使用的豆瓣影評資料,需要自己訓練。
# -*-coding: utf-8 -*-
'''
Created on 2018-8-9
@author: xubaifu
'''
import codecs
import jieba.posseg as pseg
from scipy.misc import imread
from wordcloud import ImageColorGenerator
# from os import path
import matplotlib.pyplot as plt
from wordcloud import WordCloud
import numpy as np
from snownlp import SnowNLP
from snownlp import sentiment
# 構建停用詞表
stop_words = 'stopWords.txt'
stopwords = codecs.open(stop_words,'r',encoding='utf8').readlines()
stopwords = [ w.strip() for w in stopwords ]
# 結巴分詞後的停用詞性 [標點符號、連詞、助詞、副詞、介詞、時語素、‘的’、數詞、方位詞、代詞]
stop_flag = ['x', 'c', 'u','d', 'p', 't', 'uj', 'm', 'f', 'r', 'ul']
class File_Review:
#自定義停用詞
def load_stopwords(self, path='stopwords.txt'): #檔案必須utf-8編碼.encode('utf-8')
#載入停用詞
with open(path,'r', encoding='UTF-8') as f:
stopwords = filter(lambda x: x, map(lambda x: x.strip(), f.readlines()))
# stopwords.extend([' ', '\t', '\n'])
return frozenset(stopwords)
# 對一篇文章分詞、去停用詞
def cut_words(self, filename):
result = []
with open(filename, 'r', encoding='UTF-8') as f:
text = f.read()
words = pseg.cut(text)
for word, flag in words:
if flag not in stop_flag and word not in stopwords and len(word) > 1:
result.append(word)
return result #返回陣列
# return ' '.join(result) #返回字串
#統計詞頻
def all_list(self, arr):
result = {}
for i in set(arr):
result[i] = arr.count(i)
return result
# 構建詞雲圖
def draw_wordcloud(self, txt):
#讀入背景圖片
#bj_pic=imread('C:\\Users\\xubaifu\\Desktop\\dcd0005479.jpg')
wc1 = WordCloud(
#mask=bj_pic,
background_color="white",
width=1000,
height=860,
font_path="C:\\Windows\\Fonts\\STFANGSO.ttf",#不加這一句顯示口字形亂碼
margin=2)
# wc2 = wc1.generate(txt) #我們觀察到generate()接受一個Unicode的物件,所以之前要把文字處理成unicode型別
wc2 = wc1.fit_words(txt) # 根據詞頻生成詞雲
#image_colors=ImageColorGenerator(bj_pic)
plt.imshow(wc2)
plt.axis("off")
plt.show()
# 情感分析
def sentiments_analyze(self):
f = open('articles.txt', 'r', encoding='UTF-8')
connects = f.readlines()
sentimentslist = []
#使用SnowNLP的sentiment模組訓練資料
#sentiment.train('l.txt', 'g.txt')
#儲存模型
#sentiment.save('sentiment.marshal')
#載入模型
sentiment.load('sentiment.marshal')
sum = 0
for i in connects:
s = SnowNLP(i)
# print s.sentiments
sentimentslist.append(s.sentiments)
if s.sentiments > 0.5:
sum+= 1
print(sum)
print(len(sentimentslist))
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()
if __name__ == '__main__':
file_review = File_Review()
result = file_review.cut_words('articles.txt')
# 統計詞頻
word_count = file_review.all_list(result)
# 篩選出詞頻大於2的資料
word_count={k:v for k,v in word_count.items() if v>=2}
# 製作詞雲
file_review.draw_wordcloud(word_count)
#情感分析
file_review.sentiments_analyze()