Edited at

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


はじめに

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