Open11

スマブラの特定キャラの勝率を出す

Kazufumi SuzukiKazufumi Suzuki

スマメイトで特定キャラのレート対戦結果を集計してそのキャラの有利不利を出す。

やること

  1. 各キャラ使いのページを取得
  2. 1.のURLから上位20人のユーザページを取得
  3. ユーザページから対戦結果を取得
  4. すべての対戦結果を集計して勝ち数、負け数を算出
  5. 勝率を出して、ソートする
Kazufumi SuzukiKazufumi Suzuki
  1. 各キャラ使いのページを取得
fighters.py
import requests
from bs4 import BeautifulSoup

url = 'https://smashmate.net/fighter/'
res = requests.get(url)
soup = BeautifulSoup(res.text, "html.parser")
result = soup.find('div', class_="smashlist")
fighters = result.find_all('a')

for fighter in fighters:
    print(",".join([fighter.text.strip(), fighter.get('href')]))
python3 fighter.py > fighter.txt
# 取得結果はこんな感じ
head -n 3 fighter.txt
マリオ,https://smashmate.net/fighter/mario/
ドンキーコング,https://smashmate.net/fighter/donkey_kong/
リンク,https://smashmate.net/fighter/link/
Kazufumi SuzukiKazufumi Suzuki

1.のURLから上位20人のユーザページを取得
単キャラ使いでない場合はどっちで対戦したか分からないため除外する

top_ranker.py
import requests
from bs4 import BeautifulSoup

# 対象キャラのスマメイトの上位ランカー
url = 'https://smashmate.net/fighter/steve/'
# url = 'https://smashmate.net/fighter/kazuya/'
# url = 'https://smashmate.net/fighter/minmin/'
res = requests.get(url)

soup = BeautifulSoup(res.text, "html.parser")
rankers = soup.find_all('div', class_="row row-center va-middle row-nomargin")

for i, r in enumerate(rankers):
    if i < 20:
        characters = r.find('div', class_="col-xs-3")
        # 単キャラ使いのみ集計
        if len(characters.find_all("a")) == 1:
            name = r.find('p', class_='name')
            rating = r.find('div', class_="col-xs-2 text-center rate")
            # 順位、名前、レート、ユーザーページを表示
            print(",".join([str(i + 1), name.text, rating.text.strip(), name.find('a').get('href')]))
 python3 top_ranker.py > minmin_ranker.txt
# 取得結果はこんな感じ
 head -n 3  minmin_ranker.txt
1,オムアツ,2245,https://smashmate.net/user/65286/
2,リム,2147,https://smashmate.net/user/82283/
3,たぁ,2142,https://smashmate.net/user/48115/
Kazufumi SuzukiKazufumi Suzuki

ユーザページから対戦結果を取得

count_winlose.py
import sys
import csv
import requests
import time
from bs4 import BeautifulSoup

if len(sys.argv) < 2:
    print("argument error")
    sys.exit(1)

filename = sys.argv[1]
csv_file = open(filename, "r")

f = csv.reader(csv_file)
for row in f:
    user_url = row[3]
    res = requests.get(user_url)
    soup = BeautifulSoup(res.text, "html.parser")
    # 対戦の最大ページ数を取得
    pages = soup.find('ul', class_='pagination').find_all('a')
    max_page = 0
    for p in pages:
        if p.get('href') is not None:
            page_num = p.get('href').split("=")[1]
            page_num = int(page_num)
            if max_page < page_num:
                max_page = page_num
    # 各ページで対戦成績を集計
    for page in range(1, max_page):
        time.sleep(3)
        res = requests.get(user_url + "?page=" + str(page))
        results = soup.find_all('div', class_="row row-center va-middle row-battle row-nomargin")
        for result in results:
            enemy_fighter = ""
            atags = result.find_all('a')
            for atag in atags:
                if 'fighter' in atag.get('href'):
                    # 単キャラ使いでない場合の勝敗はカウントしない
                    if enemy_fighter != "" and enemy_fighter != atag.get('href'):
                        continue
                    enemy_fighter = atag.get('href')

            winlose = result.find('span').text
            print(winlose + "," + enemy_fighter)
python3 count_winlose.py minmin_ranker.txt > minmin_result.txt
# 取得結果はこんな感じ
head -n 3  minmin_result.txt 
勝ち,https://smashmate.net/fighter/link/
負け,https://smashmate.net/fighter/link/
勝ち,https://smashmate.net/fighter/link/
Kazufumi SuzukiKazufumi Suzuki

以下の点を修正

  • with構文使った
  • 例外処理入れた(通信エラーなど補足するため)
  • ページを変えても同じurlを見ていたので修正
  • エラー出力に状況を出力
  • 最終ページがrangeの範囲外だったので修正
count_winlose.py
import sys
import csv
import requests
import time
from bs4 import BeautifulSoup

if len(sys.argv) < 2:
    print("argument error")
    sys.exit(1)

filename = sys.argv[1]
with open(filename, 'r') as csv_file:
    f = csv.reader(csv_file)
    for row in f:
        try:
            user_url = row[3]
            res = requests.get(user_url)
            soup = BeautifulSoup(res.text, "html.parser")
            # 対戦の最大ページ数を取得
            pages = soup.find('ul', class_='pagination').find_all('a')
            max_page = 0
            for p in pages:
                if p.get('href') is not None:
                    page_num = p.get('href').split("=")[1]
                    page_num = int(page_num)
                    if max_page < page_num:
                        max_page = page_num
            print(row[1], str(max_page), "ページ分取得します", file=sys.stderr)
            # 各ページで対戦成績を集計
            for page in range(max_page):
                time.sleep(3)
                res = requests.get(user_url + "?page=" + str(page+1))
                soup = BeautifulSoup(res.text, "html.parser")
                results = soup.find_all('div', class_="row row-center va-middle row-battle row-nomargin")
                print(str(page+1) + "ページ目" + str(len(results)) + "件取得中", file=sys.stderr)
                for result in results:
                    enemy_fighter = ""
                    atags = result.find('div', class_='col-xs-8').find_all('a')
                    for atag in atags:
                        if 'fighter' in atag.get('href'):
                            # 単キャラ使いでない場合の勝敗はカウントしない
                            if enemy_fighter != "" and enemy_fighter != atag.get('href'):
                                continue
                            enemy_fighter = atag.get('href')
                    winlose = result.find('span').text
                    print(winlose + "," + enemy_fighter)
        except Exception as e:
            print(e)
 python3 count_winlose.py minmin_ranker.txt > result/minmin.txt
オムアツ 14 ページ分取得します
1ページ目20件取得中
2ページ目20件取得中
3ページ目20件取得中
Kazufumi SuzukiKazufumi Suzuki

すべての対戦結果を集計して勝ち数、負け数を算出する

# 対戦中止とキャラがわからない行を除いて集計
cat minmin_result.txt | grep -v "対戦中止" | grep -v ,$ |  sort | uniq -c > minmin_winlose.txt
# 集計結果はこんな感じ
head -n 3 minmin_winlose.txt                                          
  37 勝ち,https://smashmate.net/fighter/banjo_and_kazooie/
  10 負け,https://smashmate.net/fighter/banjo_and_kazooie/
  56 勝ち,https://smashmate.net/fighter/bayonetta/
Kazufumi SuzukiKazufumi Suzuki

勝率を出して、ソートする

ファイルのクローズ漏れあるので今までのスクリプトもwith構文使うべき。

count_winrate.py
import csv
import sys

filename = sys.argv[1]

with open("fighter.txt", "r") as fighter_f:
    reader_fighter = csv.reader(fighter_f)
    for fighter in reader_fighter:
        # print(fighter)
        with open(filename, "r") as winlose_f:
            reader_winlose = csv.reader(winlose_f)
            win = 0
            lose = 0
            for winlose in reader_winlose:
                if fighter[1] == winlose[1]:
                    if "勝ち" in winlose[0]:
                        win = winlose[0][:-3].strip()
                    elif "負け" in winlose[0]:
                        lose = winlose[0][:-3].strip()
            if (int(win) + int(lose)) == 0:
                winrate = -1
            else:
                winrate= round(int(win) / (int(win) + int(lose)), 2)
            print(fighter[0], win, lose, winrate, sep=",")

下記を実行して結果を見てみるとゲッチが対戦数0なのはおかしい。
スクレイピングで例外出てたかも

python3 calc_winrate.py minmin_winlose.txt
Kazufumi SuzukiKazufumi Suzuki
python3 count_winlose.py minmin_ranker.txt > result/minmin.txt
cat result/minmin.txt | grep -v "対戦" | grep -v ,$ |  sort | uniq -c  > winlose/minmin.txt
python3 calc_winrate.py winlose/minmin.txt > winrate/minmin.txt
Kazufumi SuzukiKazufumi Suzuki

勝数負数から勝率を出すスクリプトはこちら

calc_winrate.py
import csv
import sys

filename = sys.argv[1]

with open("fighter.txt", "r") as fighter_f:
    reader_fighter = csv.reader(fighter_f)
    for fighter in reader_fighter:
        with open(filename, "r") as winlose_f:
            reader_winlose = csv.reader(winlose_f)
            win = 0
            lose = 0
            for winlose in reader_winlose:
                if fighter[1] == winlose[1]:
                    if "勝ち" in winlose[0]:
                        win = winlose[0][:-3].strip()
                    elif "負け" in winlose[0]:
                        lose = winlose[0][:-3].strip()
            if (int(win) + int(lose)) == 0:
                winrate = -1
            else:
                winrate= round(int(win) / (int(win) + int(lose)), 2)
            print(fighter[0], win, lose, winrate, sep=",")
Kazufumi SuzukiKazufumi Suzuki

結果見てみたけど、そこまではっきりした差がでない。(トップランカーの初期は相性関係なく勝つのでその辺で勝ちを稼いでるのであまり差が出てこないのか)

[0]~/development/ssbu $ cat winrate/king_k_rool.txt | grep -e "スティーブ" -e "カズヤ" -e "ミェンミェン"
ミェンミェン,43,23,0.65
スティーブ/アレックス,61,54,0.53
カズヤ,48,38,0.56
[0]~/development/ssbu $ cat winrate/kazuya.txt | grep -e "スティーブ" -e "カズヤ" -e "ミェンミェン"
ミェンミェン,30,26,0.54
スティーブ/アレックス,46,52,0.47
カズヤ,31,24,0.56
[0]~/development/ssbu $ cat winrate/minmin.txt | grep -e "スティーブ" -e "カズヤ" -e "ミェンミェン"
ミェンミェン,53,30,0.64
スティーブ/アレックス,93,53,0.64
カズヤ,57,30,0.66
[0]~/development/ssbu $ cat winrate/steve.txt | grep -e "スティーブ" -e "カズヤ" -e "ミェンミェン"
ミェンミェン,43,36,0.54
スティーブ/アレックス,60,48,0.56
カズヤ,28,15,0.65