LoginSignup
18
2

More than 3 years have passed since last update.

AtCoderの大学別レート分布を出してみた

Last updated at Posted at 2019-08-09

はじめに

PythonのWebスクレイピングでAtCoderのレート分布を出してみた」の続きです。
今回は年代別ではなく所属大学別にレート分布を出しました。
基本的な部分は前のものと同じです。

所属について

AtCoder の所属はユーザーが好きなものを書けるため、所属大学が同じでも表記が異なることがあります。例えば、同じ東京大学所属でも、所属を「東京大学」にしている人と「The University of Tokyo」にしている人がいます。
このような表記揺れに対応するために、まずは各大学の表記例を以下のスプレッドシートにて集めました。
https://docs.google.com/spreadsheets/d/1I1jQHw1A7oZv-At2XjDwuAG2IKZS3Q-pbEWUjg76XLM/edit?usp=sharing
追加してくれた方々ありがとうございます。

実装

まずスプレッドシートにより集計された大学別所属名を csv ファイルとしてダウンロードして、そのファイルを読み込みます。空白は検索時に "+" に置き換える必要があるので、 replace() で置換しておきます。

# 所属名一覧
Affiliation_dict = defaultdict(list)  # {大学名: [所属名のリスト], ...}

with open("AtCoder大学名表記一覧 - シート1.csv") as f:
    reader = list(csv.reader(f))
    for daigaku in reader[5:]:
        for syozoku in daigaku[2:]:
            if syozoku:
                Affiliation_dict[daigaku[1]].append(syozoku.replace(" ", "+"))

# {'東京大学': ['The+University+of+Tokyo', '東京大学', 'UT', 'UTokyo'], ...}

今回は所属別のレート分布のため検索対象に日本語の文字列が入ります。そのため、日本語での検索に対応させる必要があります。「PythonでURLエンコード・デコード(urllib.parse.quote, unquote)」を参考にすると、

if is_japanese(aff):  # 所属が日本語文字列か調べる
    Affiliation = parse.quote(Affiliation)  # URLエンコード

このように urllib.parse.quote() を使ってあげると日本語の文字列をURLエンコードしてあげることができます。
以下本コードです。

from urllib import request, parse
from bs4 import BeautifulSoup
from collections import defaultdict
import csv
import re


# 日本語かどうか
def is_japanese(string):
    p = re.compile("[ぁ-んァ-ン一-龥]+")
    return p.search(string) is not None


# 所属名一覧
Affiliation_dict = defaultdict(list)  # {大学名: [所属名のリスト], ...}

with open("AtCoder大学名表記一覧 - シート1.csv") as f:
    reader = list(csv.reader(f))
    for daigaku in reader[6:]:
        for syozoku in daigaku[2:]:
            if syozoku:
                Affiliation_dict[daigaku[1]].append(syozoku.replace(" ", "+"))

print(Affiliation_dict)

num = {}  # {大学名: [灰色の人数, 茶色の人数, ...]} の形式で人数を入れる

for univ in Affiliation_dict:
    print("-{}-".format(univ))

    univ_num = [0] * 10  # 大学ごとの[灰色の人数, 茶色の人数, ...]

    for aff in Affiliation_dict[univ]:
        print("--{}--".format(aff))

        if is_japanese(aff):  # 所属が日本語文字列か調べる
            Affiliation = parse.quote(aff)  # URLエンコード
        else:
            Affiliation = aff

        for rating in range(0, 4000, 400):
            print(rating)

            RatingLowerBound = rating
            if rating == 3600:  # 金以上
                RatingUpperBound = 9999
            else:
                RatingUpperBound = rating + 399

            # フィルタを URL にセットする
            url_filter = "?f.Affiliation=" + Affiliation + \
                         "&f.RatingLowerBound=" + str(RatingLowerBound) + \
                         "&f.RatingUpperBound=" + str(RatingUpperBound) + \
                         "&page="

            url = "https://atcoder.jp/ranking"
            html = request.urlopen(url + url_filter + "0")
            soup = BeautifulSoup(html, "html.parser")

            ul = soup.find_all("ul")

            a = []

            # 指定したフィルタでのページ数を調べる
            page = 0
            for tag in ul:
                try:
                    string_ = tag.get("class")

                    if "pagination" in string_:
                        a = tag.find_all("a")
                        break

                except:
                    pass

            for tag in a:
                try:
                    string_ = tag.get("href")

                    if "ranking" in string_:
                        page = max(page, int(tag.string))

                except:
                    pass

            # フィルタ内順位
            rank = []

            # 順位の最大値を調べるために、順位表の最後のページを見る
            html = request.urlopen(url + url_filter + str(page))
            soup = BeautifulSoup(html, "html.parser")

            td = soup.find_all("span")

            for tag in td:
                try:
                    string_ = tag.get("class").pop(0)

                    if string_ == "small":
                        rank.append(int(tag.string[1:-1]))

                except:
                    pass

            if rank:
                # フィルタ内順位の最大値がその人数
                univ_num[rating // 400] += max(rank) + rank.count(max(rank)) - 1

        num[univ] = univ_num

# 人数が多い順にソートする
num_sort = sorted(num.items(), key=lambda x: sum(x[1]), reverse=True)

dict_sort = {}

for i in num_sort:
    dict_sort[i[0]] = i[1]

print(dict_sort)

# CSV として書き出し
# 横軸が大学名、縦軸が色(上から灰色, 茶色, ...の順)
with open("rating_univ.csv", "w", encoding="shift_jis") as f:
    writer = csv.DictWriter(f, dict_sort.keys())
    writer.writeheader()
    for i in range(10):
        row = {}
        for k, v in dict_sort.items():
            row[k] = v[i]
        writer.writerow(row)

結果

2019年8月10日8時00分時点での所属集計結果を元にして大学別レート分布を作成しました。数が多いため、10人以上いる大学のみになります。

・出力された csv ファイルを Excel にインポートして色付け
スクリーンショット 2019-08-10 8.18.30.png
スクリーンショット 2019-08-10 8.18.49.png

・100% 積み上げ縦棒
スクリーンショット 2019-08-10 8.19.23.png

・積み上げ縦棒
スクリーンショット 2019-08-10 8.19.57.png

18
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
2