mai_Nのプログラミング勉強メモ

Python or R × マーケティング

映画レビューサイトから情報をスクレイピング②映画詳細情報

映画ドットコムからいろいろ情報を抽出して分析してみる!
今回は個別の映画の詳細ページから映画詳細情報を取りこんでみる。

まずは必要なライブラリをインポート
import requests
from bs4 import BeautifulSoup
import pandas as pd
映画詳細情報のページの情報をとってくる

今回は事前に用意した映画のIDリストを使ってforループで複数の映画情報をとってこれるようにした。
例えば以下の記事でcsvに出力したデータを使うことができる↓
ma-i.hatenablog.com

DF_movie_list = pd.read_csv("出力したcsvファイル")  #映画のIDをリストとして取得
movie_ids = DF_movie_list["movie_id"] 

data = []
for i in range(len(movie_ids)): #forループをまわす
    movie_id = movie_ids[i]
    movie_url = 'https://eiga.com/movie/' + movie_id + '/'
    soup = BeautifulSoup(requests.get(movie_url).content)
タイトルや概要などの情報を取得する

(上記のforループの中に入れる処理)

 movie_title = soup.find('h1', class_="page-title").text.replace("\u3000", "") #映画タイトル
    date = soup.find('p', class_="date-published").find('strong').text #公開年
    movie_minutes = soup.find('div', class_="movie-details").find_all('p')[1].text.split(":")[0].split("/")[1].replace("分", "") #映画の長さ
    dist_agency = soup.find('div', class_="movie-details").find_all('p')[1].text.split(":")[1] #配給会社
    explanation = soup.find('div', class_="movie-details").find_all('p')[0].text #概要
キャスト情報はulタグでリストを一気に取ってきてから1つ1つ分けた

(上記のforループの中に入れる処理)

    all_casts = soup.find('ul',class_="movie-cast").find_all('span') #リスト部分をごっそり取得
    for i in range(len(all_casts)): 
        all_casts[i] = all_casts[i].text.replace(" ", "").replace(" ", "")
    cast_1 = all_casts[0]  #5人目までは「cast_(数字)」という変数に格納
    cast_2 = all_casts[1]
    cast_3 = all_casts[2]
    cast_4 = all_casts[3]
    cast_5 = all_casts[4]
    cast_others = ", ".join(all_casts[5:]) #6人目以降はまとめて「cast_others」という変数に格納
スタッフ情報はdlタグで一気に取ってきてから1つ1つ分けた

(上記のforループの中に入れる処理)

    all_staffs = soup.find('dl',class_="movie-staff").find_all(['dt', 'dd']) #dlタグの中のdtまたはddタグ部分を取得
    for i in range(len(all_staffs)): 
        all_staffs[i] = all_staffs[i].text.replace(" ", "").replace(" ", "")
  #このリストの中身は「['監督', '山田△夫', '脚本', '田中〇美'...](役職名、個人名、役職名、個人名...)」という状態
    if '監督' in all_staffs:  #
        director = all_staffs[all_staffs.index('監督')+1] #リストの中の"監督"の次の要素を持ってくる
    if '脚本' in all_staffs:
        writer = all_staffs[all_staffs.index('脚本')+1] #リストの中の"脚本"の次の要素を持ってくる
最後に評価情報を取得してからここまで取得した全ての情報を辞書型データにまとめる

(上記のforループの中に入れる処理)

    rating_star = soup.find('span', class_="rating-star").text #評価(星の数)
    review_number = soup.find('span', class_="total-number").text.replace("全", "").replace("件", "") #レビュー数
    checkin_number = soup.find('div', class_="popular-tool").find('span').text.replace("人", "") #チェックイン数
    
    datum = {  #辞書型データにまとめる
        "movie_title":movie_title,
        "date":date,
        "movie_minutes":movie_minutes,
        "dist_agency":dist_agency,
        "explanation":explanation,
        "cast_1":cast_1,
        "cast_2":cast_2,
        "cast_3":cast_3,
        "cast_4":cast_4,
        "cast_5":cast_5,
        "cast_others":cast_others,
        "director":director,
        "writer":writer,
        "rating_star":rating_star,
        "review_number":review_number,
        "checkin_number":checkin_number,
    } 
    
    data.append(datum) #最初に作った空のリストに格納
必要ならcsvに出力

(これはforループの外。最後に。)

csv_filename = "映画詳細情報.csv"
DF_movie_detail = pd.DataFrame(data)
DF_movie_detail.to_csv(csv_filename, encoding="cp932")


↓↓↓↓↓

こんなファイルができた!

f:id:ma__i:20210728055838p:plain

映画レビューサイトから情報をスクレイピング①俳優の出演作品一覧

映画ドットコムからいろいろ情報を抽出して分析してみる!
今回は特定の俳優さんのページから出演作品一覧を取りこんでみる。

まずは必要なライブラリをインポート
import requests
from bs4 import BeautifulSoup
import pandas as pd
出演作品一覧のページのURLを指定する

URLを確認してみると俳優さん毎に個別のIDがあることがわかる。
あとでforループを使って複数の俳優さんの出演作品を抽出するためにここはIDからURLを作れるようにしておく。
そして複数のページにまたがった情報をとってこれるよう、URLの中のページを示す数字部分は{}にしておく。

actor_id = str(83068) #ここに俳優のIDを入力(今回は新垣結衣のIDを入れている)
url = 'https://eiga.com/person/' + actor_id + '/movie/{}/'
いよいよ情報を抽出する!
movie_list = []  #空のリストを用意。ここに情報を入れていく。
for i in range(3): #今回は3ページ目までとれるようにした。
    soup = BeautifulSoup(requests.get(url.format(i)).content) #formatを使ってページを指定する。
    movies = soup.find_all('li', class_="col-s-4") #各映画はliタブのcol-s-4クラスにあった。
    for i in range(len(movies)): #各映画について詳細情報をとっていく。
        movie = movies[i] #情報をとる映画を指定
        movie_title = movie.find('p', class_="title").text.replace("\u3000", "") #タイトル
        movie_year = movie.find('small', class_="time").text.replace("公開", "") #公開年
        movie_url = 'https://eiga.com/' + movie.find('a').get('href') #映画詳細ページのURL
        movie_id = movie_url.split("/")[-2] #俳優と同様に映画にも個別のIDがある。後で情報をとれるように保存しておく。
        movie_datum = { #辞書型データに格納する。
            "movie_title":movie_title,
            "movie_year":movie_year,
            "movie_url":movie_url,
            "movie_id":movie_id
        }
        movie_list.append(movie_datum)
   #辞書型データをリストに格納する。リストの中に、個別の映画のデータ(辞書型)が一つずつ入っていくイメージ。
最後にデータフレーム型にしてからcsv出力する
actor = soup.find('p', class_="title-link").text #俳優の名前を取得
csv_filename = "{}の出演作品一覧.csv".format(actor) #csvのファイル名を作る
DF_movie_list = pd.DataFrame(movie_list) #データフレームにする
DF_movie_list.to_csv(csv_filename, encoding="cp932") #csvに出力


↓↓↓↓↓

こんなファイルができた!

f:id:ma__i:20210727184157p:plain

Janomeを使った形態素解析④

※使用したデータ:カップヌードルミュージアムの口コミ500件
じゃらんの口コミをスクレイピングで取得したもの。 )

■Jaccard係数を計算する

まずはレビューごとのtermリストのリスト(つまり二次元リスト)を作る
terms_list= []

from janome.tokenizer import Tokenizer
t = Tokenizer()
for i in range(500):
    text = DF['本文'][i]
    terms = []
    token_list = t.tokenize(text)
    for token in token_list:
        if token.part_of_speech.split(',')[0] in ['名詞']: #品詞は名詞に限定
            terms.append(token.base_form)
    terms_list.append(terms)


次に頻出termのみ抽出してtermの組み合わせリストを作る
from collections import defaultdict
term_freq = defaultdict(lambda: 0)
for term in sum(terms_list, []):
    term_freq[term] += 1
term_freq_sort = sorted(term_freq.items(), key=lambda x:x[1], reverse=True)
term_freq_20 = term_freq_sort[:20] #ここで頻出単語Top20にした
term_uni = [tupple[0] for tupple in term_freq_20]
term_combis = list(itertools.combinations(term_uni, 2))


Jaccard係数を計算する
def Jaccard(x,y,terms_list): #x,y=2つのterm
    seki = sum(((x in terms) & (y in terms)) for terms in terms_list)
    wa = sum(((x in terms) or (y in terms)) for terms in terms_list)
    return seki/wa

Jaccard_list = []
for term_combi in term_combis:
    data = dict(term1=term_combi[0], term2=term_combi[1], Jaccard=Jaccard(term_combi[0], term_combi[1], terms_list))
    Jaccard_list.append(data)


termの組み合わせとJaccard係数をまとめたデータフレームを作る
import pandas as pd
DF_Jaccard = pd.DataFrame(Jaccard_list)
DF_Jaccard = DF_Jaccard.sort_values(by='Jaccard', ascending=False).reset_index()[:30] 
 #Jaccard係数が大きい順に並べ替えてTop30を抽出

f:id:ma__i:20210727165754p:plain


■ NetworkXで描画する

import networkx as nx
import matplotlib
import matplotlib.pyplot as plt
%matplotlib inline

DF = DF_Jaccard

term_combi = [(DF['term1'][i], DF['term2'][i]) for i in range(len(DF))]
G = nx.Graph()
G.add_edges_from(term_combi)
G.remove_nodes_from(['ラーメン', '体験', '大人', 'チキン']) #適宜ノードやエッジを削除して見た目をよくする

width = DF['Jaccard']*10
node_size = [term_freq[term]*7 for term in G.nodes]
pos = nx.spring_layout(G, k=0.5) #エッジの大きさによる吸引力でノードの位置を決定する(kの値が大きいと全体的に丸くなる)

nx.draw(G, pos, with_labels=True, font_family='IPAexGothic', width=width, node_size=node_size,\
        node_color='skyblue',alpha=0.6, edge_color='gray')

f:id:ma__i:20210727170303p:plain

■おまけ:pyfpgrowthを使った組み合わせパターンの抽出

import pyfpgrowth
kyoki = pyfpgrowth.find_frequent_patterns(wakati_list1, 30) #頻度の高い組み合わせを抽出。「30回以上」と指定。
pair = []
for kyoki_set in kyoki.items():
    if len(kyoki_set[0]) == 2:  #2つの単語の組み合わせのみ抽出し、単語3つ以上の組み合わせは除く。
        pair.append(kyoki_set)
sorted_pair = sorted(pair, key=lambda x:x[1], reverse=True) #出現頻度で並び替え
sorted_pair_30 = sorted_pair[:30] #Top30を抽出
sorted_pair_30

[(('カップ', 'ヌードル'), 636),
(('する', 'カップ'), 274),
(('できる', 'カップ'), 244),
(('オリジナル', 'ヌードル'), 228),
(('こと', 'できる'), 228),
(('いる', 'する'), 193),
(('カップ', '作る'), 180),
(('ある', 'カップ'), 177),
(('カップ', '自分'), 161),
(('カップ', '楽しい'), 140),
(('カップ', '作れる'), 132),
(('ヌードル', '歴史'), 131),
(('ヌードル', 'ヌードル'), 127),
(('カップ', 'カップ'), 127),
(('する', 'ラーメン'), 125),
(('する', 'の'), 118),
(('カップ', '楽しめる'), 117),
(('の', 'カップ'), 110),
(('する', '体験'), 98),
(('なる', 'カップ'), 94),
(('する', 'れる'), 89),
(('カップ', '子供'), 84),
(('ヌードル', '子供'), 80),
(('する', 'カップラーメン'), 79),
(('カップ', '具'), 76),
(('カップ', '食べる'), 76),
(('ヌードル', '食べる'), 73),
(('する', '行く'), 73),
(('カップ', 'ラーメン'), 73),
(('する', 'チキン'), 71)]

TF-IDF(特徴語の抽出)

※使用したデータ:カップヌードルミュージアムの口コミ500件
じゃらんの口コミをスクレイピングで取得したもの。
 なお、単語はリスト化した後に動詞・名詞・形容詞・副詞のみに限定。)

分かち書きデータの作成

def generate_wakati_data(text_list):
    wakati_data = ""
    from janome.tokenizer import Tokenizer
    t = Tokenizer()
    for text in text_list:
        token_list = t.tokenize(text)
        for token in token_list:
            if token.part_of_speech.split(',')[0] in ['名詞']:  #ここで品詞を限定
                wakati_data = wakati_data + " " + token.base_form
    return wakati_data

wakati_0 = generate_wakati_data(text_list_0) #text_listはデータフレームの1つのカラム(レビュー本文etc.)を指定するなど
wakati_1 = generate_wakati_data(text_list_1)
#以下、同様に分かち書きデータを作る


■TF-IDFの計算

datas = [wakati_0, wakati_1,...] #比較する分かち書きデータのリスト
from sklearn.feature_extraction.text import TfidfVectorizer
vectorizer = TfidfVectorizer()
X = vectorizer.fit_transform(datas)
terms = vectorizer.get_feature_names()  #キーワードのリスト
matrix = X.toarray()  #TF-IDFの行列

def get_tfidfs(i, top):  #i=matrixのindex(行)、top=取得する重要語の数
    array = matrix[i]
    key_indexs = array.argsort()[-top:][::-1]
    key_terms = [terms[index] for index in key_indexs]
    tfidfs = [matrix[i][index] for index in key_indexs]
    return key_terms, tfidfs

key_terms_0, tfidfs_0 = get_tfidfs(0, 50)
key_terms_1, tfidfs_1 = get_tfidfs(1, 50)
#以下、同様にTF-IDFを計算


■TFの計算

def get_tfs(wakati_data, key_terms):
    from collections import defaultdict
    term_list = wakati_data.split(" ")
    dic = defaultdict(lambda: 0)
    for key in term_list:
        dic[key] += 1
    tfs = [(dic[term])/len(wakati_data.split(" ")) for term in key_terms]
    return tfs

tfs_0 = get_tfs(wakati_0, key_terms_0)
tfs_1 = get_tfs(wakati_1, key_terms_1)
#以下、同様にTFを計算


■必要ならデータフレーム化

DF_tfidfs = pd.DataFrame({
    'category0':key_terms_0,
    'category0_tfidf':tfidfs_0,
    'category0_tf':tfs_0,
    'category1':key_terms_1,
    'category1_tfidf':tfidfs_1,
    'category1_tf':tfs_1
})
DF_tfidfs

Twitterからデータを取得①キーワード検索してコメントを収集

■tweepyの場合

import tweepy
import pandas as pd

CONSUMER_KEY = "取得したAPI key"
CONSUMER_SECRET = "取得したAPI Secret Key"
ACCESS_TOKEN = "取得したAccess Token"
ACCESS_TOKEN_SECRET = "取得したAccess Token Secret"
auth = tweepy.OAuthHandler(CONSUMER_KEY, CONSUMER_SECRET)
auth.set_access_token(ACCESS_TOKEN, ACCESS_TOKEN_SECRET)
twitter = tweepy.API(auth)

tweet_data_py = []  
keyword='ディズニー exclude:retweets' #キーワード「ディズニー」で検索。リツイートを除く
tweets = api.search(q=keyword, count=100) 
for tweet in tweets:
    tweet_datum = dict(ID=tweet.id, ユーザー名=tweet.user.screen_name, ツイート日時=tweet.created_at,\
                                    ツイート内容=tweet.text, いいね数=tweet.favorite_count, リツイート数=tweet.retweet_count)
    tweet_data_py.append(tweet_datum)

DF_py = pd.DataFrame(tweet_data_py)


■twythonの場合

from twython import Twython
import pandas as pd

CONSUMER_KEY = "取得したAPI key"
CONSUMER_SECRET = "取得したAPI Secret Key"
ACCESS_TOKEN = "取得したAccess Token"
ACCESS_TOKEN_SECRET = "取得したAccess Token Secret"
twitter = Twython(CONSUMER_KEY, CONSUMER_SECRET, ACCESS_TOKEN, ACCESS_TOKEN_SECRET)

tweet_data_py = []  
keyword='ディズニー exclude:retweets' #キーワード「ディズニー」で検索。リツイートを除く
tweets = twitter.search(q=keyword, count=100)
for tweet in tweets['statuses']:
    tweet_datum = dict(ID=tweet['id'], ユーザー名=tweet['user']['screen_name'], ツイート日時=tweet['created_at'], \
                                    ツイート内容=tweet['text'], いいね数=tweet['favorite_count'], リツイート数=tweet['retweet_count'])
    tweet_data_thon.append(tweet_datum)

DF_thon = pd.DataFrame(tweet_data_thon)

Janomeを使った形態素解析③

※使用したデータ:カップヌードルミュージアムの口コミ500件
じゃらんの口コミをスクレイピングで取得したもの。
 なお、単語はリスト化した後に動詞・名詞・形容詞・副詞のみに限定。)

■単語の出現回数順に並べる

  1. 辞書型データに「.items()」ですべての要素を取り出す
    (豆知識:「.keys()」=すべてのキーを取り出す、「.values()」=すべての値を取り出す)
  2. 「sorted()」で並び替える
sorted_word_freq = sorted(word_freq.items(), key=lambda x:x[1], reverse=True)
  • lambda関数の形:lambda (引数) : (返り値=式)
  • .items()で取り出した要素はタプル(キー、値)になっているので、x[1]=タプルの2番目の要素で並べ替えるという意味
  • reverse=True:降順

■グラフ化する(matplotlib)

import matplotlib.pyplot as plt
import japanize_matplotlib  #日本語に対応させる!
%matplotlib inline

words = [x[0] for x in sorted_word_freq[:10]]
freqs = [x[1] for x in sorted_word_freq[:10]]

fig = plt.figure(figsize=(10, 6))
plt.bar(words, freqs)
plt.show(fig)

f:id:ma__i:20210725211703p:plain
※ワードクラウドの画像を保存するには:

fig.savefig("画像名.png")


■ワードクラウドを作る

from wordcloud import WordCloud
import matplotlib.pyplot as plt

wc_data = ' '.join(base_list) #スペースで区切った1つの文字列にする
wordcloud = WordCloud(font_path='meiryo.ttc',\
                      background_color='white',\
                      max_words=50)
wordcloud.generate(wc_data)

plt.imshow(wordcloud)

f:id:ma__i:20210725212231p:plain

※ワードクラウドの画像を保存するには:

wordcloud.to_file("画像名.png")

Janomeを使った形態素解析②

■単語ごとに分けた辞書を作る

Tokenオブジェクトの中から必要な属性を取り出してリスト化する。

data = []
from janome.tokenizer import Tokenizer
t = Tokenizer()
token_list = t.tokenize("今日はいい天気ですね。")
for token in token_list:
    surface = token.surface
    base = token.base_form
    part_of_speech = token.part_of_speech.split(',')[0]
    part_of_speech_1 = token.part_of_speech.split(',')[1]
    datum = {
        '表層形':surface,
        '基本形':base,
        '品詞':part_of_speech,
        '品詞細分類1':part_of_speech_1,
    }
    data.append(datum)
data

[{'表層形': '今日', '基本形': '今日', '品詞': '名詞', '品詞細分類1': '副詞可能'},
{'表層形': 'は', '基本形': 'は', '品詞': '助詞', '品詞細分類1': '係助詞'},
{'表層形': 'いい', '基本形': 'いい', '品詞': '形容詞', '品詞細分類1': '自立'},
{'表層形': '天気', '基本形': '天気', '品詞': '名詞', '品詞細分類1': '一般'},
{'表層形': 'です', '基本形': 'です', '品詞': '助動詞', '品詞細分類1': '*'},
{'表層形': 'ね', '基本形': 'ね', '品詞': '助詞', '品詞細分類1': '終助詞'},
{'表層形': '。', '基本形': '。', '品詞': '記号', '品詞細分類1': '句点'}]

※辞書型リストにする別の方法※
dict(key=値, key=値, ....)を使う

data.append(dict(表層形=surface, 基本形=base, 品詞=part_of_speech, 品詞細分類1=part_of_speech_1))


■単語の出現回数をカウントする

まずは基本形のリストを作る。

base_list = []
for i in range(len(data)):
    base_list.append(data[i]['基本形'])
base_list

['今日', 'は', 'いい', '天気', 'です', 'ね', '。']

次に「defaultdict」を使ってカウントする。
※defaultdict→存在しないキーは新しいキーとして生成しながら辞書を作ってくれる。
(defautdictの引数=初期化時に実行する関数。今回は0を返す関数=「lambda:0」または「int」)

from collections import defaultdict
d = defaultdict(lambda: 0)
for key in base_list:
    d[key] += 1
d

defaultdict(int, {'今日': 1, 'は': 1, 'いい': 1, '天気': 1, 'です': 1, 'ね': 1, '。': 1})
(※キーのデフォルトは0で、一度出現する度に数値が増えていくプログラムになっている)