はじめに
前回の記事に引き続き、Django✕Herokuでモバイルゲーム「Mobile Legends: Bang Bang」のヒーローランキングを作成していきます。今回は特にスクレイピングとランキング表示の実装に注目します。
前回の記事はこちらです:
スクレイピングでランキング作成
モデルを作成
スクレイピングで取得したデータを保存するために、main/models.py
にHeroMetaDataモデルを定義します。このモデルには、各ヒーローの名前、勝率、ピック率、BAN率、参照日、ランクレベル、作成日時を保存します。
class HeroMetaData(models.Model):
name = models.TextField()
win_rate = models.FloatField()
pick_rate = models.FloatField()
ban_rate = models.FloatField()
reference_date = models.DateField()
rank_level = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
unique_together = (('name', 'rank_level', 'reference_date'),)
モデルを定義した後、マイグレーションを実行してデータベースにテーブルを作成します。ローカル環境だけでなく、Heroku環境でもマイグレーションを実行することを忘れないでください。
python manage.py makemigrations
python manage.py migrate
heroku run python manage.py migrate
スクレイピングするファイルを作る
次に、スクレイピングを行うためのファイルを作成します。今回は、下記サイトからデータを取得します。必要なデータは各ヒーローの勝率、BAN率、ピック率です。
まず、必要なライブラリをインストールします。
pip install pandas selenium webdriver_manager beautifulsoup4
続いて、main/scraper.py
にスクレイピングのロジックを記述します。スクレイピングしたデータはHeroMetaDataモデルを使ってデータベースに保存するようにしてください。
import time
from selenium import webdriver
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.common.by import By
from bs4 import BeautifulSoup
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from datetime import datetime
from .models import HeroMetaData
def scrape_mlbb_meta_data():
# スクレイピングロジックを記述
# ...
管理コマンドの作成
main/management/commands/run_scraper.py
に、scrape_mlbb_meta_data
関数を実行するための管理コマンドを作成します。
from django.core.management.base import BaseCommand
from main.scraper import scrape_mlbb_meta_data
class Command(BaseCommand):
def handle(self, *args, **options):
scrape_mlbb_meta_data()
管理コマンドを実行し、データが正しくデータベースに保存されているか確認します。
python manage.py run_scraper
データが保存されていることが確認できました。
ランキングの表示
ビューの作成
main/views.py
に、スクレイピングしたデータを使ってランキングを作成するロジックを記述します。
- ヒーローのデータをz-scoreで評価し、各ヒーローの相対的な強さを計算
- z-scoreに基づいてヒーローにランクを割り当て
- ランクごとにヒーローの情報をグループ化
- ビューにデータを渡す
ランキング表示には、各ヒーローの画像を使用します。画像はWordPress上で保存されており、画像URLがヒーローのデータと一緒にデータベースに保存されています。
from django.shortcuts import render
from .models import Hero
from django.db.models import Max
from .models import HeroMetaData
import pandas as pd
# 定数の定義
RANK_THRESHOLDS = [1.0, 0.5, 0, -0.5, -1.0] # ランクの閾値
RANK_LABELS = ['S+', 'S', 'A+', 'A', 'B', 'C'] # ランクのラベル
def get_rank_from_z_score(score):
"""
z-scoreからランクを取得する関数
"""
for threshold, label in zip(RANK_THRESHOLDS, RANK_LABELS):
if score >= threshold:
return label
return 'C'
def hero_ranking(request):
# Heroモデルから必要なデータを取得
heroes = Hero.objects.all().values_list('name_jp', 'name_en', 'image_url')
# ロジックを記述
# ...
テンプレートの作成
ビューから渡されたデータをmain/templates/main/hero_ranking.html
で表示します。各ランクごとにヒーローの画像を表示するようにします。
<!DOCTYPE html>
<html>
<head>
<title>Hero List</title>
</head>
<body>
<h1>ヒーローTier</h1>
<p>Latest Update: {{ latest_date }}</p>
<h2>S+ Heroes</h2>
{% for hero in s_plus_heroes %}
<img src="{{ hero.2 }}" alt="{{ hero.0 }}" width="100">
{% endfor %}
<h2>S Heroes</h2>
{% for hero in s_heroes %}
<img src="{{ hero.2 }}" alt="{{ hero.0 }}" width="100">
{% endfor %}
...
</html>
これでローカル環境ではランキングが表示できるようになりました。
Herokuでの実行
次に、Heroku上でもスクレイピングとランキング表示ができるように設定します。
ビルドパックの設定
requirements.txt
に必要なライブラリを追加します。
...
pandas==2.2.1
selenium==4.19.0
webdriver_manager==4.0.1
beautifulsoup4==4.12.3
当初、以下の記事を参考にビルドパックを設定していましたが、この方法は非推奨になっていました。
代わりに、以下のビルドパックが推奨されていました。
このビルドパックは、ChromeとChromeDriverのバージョンを自動で合わせてくれるため、バージョン違いによるエラーを防ぐことができます。
以下のコマンドでビルドパックを追加します。
heroku create --buildpack https://github.com/heroku/heroku-buildpack-google-chrome.git
その後、アプリを再デプロイします。
git commit --allow-empty -m "Update buildpacks"
git push heroku main
上手くいかなかった
これでスクレイピングができるようになったので、まずは下記コマンドで取ってみることにしました。
heroku run python manage.py run_scraper
ただ、残念ながら、Heroku上でスクレイピングを実行することができませんでした。スクレイピング自体はできたのですが、対象のサイトを表示した際に表示されるプライバシーポリシーを上手く閉じることができず、データを取得できませんでした。
ローカル環境では問題なくプライバシーポリシーを閉じられるのですが、Heroku上では何故か閉じることができません。様々な方法を試しましたが、上手くいかなかったため、Heroku上でのスクレイピングは諦めることにしました。
データ同期の自動化
なので、ローカルでスクレイピングしたデータをJSON形式で保存し、そのデータをHerokuに同期するようにします。
scraper.py
にJSONファイルを作成するロジックを追加します。
def create_mlbb_meta_data():
# ロジックを記載
main/management/commands/run_create_hero_meta.py
を作成し、データ作成コマンドを実装します。
from django.core.management.base import BaseCommand
from main.scraper import create_mlbb_meta_data
class Command(BaseCommand):
def handle(self, *args, **options):
create_mlbb_meta_data()
下記コマンドを実行するとJSONファイルを作成できます。
python manage.py run_create_hero_meta
作成したJSONをデータベースに保存するロジックをscraper.py
に追加します。
def save_latest_meta_data_to_db():
# ロジックを記載
実行すると下記のようになります。作成しているか更新しているかを確認できるようにしました。
% python manage.py run_save_latest_meta_data_to_db
Updated: 124 records
Created: 0 records
このコマンドはHeroku側で問題なく実行できました。
% heroku run python manage.py run_save_latest_meta_data_to_db
Updated: 0 records
Created: 124 records
GitHubアクションによる自動化
データを最新の状態に保つ工程をGitHubアクションで自動化します。
.github/workflows/update_meta_data.yml
を作成し、自動化の手順を記述します。
name: Update Meta Data # ワークフローの名前
# ロジックを記載
...
GitHubのリポジトリ設定ページで、HEROKU_API_KEY
とHEROKU_APP_NAME
のシークレットを設定します。
また、テストとして手動でワークフローを実行できるようにするため、workflow_dispatch
トリガーを追加します。
しかし、GitHub上でワークフローを実行したところ、Heroku上で実行した場合と同様に、プライバシーポリシーが上手く閉じられずデータを取得できませんでした。
そのため、現時点ではコマンドの実行などは手動で行うことにしました。完全自動化については、今後も検討を続けていきます。
まとめ
とりあえず最低限としてヒーローをランキングで表示できるようになりました。
次はBootstrapを入れて見た目を整えていきます。