Amazonレビューをセンチメント分析(感情分析)する【Python】

Amazonレビューをセンチメント分析(感情分析)する【Python】 機械学習

Amazonレビューは、何かと話題になります。
今なら、Amazonレビューにはサクラが多くて胡散臭いという話題でしょうか?

Amazonレビューは、以前は本当に役に立ちました。
参考にして買い物をすることも多くありました。
しかし、今はレビューを参考にすることが、めっきりと減りました。

そもそも、Amazonでの買い物をする機会が減りました。
その代わりに、安全なイメージのヨドバシで買い物をしています。
レビュー以外にも、悪質な業者の問題がAmazonには存在していますからね。

そのイメージの悪いAmazonレビューに関して、この記事で取り上げます。
どう取り上げるかと言うと、センチメント分析(感情分析)の対象とします。

本記事の内容

  • Amazonレビューをセンチメント分析(感情分析)する目的・意図
  • Amazonレビューを取得(データ収集)する
  • Amazonレビューをセンチメント分析(感情分析)する
  • Amazonレビューのセンチメント分析結果
  • センチメント分析(感情分析)は使えない!?

それでは、上記に沿って解説していきます。

Amazonレビューをセンチメント分析(感情分析)する目的・意図

Amazonレビューには、多くの情報が集まっています。
まず、その投稿の量が圧倒的です。

確かに、サクラも多いのでしょう。
でも、サクラ以外の真っ当なレビューも多く投稿されています。

その投稿数が、他サイトとは比較にはなりません。
ヨドバシでは、レビューはほとんど投稿されないようです。
その意味でも、Amazonのレビューには価値があります。

また、コメントと評価(星1~5)がセットになっているのもポイントです。
このようにセットになっている点が、データ分析の際には役に立ちます。

以上より、Amazonレビューにはデータ量と内容(コメントと評価)が備わっていると言えます。
そして、何よりもネガポジ分析を実際のデータでやりたかったというのが大きいです。

自分にも身近なデータで分析する方が、モチベーションも沸きますからね。

Amazonレビューを取得(データ収集)する

データ分析の最初の関門となります。
どうやってデータを大量に集めるのか?ということですね。

これに関しては、全く問題ありません。
本ブログでは、多くのサイトをスクレイピングした結果を記事にしています。

その中でも、次の記事は今回の作業にビンゴです。

上の記事の内容を若干修正して、データを集めました。
改良したコードは以下。

import bs4           
import time
import re
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import pandas as pd

CHROME_DRIVER = "chromedriver.exeのパス"
FILE_PATH = "./amazon_data/review.json"

# Amazonページ取得
def get_page_from_amazon(url):
    
    text = ""
    # ヘッドレスモードでブラウザを起動
    options = Options()
    options.add_argument('--headless')
    
    # ブラウザーを起動
    driver = webdriver.Chrome(CHROME_DRIVER, options=options)
    driver.get(url)
    driver.implicitly_wait(10)  # 見つからないときは、10秒まで待つ
    
    text = driver.page_source
        
    # ブラウザ停止
    driver.quit()
    
    return text

# 全ページ分をリストにする
def get_all_reviews(url):
    rvw_list = []
    i = 1
    while True:
        print(i,'searching')
        i += 1
        text = get_page_from_amazon(url)
        amazon_soup = bs4.BeautifulSoup(text, features='lxml')

        for ratings in amazon_soup.find_all("div", attrs={"data-hook": "review"}):     
            submission_date = ratings.find("span", {'data-hook':'review-date'}).text
            rating = ratings.find('i', attrs={"data-hook": "review-star-rating"}).text
            review_txt = ratings.find("span", {'data-hook':'review-body'}).text
            #paid = ratings.find("span", attrs={"class": "a-color-success a-text-bold"})
            print(submission_date + "@" + rating + "@" + review_txt)
            print('\n')
            
            if submission_date:
                
                rating = rating.replace("5つ星のうち", "")
                submission_date = extract_date(submission_date)
                
                info = {}
                info["postdate"] = submission_date
                info["rating"] = rating
                info["review_txt"] = review_txt
            
                rvw_list.append(info)
        # 次へボタン
        next_page = amazon_soup.select('li.a-last a')   

        if next_page != []:    
            next_url = 'https://www.amazon.co.jp/' + next_page[0].attrs['href']    
            url = next_url
            # 最低でも1秒は間隔をあける
            time.sleep(1)       
        else:
            break

    return rvw_list

def extract_date(s):
    date_pattern = re.compile('(\d{4})年(\d{1,2})月(\d{1,2})日')
    result = date_pattern.search(s)
    if result:
        y, m, d = result.groups()
        
        return str(y) + str(m.zfill(2)) + str(d.zfill(2))
    else:
        return None
    
if __name__ == '__main__':
    
    # Amzon商品ページ
    url = 'https://www.amazon.co.jp/dp/B081CR9LQ1?pf_rd_r=9G7DP9DBQV9R5WWBA7GB&pf_rd_p=cf3dd3d4-7c29-4fa2-a9b7-8e9f95c1f37f&pd_rd_r=56934866-4323-49ea-ae8e-289260468100&pd_rd_w=bIWmn&pd_rd_wg=gA4B0&ref_=pd_gw_ci_mcx_mr_hp_d'
   
    # URLをレビューページのものに書き換える
    new_url = url.replace('dp', 'product-reviews')
    
    # レビューの取得
    rvw_list = get_all_reviews(new_url)    
    
    # データフレームに変換
    df = pd.DataFrame(rvw_list)
    
    # jsonとして保存
    df.to_json(FILE_PATH, orient='records')

内容に不明な点があれば、過去記事を参考にしてください。
詳細を説明しています。

このサンプルコードを動かすと、該当するAmazonの商品ページのレビューを収集します。
その結果は、amazon_dataフォルダ内のreview.jsonに保存されます。

  • 投稿日
  • 評価
  • コメント

項目的には、上記の3つとなります。

Amazonレビューをセンチメント分析(感情分析)する

センチメント分析(感情分析)は、難しそう。。。
このように思うかもしれません。

ただ、分析するだけなら簡単にできます。
やり方は以下。

  • 単語感情極性対応表(ネガポジ辞書)を取得する
  • 分析対象の文章を単語に分割する(形態素解析)
  • ネガポジ辞書と単語を比較する(否定・肯定の判定)

では、上記を説明していきましょう。

単語感情極性対応表(ネガポジ辞書)を取得する

以下のページにアクセスします。
「日本語」のリンク先が辞書となります。
http://www.lr.pi.titech.ac.jp/~takamura/pndic_ja.html

優れる:すぐれる:動詞:1
良い:よい:形容詞:0.999995
喜ぶ:よろこぶ:動詞:0.999979
褒める:ほめる:動詞:0.999979
めでたい:めでたい:形容詞:0.999645
~
厄難:やくなん:名詞:-0.765276
紡ぐ:つむぐ:動詞:-0.765358
自製:じせい:名詞:-0.765388
死亡:しぼう:名詞:-0.765674
主義:しゅぎ:名詞:-0.765714

上記がその内容です。
単語毎に値が設定されています。
その値の範囲は、-1から1までとなります。

1に近いほど、肯定的なワードです。
-1に近いほど、否定的なワードです。

分析対象の文章を単語に分割する(形態素解析)

文章を形態素解析します。
形態素解析に関しては、次の記事で詳しく説明しています。

プログラム的には、複雑なことはしません。
専用のソフトを用いて、簡単に行うことができます。
今回は、MecabをPythonから用いて形態素解析を行っています。

ネガポジ辞書と単語を比較する(否定・肯定の判定)

ここまで説明してきたら、わかると思います。
形態素解析によって、文章毎(レビューのコメント)に単語が求められます。

そして、その単語が単語感情極性対応表(ネガポジ辞書)にあるかどうかを調べます。
あった場合は、何点(-1~1の間の数値)なのかを求めます。
複数の単語がマッチする場合は、その平均を用います。

Amazonレビューのセンチメント分析結果

約9000件のレビューを分析しました。
上記のデータ収集プログラムで集めたデータです。

以下の3つの結果を確認しましょう

  • ヒストグラム
  • 散布図
  • 結果一覧

では、それぞれを確認します。

ヒストグラム

Amazonレビューをセンチメント分析した結果・ヒストグラム

基本的には、ネガティブの結果が多いです。
9割以上が、0以下という結果ですね。

ヒストグラムに関しては、次の記事が参考になります。
簡単にヒストグラムの図を作成できます。

レビューのコメントには、ネガティブな結果が多いことはわかりました。
では、その結果と評価(星1~5)との関係が気になります。

その二つの値の関係性を見るには、散布図が約に立ちます。
以下で、それを確認しましょう。

散布図

Amazonレビューをセンチメント分析した結果・散布図

横(x軸)が、ネガポジ(PN)結果の値です。
縦(y軸)が、Amazonレビューにおける評価です。

この結果を見ると、次のことが言えます。

  • 低評価(1と2)にはネガティブなコメントしかない
  • 高評価にもネガティブなコメントが多い
  • 基本的にはネガティブなコメントが多い

結果一覧

結果を直接確認します。

まずは、ポジティブなコメントから。
つまり、値が1に近いモノということです。

Amazonレビューをセンチメント分析した結果・結果一覧ポジティブ

pn:ネガポジ分析の値
rating:Amazonレビューにおける評価(星)
review_text:Amazonレビューにおけるコメント

確かに、肯定的なコメントが並びます。
あと、コメントが短いというのが目立ちますね。

次は、ネガティブなコメントを確認しましょう。
pnが-1に近いモノということです。

Amazonレビューをセンチメント分析した結果・結果一覧ネガティブ

どうもオカシイ。。。
まあ、これが単語感情極性対応表(ネガポジ辞書)を単純に利用した場合の限界ですね。

「言うことなし」の「なし」が、ネガティブ判定されているのです。
「部屋の汚れが少なくなって来ました」では、「汚れ」や「少なく」がネガティブとされているのでしょう。

文脈に沿った分析を行えていないという証拠になります。
単純に、単語で判定するとこのような結果になってしまうのでしょう。

センチメント分析(感情分析)は使えない!?

センチメント分析(感情分析)を行ってきました。
正直、微妙ですね。

散布図を見る限りでは、全く見当違いというわけでもないようです。
そのため、「センチメント分析(感情分析)は使えない」と断定するのは時期早々でしょう。

これは、あくまでセンチメント分析(感情分析)のスタートです。
ここからネガポジ判定の精度をどうやって上げていくのか?

単語感情極性対応表(ネガポジ辞書)にもっと情報を増やすのか?
それとも、文脈を考慮した分析方法があるのか?

センチメント分析(感情分析)における課題ですね。
それでは、最後にセンチメント分析(感情分析)を行ったサンプルコードを載せておきます。

import pandas as pd
import json
import MeCab
import re
import matplotlib.pyplot as plt
import numpy as np

FILE_PATH = "./amazon_data/review.json"
DIC_PATH = "-d ipadic-neologdのパス" # 辞書ipadic-neologdがあれば、そのパスを記述

def get_dic_list(text):
    
    mecab = MeCab.Tagger(DIC_PATH)
    parsed = mecab.parse(text)      
    lines = parsed.split('\n') 
    lines = lines[0:-2]     
    
    dic_list = []
    for line in lines:
        tmp = re.split('\t|,', line)
        dic = {'dsp':tmp[0], 'kind_1':tmp[1], 'kind_2':tmp[2], 'value':tmp[7]}
        dic_list.append(dic)
        
    return dic_list

# PN判定
def judge_pn(pn_dict, dic_list):
    
    all_pn = 0
    count = 0
    result = ""
    
    for word in dic_list:
        base = word['value']        
        if base in pn_dict:
            count = count + 1
            pn = float(pn_dict[base])  
            all_pn = all_pn + pn

    if all_pn != 0:
        result = all_pn / count
    
    return result  

# Amazonレビューの読み込み

json_open = open(FILE_PATH, 'r')
review_list = json.load(json_open)


# ネガポジ辞書の読み込み
pn_df = pd.read_csv('dic/pn_ja.dic.txt',\
                    sep=':',
                    encoding='utf-8',
                    names=('Word','Reading','POS', 'PN')
                   )

# ネガポジ辞書をデータフレームからdict型に変換
word_list = list(pn_df['Word'])
pn_list = list(pn_df['PN'])  
pn_dict = dict(zip(word_list, pn_list))

new_review_list = []
for review in review_list:
    
    review_txt = review["review_txt"]
    rating = review["rating"]
    postdate = review["postdate"]
    dic_list = get_dic_list(review_txt)
    pn = judge_pn(pn_dict, dic_list)
    
    review["pn"] = pn
    
    if pn:
        new_review_list.append(review)
    
new_review_list_df = pd.DataFrame(new_review_list)

# 欠損値が一つでも含まれる行を削除
new_review_list_df = new_review_list_df.dropna(how='any')
x = new_review_list_df["pn"].astype(float)
y = new_review_list_df["rating"].astype(float)

fig = plt.figure(dpi=100, figsize=(4.6,3.8))
#fig.suptitle('評価:PN', fontsize=10, x=0.27, y=0.87)
ax = fig.add_subplot(111)
ax.yaxis.set_label_coords(-0.07,0.4)

# 目盛の値
xscale = np.arange(-1.0, 1.1, 0.2)
yscale = np.arange(0, 6, 1.0)

# 散布図を描画
plt.scatter(x, y, c='#00FFFF', edgecolors="#37BAF2")
plt.title("評価・PN散布図", fontsize=17)
plt.xticks(xscale, fontsize=8)
plt.yticks(yscale, fontsize=8)
plt.xlabel("PN", fontsize=10)
plt.ylabel("評\n価", fontsize=10, rotation=0)
plt.xlim(-1.1, 1.1)
plt.ylim(0.5, 5.5)
 
plt.subplots_adjust(left=0.1, right=0.98, bottom=0.13, top=0.89)
plt.savefig("result_1.png", facecolor="white")

"""
# グラフを表示
plt.hist(x)
# グラフを画像保存
plt.savefig("result_2.png", facecolor="white")
"""
タイトルとURLをコピーしました