1. 程式人生 > >自然語言處理:wordcloud+snownlp《西虹市首富》影評情感分析

自然語言處理: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()