
スクレイピングで全国の進学校を捕捉する

前回は埼玉の御三家を調べたけど、次はどうするの?
コツコツとデータ収集して分析できる学校を増やしていきたいのだが。
うんうん。
ただ、他の都道府県もやっぱり気になるよな。
そうだね!
でも、全部の都道府県をいきなり調べるのは大変そうだなぁ。
とはいえ、高校の偏差値情報は必要そうなので、これまで同様「みんなの学校」から少し情報を入手しようと思う。
どのあたりを取得するの?
進学偏差は大学進学が前提になっているから高偏差値でないと意味がないからな。
とりあえず、偏差値65以上の高校をとってみる。
今回は面倒なのでスクレイピングさせていただくとして、、ChatGPTに聞いてみよう。
#pip install requests beautifulsoup4
インストールしたで。で、プログラムはうんうん、少し変更してこんな感じ。
get_link_list.py
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin, urlparse
def extract_links(base_url):
# HTMLを取得
response = requests.get(base_url)
if response.status_code != 200:
print(f"Failed to fetch {base_url}")
return
html_content = response.text
soup = BeautifulSoup(html_content, "html.parser")
# 学校詳細のリンクを取得
for name in soup.select('div.mod-listRanking-name a') :
if ( name['href'] ) :
print(name['href'])
if __name__ == "__main__":
base_url = 'https://www.minkou.jp/hischool/ranking/deviation/';
for i in range(36) :
if ( i == 0 ) :
extract_links(base_url)
else:
extract_links(base_url + "page=" + str(i+1) + "/")
"soup.select"は"soup.find_all"でもいいんだけど、"soup.select"はCSSセレクタでかけるから、そっちを使っている。慣れていない人は"soup.find_all"の方がいいのかもしれん。
色々な取得方法を用意しているんだね。
ChatGPTにある程度書いてもらえると助かるね。微妙な所はプロンプト考えるよりこっちでやった方が早そうだから、そこは適材適所かな。ファイル出力も面倒なので標準出力にした。
「36」って数字は何?
あー、ページだな。調べた感じだと、偏差値降順で見た場合、36ページくらいまで偏差値65の高校があったのでそこまでをループ設定している。
これで完成?
いや、まだ学校詳細ページのURL取得しただけだからな。これからさらに学校情報をとっていくぞ。
少し加工してこんな感じ。
get_hs_list.py
import sys
import re
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin, urlparse
def fetch_page_and_assets(base_url):
# HTMLを取得
response = requests.get(base_url)
if response.status_code != 200:
print(f"Failed to fetch {base_url}")
return
html_content = response.text
soup = BeautifulSoup(html_content, "html.parser")
#基本情報を取得
for tag in soup.select('div.mod-subSection table.table-binfo') :
td_list = tag.select('td')
if ( len(td_list) < 4 ):
continue
if ( td_list[4].get_text().strip() == '-' ):
continue
name = td_list[0].get_text()
gakka = re.findall('((\d+))',td_list[2].get_text())
hensa = sum(int(i) for i in gakka)/len(gakka)
#生徒数を設定
num = 500
if ( td_list[4].get_text().strip()[0:1] == '小' ):
num = 120
elif ( td_list[4].get_text().strip()[0:1] == '中' ):
num = 250
address = " ".join(i.get_text() for i in td_list[5].select('p.tx-address span'))
print(f"{name}\t{hensa}\t{num}\t{address}")
if __name__ == "__main__":
if len(sys.argv) != 2:
print("使い方: python get_hs_list.py <入力ファイル>", file=sys.stderr)
sys.exit(1)
input_file = sys.argv[1]
base_url = 'https://www.minkou.jp';
with open(input_file, 'r', encoding='utf-8') as f_in:
for line in f_in:
fetch_page_and_assets(base_url + line.strip() )
学校名と偏差値平均と生徒数・住所を取得するようにしている。
ただし、生徒数は小規模・中規模・大規模って記述しかないから暫定で小規模→120名、中規模→250名、大規模→500名としている。みんなの学校では小規模は400名以下、中規模が400~1000名、大規模が1000名以上ってなっているので小規模を360名、中規模を750名、大規模を1500名と固定にさせてもらった。これは全校の数字なのでそれぞれ3で割ったのがこの数字になる。
判断する情報がこれしかないからね。仕方ないもん!
実行して取得出来たデータは以下になる。
hs_list.txt
沖縄尚学高等学校 56.8 250 沖縄県 那覇市 国場747番地
お茶の水女子大学附属高等学校 77.0 120 東京都 文京区 大塚2-1-1
開成高等学校 77.0 250 東京都 荒川区 西日暮里4-2-4
開智高等学校 70.0 250 埼玉県 さいたま市岩槻区 徳力西186
開明高等学校 66.0 250 大阪府 大阪市城東区 野江1-9-9
大手前高松高等学校 59.0 250 香川県 高松市 室新町1166
高松高等学校 71.0 250 香川県 高松市 番町3-1-1
丸亀高等学校 69.0 250 香川県 丸亀市 六番丁1
学習院高等科 69.0 250 東京都 豊島区 目白1-5-1
鹿児島中央高等学校 67.0 250 鹿児島県 鹿児島市 加治屋町10-1
甲南高等学校 69.0 250 鹿児島県 鹿児島市 上之園町23-1
鶴丸高等学校 73.0 250 鹿児島県 鹿児島市 薬師2-1-1
鹿児島実業高等学校 54.0 250 鹿児島県 鹿児島市 五ケ別府町3591番3
鹿児島純心女子高等学校 59.25 120 鹿児島県 鹿児島市 唐湊4-22-2
春日部共栄高等学校 66.0 500 埼玉県 春日部市 上大増新田213番地
厚木高等学校 69.0 250 神奈川県 厚木市 戸室2-24-1
旭丘高等学校 64.5 250 愛知県 名古屋市東区 出来町3-6-15
小田原高等学校 66.0 250 神奈川県 小田原市 城山3-26-1
:
出来たもん!
言語の使い分け:Python・Perl・Shellの適材適所

偏差値65以上の高校を都道府県別にみてみるか。少し、Perlで書いてみよう。
何故にPerl?
本当はawkとかがいいんだろうが、インラインプログラムってどうも苦手で。
upper_65.pl
%pref_list = ();
while(<>) {
chomp;
my @list = split("\t",$_);
next if ($#list < 3);
my @address = split(" ",$list[3]);
next if ($#address < 2);
if ( $list[1] >= 65 ) {
$pref_list{$address[0]} += 1;
}
}
foreach my $key ( keys %pref_list) {
print $pref_list{$key} . "\t" . $key . "\n";
}
perlはこうチャチャっと書くのにはちょうどいい言語だと思うぞ。
pythonは・・。
う~ん、実行するぞ。
でも、このままだと、分かりにくくない?
shellで実行するときにソートします。そのために件数を前に持ってきた。
#perl upper_65.pl pref_list.txt | sort -r -n
54 東京都
27 神奈川県
27 大阪府
27 埼玉県
21 千葉県
13 福岡県
13 兵庫県
11 北海道
11 京都府
10 静岡県
10 茨城県
9 愛知県
6 鹿児島県
6 福島県
6 栃木県
6 宮城県
6 奈良県
5 群馬県
:
あ、圧倒的、東京都!
本当だな。次に多いのは神奈川・大阪・埼玉か。
やっぱり人口が多いところが学校数も多いな。
ちょっと、都道府県の年齢階級別人口をとってこよう。
ん?なんで?
まぁまぁ。
一旦、コピペしたよ。
pref
北海道 計 5183687 156638 187132 203762 216223
北海道 男 2450393 80315 95673 104103 110815
北海道 女 2733294 76323 91459 99659 105408
青森県 計 1243081 36305 44151 48461 52881
青森県 男 589143 18627 22727 24685 27066
青森県 女 653938 17678 21424 23776 25815
岩手県 計 1206479 36020 44793 49645 52659
岩手県 男 581809 18351 23003 25521 26917
岩手県 女 624670 17669 21790 24124 25742
宮城県 計 2268355 76333 92434 97534 102804
宮城県 男 1106183 39100 47510 50068 52689
宮城県 女 1162172 37233 44924 47466 50115
秋田県 計 956836 24275 30708 35643 37726
:
7カラム目(一番後ろ)が15歳~19歳。つまり、高校生の年代だな。この情報を取得するんだが、少し手を加える。
pref_count.pl
use strict;
while(<>) {
chop;
my @list = split("\t",$_);
next unless ( $list[1] eq '計' ) ;
print $list[0] . "\t" .int($list[6]/5 + 0.5) . "\n";
}
何か5で割って、0.5足してるね。
5で割っているのは1学年をとりたいからな。15歳~19歳だと5学年だから5で割っている。0.5はperlのint関数は切り捨てだから0.5を足すという四捨五入をするときのテクニックだ。
なるほど。
それで出来たのがこのファイル。
pref_hs.txt
北海道 43245
青森県 10576
岩手県 10532
宮城県 20561
秋田県 7545
山形県 9513
福島県 16870
茨城県 26519
栃木県 17906
群馬県 18301
埼玉県 65603
千葉県 55795
東京都 106001
:
都道府県別の高校生人口を調べたわけ

高校生1学年の人数ってことだ。ちょっとここでCharGPTに質問してみるか。
意味・解釈
1.上位学力層の割合
偏差値65以上は、統計的には上位約6.7%(正規分布に基づくと約6.68%)に相当します。そのため、都道府県ごとのこの割合を見ることで、「全国平均と比較して、その都道府県の上位層の生徒が多いか少ないか」を判断できます。
2.都道府県ごとの学力の地域差を測る指標の一つ
全国平均より高ければ、その都道府県では上位層の生徒が比較的多いことを示します。
低ければ、上位層の生徒の割合が全国平均より少ない可能性があります。
3.進学校の充実度や教育環境の影響を反映
学力の高い生徒が多い地域は、進学校や塾・予備校が充実していることが多いです。
都市部(東京・大阪など)は学力上位層の割合が高くなりやすい傾向があります。
4.将来の大学進学率や難関大学合格者数との関係
偏差値65以上の生徒が多い都道府県では、難関大学(東大・京大・旧帝大など)への合格者が多くなる可能性が高いです。
逆にこの割合が低い場合は、難関大学への進学者数も相対的に少ない可能性があります。
なるほど、これで偏差値65以上の生徒の割合が上位の都道府県を調べようってことね。
その通り。
今回はプログラム等で少し長くなったのでランキングは次回にします。乞うご期待!
次は、各都道府県の「偏差値65以上の生徒割合」を算出して、全国ランキングを作成するぞ。さらに上位の「偏差値70以上の生徒割合」を出してみるのも面白いな。
おぉ!地方と都市部でどれくらい差があるのかも気になるもん!
そうだな。人口密度や経済状況、教育予算なども関係してくるかもしれない。偏差値だけでなく、地域の特性との相関関係も見てみたいところだ。
データ分析って奥が深いね。次回も楽しみにしているもん!