NetflixのRotten Tomatoesで高評価な作品を探すシリーズ第2回めです。前回の記事はこちらです。(前回からかなり時間が経ってしまいました。。)
前回の記事に書きましたが、実装手順は以下になります。
- wikipediaのnetflix original映画一覧をスクレイピングで取得
- 取得した映画一覧データを元にrotten tomatoesにアクセスして評価の値を取得
- 取得した評価の値から値の高い映画を求める
そして前回の記事では1の「wikipediaのnetflix original映画一覧をスクレイピングで取得」まで行いました。今回の記事では2と3の実装を行おうと思います。
この記事のゴール
rotten tomatoesの評価点数の値を取得してcsvファイルに出力する
rotten tomatoesの評価点数を取得
さて手順2「取得した映画一覧データを元にrotten tomatoesにアクセスして評価の値を取得」になりますが、実際に実装する方法としては取得した一覧をloopで回し、一つ一つの各作品ページにアクセスします。
とりあえず、rotten tomatoesのある1ページの点数の値をとるところから実装します。
rotten-tomatoes-scraper
rotten-tomatoes-scraperというモジュールがあるのでこれを今回は使います。ただしAnacondaにはインストールできませんでした。
なので今回はjupyter notebookは使用せず、普通のpythonで実装を試みようと思います。
コマンドプロンプトで以下を実行し、rotten-tomatoes-scraperをインストールします。
pip install rotten-tomatoes-scraper
rotten-tomatoes-scraperの詳細はこちらから。
試しに『THE SIEGE OF JADOTVILLE』という作品のデータをとってこようかと思います。以下がページの画像です。
TOMATOMETERは評論家の点数、AUDIENCE SCOREは観客の点数です。TOMATOMETERが64でAUDIENCE SCOREが74ですね。
以下のコードを記載します。
from rotten_tomatoes_scraper.rt_scraper import MovieScraper
movie_url = 'https://www.rottentomatoes.com/m/the_siege_of_jadotville' #👈点数が載っているページのURL
movie_scraper = MovieScraper(movie_url=movie_url)
movie_scraper.extract_metadata()
print(movie_scraper.metadata)
コマンドプロンプトで書いたコードを実行しましょう。(今回はrotten_tomatoes.pyというファイル名なのでrotten_tomatoes.pyを実行しています。)
python test.py 👈このコマンドをpythonファイルのあるディレクトリで実行
{'Score_Rotten': '64', 'Score_Audience': '74', 'Genre': ['Mystery&thriller', 'Drama']}
OMG!!!!何という事だ!!!!
超絶簡単に点数が取れている!!TOMATOMETER(Score_Rotten)が64でAUDIENCE SCORE(Score_Audience)が74です。
netflix original映画一覧から各作品の点数を取得する
さて、次のステップとしては映画一覧の各映画のそれぞれの点数を取得することにしましょう。
Rotten Tomatoes側のURLを見てみる
まずは評価値を取得するためにRotten Tomatoesにアクセスする必要があります。
幾つか見てみます。
2016年の作品からピックアップしますが例えば、『REBIRTH』という作品や『XOXO』という作品はその作品が製作された年がURLに入っています。
『REBIRTH』であれば「https://www.rottentomatoes.com/m/rebirth_2016」、『XOXO』であれば「https://www.rottentomatoes.com/m/xoxo_2016」といった感じです。
一方、同じ2016年の作品でも製作された年がURLに入っていない作品もあります。例えば、『BRAHMAN NAMAN』や『THE SIEGE OF JADOTVILLE』がそうです。
『BRAHMAN NAMAN』であれば「https://www.rottentomatoes.com/m/brahman_naman」、『THE SIEGE OF JADOTVILLE』であれば「https://www.rottentomatoes.com/m/the_siege_of_jadotville」という感じです。
また、URLはタイトルを小文字にしたもの、そして、『BRAHMAN NAMAN』のようにタイトルにスペースが入っているようなものはスペースのところに「_」(アンダースコア)が入っているのがわかります。
以上を踏まえてプログラムに落とし込んでいきましょう。
プログラムに落とし込む
上記で作成したプログラムに追記して以下のようなプログラムにします。
from rotten_tomatoes_scraper.rt_scraper import MovieScraper
import pandas as pd
import re
movie_url = 'https://www.rottentomatoes.com/m/the_siege_of_jadotville'
movie_scraper = MovieScraper(movie_url=movie_url)
movie_scraper.extract_metadata()
print(movie_scraper.metadata)
## wikipediaのNetflix originalの表があるURLを指定 👇
## 2015~2017年、2018年、2019年、2020年、2021年それぞれのURLを取得
url_20152017 = "https://en.wikipedia.org/wiki/List_of_Netflix_original_films_(2015%E2%80%932017)"
url_2018 = "https://en.wikipedia.org/wiki/List_of_Netflix_original_films_(2018)"
url_2019 = "https://en.wikipedia.org/wiki/List_of_Netflix_original_films_(2019)"
url_2020 = "https://en.wikipedia.org/wiki/List_of_Netflix_original_films_(2020)"
url_2021 = "https://en.wikipedia.org/wiki/List_of_Netflix_original_films_(2021%E2%80%93)"
## (1). 各ページの表をDataframeに入れる👇
df_20152017 = pd.read_html(url_20152017)
df_2018 = pd.read_html(url_2018)
df_2019 = pd.read_html(url_2019)
df_2020 = pd.read_html(url_2020)
df_2021 = pd.read_html(url_2021)
## (2). 上記(1)で作成したDataframeを辞書型に変換👇
dict_20152017 = df_20152017[0].to_dict()
dict_2018 = df_2018[0].to_dict()
dict_2019 = df_2019[0].to_dict()
dict_2020 = df_2020[0].to_dict()
dict_2021 = df_2021[0].to_dict()
base_tomato_url = "https://www.rottentomatoes.com/m/"
## (3). 上記(2)で作成した辞書からアクセスするURLを作成していく👇
## 今回は2015~2017年の作品に対してのみ行う
for (title, premiere_day) in zip(dict_20152017['Title'].values(), dict_20152017['Premiere'].values()):
original_title = title.replace(' ', '_')
### 作品タイトルだけのURLの場合用のURL👇
original_title = original_title.lower()
print(base_tomato_url + original_title)
### 表から公開された年を正規表現で取得する👇
year = re.findall('\S\d\d\d', premiere_day)
### 作品タイトルに公開年を加えたURLの場合用のURL👇
title_with_year = original_title + "_" + year[0]
print(base_tomato_url + title_with_year)
実行すると以下のようになります。
URLに公開年が含まれているパターンと含まれていないパターンの2種類を一旦作成しています。
python rotten_tomatoes.py 👈実行コマンド
{'Score_Rotten': '64', 'Score_Audience': '74', 'Genre': ['Mystery&thriller', 'Drama']}
Beasts of No Nation | Rotten TomatoesAs civil war rages in Africa, a fierce warlord (Idris Elba) trains a young orphan (Abraham Attah) to join his group of guerrilla soldiers.
Rotten Tomatoes: MoviesRotten Tomatoes, home of the Tomatometer, is the most trusted measurement of quality for Movies & TV. The definitive site for Reviews, Trailers, Showtimes, and
The Ridiculous 6 | Rotten TomatoesWhite Knife, an orphan raised by Native Americans, discovers that five outlaws are actually his half-brothers. Together, they set out to save their wayward fath
Rotten Tomatoes: MoviesRotten Tomatoes, home of the Tomatometer, is the most trusted measurement of quality for Movies & TV. The definitive site for Reviews, Trailers, Showtimes, and
Rotten Tomatoes: MoviesRotten Tomatoes, home of the Tomatometer, is the most trusted measurement of quality for Movies & TV. The definitive site for Reviews, Trailers, Showtimes, and
Rotten Tomatoes: MoviesRotten Tomatoes, home of the Tomatometer, is the most trusted measurement of quality for Movies & TV. The definitive site for Reviews, Trailers, Showtimes, and
Special Correspondents | Rotten TomatoesTwo radio journalists concoct a scheme to fake their kidnappings in South America.
Rotten Tomatoes: MoviesRotten Tomatoes, home of the Tomatometer, is the most trusted measurement of quality for Movies & TV. The definitive site for Reviews, Trailers, Showtimes, and
Rotten Tomatoes: MoviesRotten Tomatoes, home of the Tomatometer, is the most trusted measurement of quality for Movies & TV. The definitive site for Reviews, Trailers, Showtimes, and
Rotten Tomatoes: MoviesRotten Tomatoes, home of the Tomatometer, is the most trusted measurement of quality for Movies & TV. The definitive site for Reviews, Trailers, Showtimes, and
The Fundamentals of Caring | Rotten TomatoesA writer (Paul Rudd) retires after a personal tragedy and becomes a disabled teen's caregiver. When the two embark on an impromptu road trip, their ability to c
Rotten Tomatoes: MoviesRotten Tomatoes, home of the Tomatometer, is the most trusted measurement of quality for Movies & TV. The definitive site for Reviews, Trailers, Showtimes, and
Brahman Naman | Rotten TomatoesNaman, Ronnie, and Bernie, three sex-starved college students from the quiz team are ecstatic when they have to head to Calcutta for a competition, hoping to lo
Rotten Tomatoes: MoviesRotten Tomatoes, home of the Tomatometer, is the most trusted measurement of quality for Movies & TV. The definitive site for Reviews, Trailers, Showtimes, and
The Rebirth | Rotten TomatoesAfter his young daughter is murdered, a man (Masahiro Kobayashi) moves away only to encounter the mother (Makiko Watanabe) of the killer.
Rebirth (2016) | Rotten TomatoesA white-collar suburban father is drawn into a bizarre rabbit hole of psychodrama, seduction and violence by his old friend's self-actualization program.
・・・【略】・・・
続いて各映画作品のrotten tomatoesのサイトにアクセスして点数を取得します。
上記のプログラムに追記する形で以下のようなコードにします。2015~2017を今回対象にしたいのでそれ以外のところは見やすいように削除しておきます。
その他のところも不要なところは削除しておきます。
追記したところは「👈」を付けてあります。(すいません、見にくいかもしれません。。)
from rotten_tomatoes_scraper.rt_scraper import MovieScraper
import pandas as pd
import requests #👈
from urllib.error import HTTPError #👈
import re
import time #👈
url_20152017 = "https://en.wikipedia.org/wiki/List_of_Netflix_original_films_(2015%E2%80%932017)"
df_20152017 = pd.read_html(url_20152017)
dict_20152017 = df_20152017[0].to_dict()
base_tomato_url = "https://www.rottentomatoes.com/m/"
for (title, premiere_day) in zip(dict_20152017['Title'].values(), dict_20152017['Premiere'].values()):
original_title = title.replace(' ', '_')
original_title = original_title.replace("'", '') #👈
original_title = original_title.replace("-", '_') #👈
original_title = original_title.lower()
year = re.findall('\S\d\d\d', premiere_day)
title_with_year = original_title + "_" + year[0]
time.sleep(5) #👈
try: #👈
movie_url = base_tomato_url + original_title
print(movie_url)
movie_scraper = MovieScraper(movie_url=movie_url)
movie_scraper.extract_metadata()
print(movie_scraper.metadata["Score_Rotten"]) #👈
except UnicodeEncodeError: #👈
print("unicode error") #👈
except HTTPError: #👈
try: #👈
movie_url = base_tomato_url + title_with_year
print(movie_url)
movie_scraper = MovieScraper(movie_url=movie_url)
movie_scraper.extract_metadata()
print(movie_scraper.metadata["Score_Rotten"]) #👈
except: #👈
pass #👈
上記のコードの説明をしていきます。
4行目「from urllib.error import HTTPError」はこれをしないと31行目「except HTTPError」が機能しないために入れています。
22行目はスクレイピングするにあたって攻撃と思われないようにスクレイピングするタイミングを5秒ごとにします。
29行目から39行目が各映画の点数を取ってくるところなのですが、基本的な流れとしては
「URLに公開年が含まれていないパターンのURLでアクセスできるか」
↓
「アクセスできなければURLに公開年が含まれているパターンのURLでアクセスできるか」
↓
「そうでなければ飛ばして次ぎの映画のページにアクセスを行う」
といった流れになります。
29行目「except UnicodeEncodeError:」は「7 años」のようなアルファベットとは違う文字が入ってしまっているとURLに組み込めないので今回はexpectで外すようにしています。
31行目の「except HTTPError:」の中にさらにtry exceptがあるのは「URLに公開年が含まれているパターンと含まれていないパターンの2種類」以外のものがあるからです。
例えば、wikipediaでは「True Memoirs of an International Assassin」という作品ですが、rotten tomatoでは「THE TRUE MEMOIRS OF AN INTERNATIONAL ASSASSIN」と「THE」がついてしまっています。(大文字小文字は全てURLが小文字であり、URLにする際にタイトルを小文字に変えているので問題ないです)
高得点の点数の映画をcsvファイルに出力する
この記事の頭でも書きました以下の手順の「3」のところの実装に移ります。
- wikipediaのnetflix original映画一覧をスクレイピングで取得
- 取得した映画一覧データを元にrotten tomatoesにアクセスして評価の値を取得
- 取得した評価の値から値の高い映画を求める
点数を取得するところまでできたので後は「ある閾値以上の映画をcsvに出力する」だけです。
上記プログラムに対して、以下のようにコードを追記します。
from rotten_tomatoes_scraper.rt_scraper import MovieScraper
import pandas as pd
import requests
from urllib.error import HTTPError
import re
import time
url_20152017 = "https://en.wikipedia.org/wiki/List_of_Netflix_original_films_(2015%E2%80%932017)"
df_20152017 = pd.read_html(url_20152017)
dict_20152017 = df_20152017[0].to_dict()
base_tomato_url = "https://www.rottentomatoes.com/m/"
high_score_rotten_point_list = []
high_score_rotten_name_list = []
high_score_audience_point_list = []
high_score_audience_name_list = []
both_high_rotten_audience_rotten_point_list = []
both_high_rotten_audience_audience_point_list = []
both_high_rotten_audience_name_list = []
for (title, premiere_day) in zip(dict_20152017['Title'].values(), dict_20152017['Premiere'].values()):
original_title = title.replace(' ', '_')
original_title = original_title.replace("'", '')
original_title = original_title.replace("-", '_')
original_title = original_title.lower()
year = re.findall('\S\d\d\d', premiere_day)
title_with_year = original_title + "_" + year[0]
time.sleep(3)
try:
movie_url = base_tomato_url + original_title
movie_scraper = MovieScraper(movie_url=movie_url)
movie_scraper.extract_metadata()
#### 👇👇👇ここから追加👇👇👇 ####
if movie_scraper.metadata["Score_Rotten"] is not '':
if int(movie_scraper.metadata["Score_Rotten"]) >= 80:
high_score_rotten_name_list.append(original_title)
high_score_rotten_point_list.append(movie_scraper.metadata["Score_Rotten"])
if movie_scraper.metadata["Score_Audience"] is not '':
if int(movie_scraper.metadata["Score_Audience"]) >= 75:
high_score_audience_name_list.append(original_title)
high_score_audience_point_list.append(movie_scraper.metadata["Score_Audience"])
if (movie_scraper.metadata["Score_Rotten"] is not '') and (movie_scraper.metadata["Score_Audience"] is not ''):
if ( int(movie_scraper.metadata["Score_Rotten"]) >= 80 ) and ( int(movie_scraper.metadata["Score_Audience"]) >= 75 ):
both_high_rotten_audience_name_list.append(original_title)
both_high_rotten_audience_rotten_point_list.append(movie_scraper.metadata["Score_Rotten"])
both_high_rotten_audience_audience_point_list.append(movie_scraper.metadata["Score_Audience"])
#### 👆👆👆ここまで追加👆👆👆 ####
except UnicodeEncodeError:
print("unicode error")
except HTTPError:
try:
movie_url = base_tomato_url + title_with_year
movie_scraper = MovieScraper(movie_url=movie_url)
movie_scraper.extract_metadata()
#### 👇👇👇ここから追加👇👇👇 ####
if movie_scraper.metadata["Score_Rotten"] is not '':
if int(movie_scraper.metadata["Score_Rotten"]) >= 80:
high_score_rotten_name_list.append(original_title)
high_score_rotten_point_list.append(movie_scraper.metadata["Score_Rotten"])
if movie_scraper.metadata["Score_Audience"] is not '':
if int(movie_scraper.metadata["Score_Audience"]) >= 75:
high_score_audience_name_list.append(original_title)
high_score_audience_point_list.append(movie_scraper.metadata["Score_Audience"])
if (movie_scraper.metadata["Score_Rotten"] is not '') and (movie_scraper.metadata["Score_Audience"] is not ''):
if ( int(movie_scraper.metadata["Score_Rotten"]) >= 80 ) and ( int(movie_scraper.metadata["Score_Audience"]) >= 75 ):
both_high_rotten_audience_name_list.append(original_title)
both_high_rotten_audience_rotten_point_list.append(movie_scraper.metadata["Score_Rotten"])
both_high_rotten_audience_audience_point_list.append(movie_scraper.metadata["Score_Audience"])
#### 👆👆👆ここまで追加👆👆👆 ####
except:
pass
#### 👇👇👇ここから追加👇👇👇 ####
dict_both_high_point = {'name': both_high_rotten_audience_name_list, \
'rotten point': both_high_rotten_audience_rotten_point_list, \
'audience point': both_high_rotten_audience_audience_point_list}
df_both_high_point = pd.DataFrame(dict_both_high_point)
df_both_high_point.to_csv('both_high_point.csv')
#### 👆👆👆ここまで追加👆👆👆 ####
追加した部分は簡単である点数以上であればリストに追加して、そのリストをdict→dataframe→csvとして出力しています。
Score_Rottenが80以上の映画、Score_Audienceが75以上の映画、そしてその両方を満たしている映画の3種類をファイルに出力しています。
これでファイルとして出力されます。こんな感じ👇
はい、終了です。
今回の例は2015~2017年のものだけを対象にコードを書きましたが、2018年以降も同様にしてコードを書くことで高評価の映画を収集することができます。
お疲れさまでした。
まとめ
今回は、rotten tomatoesの点数をスクレイピングしました。
といってもrotten-tomatoes-scraperというモジュールがあったから結構楽にできました。
実はBeautifule Soupで点数をスクレイピングしようとしたらうまくできなくて結構詰まっていました。
そんな時にみつけたrotten-tomatoes-scraper。こういうのは早く見つけられるようにしたいものです。
スクレイピングできたら分析をしていけますね!
最後まで読んでいただきありがとうございます。