Sponsored Link

カテゴリー別アーカイブ: BeautifulSoup

(87) NPBプロ野球選手の年別成績を取得する。(ver.2)

(87) NPBプロ野球選手の年別成績を取得する。(ver.2)

前回の 「(86) NPBプロ野球選手の年別成績を取得する。」 では、一人の選手の紹介ページのURLを入力し、一人の選手の年別成績を出力するだけの機能だった。数百人ものプロ野球選手についてこれを手作業で繰り返すのは大変だ。

今回は、NPB様サイトのチームメンバー一覧ページのURLを入力し、そこから個々の選手紹介ページのURLを抽出し、個々の選手の年別成績を出力するように改良する。すなわち、チームを指定すれば所属メンバー全員分のデータが自動で出力されるようにする。

■仕様

・NPB様ホームページの1チームの選手一覧ページのURLを入力する。
・そのページから選手個々の紹介ページのURLを自動抽出する。
・選手個々の紹介ページから年別成績を自動抽出する。
・打者成績、投手成績に分けてCSVファイルを自動出力する。

■プログラム

ちょっと力ずくなところもあるが後で改良しよう…

コマンドライン入力

以下の操作でホークスの全選手の年度別投手成績、年度別打者成績がファイル出力される。

$ mkdir ./da
$ mkdir ./pt
$ python3

>>> import lib_npb
>>> lib_npb.generate_record_file_for_1team("http://npb.jp/bis/teams/rst_h.html", "H")

他のチームのURLは以下の通り。

http://npb.jp/bis/teams/rst_c.html 広島
http://npb.jp/bis/teams/rst_g.html 巨人
http://npb.jp/bis/teams/rst_db.html 横浜
http://npb.jp/bis/teams/rst_t.html 阪神
http://npb.jp/bis/teams/rst_s.html ヤクルト
http://npb.jp/bis/teams/rst_d.html 中日

http://npb.jp/bis/teams/rst_h.html ソフトバンク
http://npb.jp/bis/teams/rst_f.html 日ハム
http://npb.jp/bis/teams/rst_m.html ロッテ
http://npb.jp/bis/teams/rst_l.html 西武
http://npb.jp/bis/teams/rst_e.html 楽天
http://npb.jp/bis/teams/rst_bs.html オリックス

lib_npb.py

import urllib.request
from bs4 import BeautifulSoup
import csv

#////////////////////////////////////////////////////////////////
class NpbPlayer:
    #------------------------------------------------------------
    def __init__(self, name, url):
        self.name = name
        self.url  = "http://npb.jp" + url
        self.ary_record_pitch = []    # 投手記録
        self.ary_record_bat = []      # 打者記録

    #------------------------------------------------------------
    # 1選手の投手記録をCSVファイルに出力
    def output_record_to_csv_file_pitch(self, fname):
        if len(self.ary_record_pitch) > 0 :
            with open(fname, "w") as fh:
                writer = csv.writer(fh, lineterminator="\n")
                writer.writerows(self.ary_record_pitch)

    #------------------------------------------------------------
    # 1選手の打者記録をCSVファイルに出力
    def output_record_to_csv_file_bat(self, fname):
        if len(self.ary_record_bat) > 0 :
            with open(fname, "w") as fh:
                writer = csv.writer(fh, lineterminator="\n")
                writer.writerows(self.ary_record_bat)

    #------------------------------------------------------------
    # 1選手の記録を収集
    def get_record(self):
        try:
            resp = urllib.request.urlopen(self.url)
        except urllib.error.HTTPError as e:
            print("HTTP-Error : ", e.code)
            return False
        except urllib.error.URLError as e:
            print("URL-Error : ", e.reason)
            return False
        else:
            self.ary_record_pitch = []
            self.ary_record_bat = []
            bs = BeautifulSoup(resp.read(), "html.parser")
            for div in bs.findAll("div",id="registermaintbl"):
                th = div.find(text="防")
                for tr in div.findAll("tr",class_="registerStats"):
                    ary_1_record = []
                    for td in tr.findAll("td"):
                        ary_1_record.append(td.get_text())
                    if th == None :
                        self.ary_record_bat.append(ary_1_record)
                    else:
                        self.ary_record_pitch.append(ary_1_record)
            return True

#////////////////////////////////////////////////////////////////
def get_player_list_detail( resp ):
    ary_cPlayer = []
    bs = BeautifulSoup(resp.read(), "html.parser")
    # find name
    for tr in bs.findAll("tr",{"class":"rosterPlayer"}):
        td = tr.find("td",{"class":"rosterRegister"})
        a = td.find("a")
        if a == None :
            continue
        url  = a.attrs["href"]
        name = a.get_text()
        cPlayer = NpbPlayer(name, url)
        ary_cPlayer.append(cPlayer)
    return ary_cPlayer

#////////////////////////////////////////////////////////////////
def get_player_list( team_url ):
    try:
        resp = urllib.request.urlopen(team_url)
    except urllib.error.HTTPError as e:
        print("HTTP-Error : ", e.code)
        return None
    except urllib.error.URLError as e:
        print("URL-Error : ", e.reason)
        return None
    else:
        ary_cPlayer = get_player_list_detail(resp)
        return ary_cPlayer

#////////////////////////////////////////////////////////////////
def generate_record_file_for_1team( team_url, prefix ):
    ary_cPlayer = get_player_list(team_url)
    for cPlayer in ary_cPlayer:
        if True == cPlayer.get_record():
            print("now generating %s" % cPlayer.name)
            cPlayer.output_record_to_csv_file_bat(  "./da/data_%s_%s_da.txt" % (prefix, cPlayer.name))
            cPlayer.output_record_to_csv_file_pitch("./pt/data_%s_%s_pt.txt" % (prefix, cPlayer.name))

■備忘録

python3では reload()が使えなくなった。
同じことを以下のように実現できる。

import importlib
importlib.reload(lib_npb)

シーズン中は日々成績が変化するので、ときどき以下を実行して最新情報を収集する。

import lib_npb
lib_npb.generate_record_file_for_1team("http://npb.jp/bis/teams/rst_c.html", "C")
lib_npb.generate_record_file_for_1team("http://npb.jp/bis/teams/rst_g.html", "G")
lib_npb.generate_record_file_for_1team("http://npb.jp/bis/teams/rst_db.html", "DN")
lib_npb.generate_record_file_for_1team("http://npb.jp/bis/teams/rst_t.html", "T")
lib_npb.generate_record_file_for_1team("http://npb.jp/bis/teams/rst_s.html", "Y")
lib_npb.generate_record_file_for_1team("http://npb.jp/bis/teams/rst_d.html", "D")
lib_npb.generate_record_file_for_1team("http://npb.jp/bis/teams/rst_h.html", "H")
lib_npb.generate_record_file_for_1team("http://npb.jp/bis/teams/rst_f.html", "F")
lib_npb.generate_record_file_for_1team("http://npb.jp/bis/teams/rst_m.html", "M")
lib_npb.generate_record_file_for_1team("http://npb.jp/bis/teams/rst_l.html", "L")
lib_npb.generate_record_file_for_1team("http://npb.jp/bis/teams/rst_e.html", "E")
lib_npb.generate_record_file_for_1team("http://npb.jp/bis/teams/rst_bs.html", "O")

(86) NPBプロ野球選手の年別成績を取得する。

(86) NPBプロ野球選手の年別成績を取得する。

NPB様のサイトには、現役全選手の年別成績が掲載されている。
趣味でいろいろと統計計算をしてみたいのだが、選手個々のデータをコピペでExcelに貼り付ける作業には限界がある。
そこで、またスクレイピングで取得することにする。

以前 (81) 野球選手の成績を主成分分析 (続編1/2) ではYahooスポーツ様のサイトの掲載情報を計算に使わせていただいた。今回はNPB様のデータを取得してみたい。

試作したプログラムでは、現在三冠王の柳田選手のページを対象にデータ取得してみる。

実行結果はこんな感じになった。
CSVファイルなので、この後はいろいろなツールで読み込んで計算に使える。

2011,福岡ソフトバンク,6,5,5,1,0,0,0,0,0,0,0, 0,0, 0,0,0,3, 0,.000,.000,.000
2012,福岡ソフトバンク,68,212,195,17,48,10,1,5,75,18,6, 1,2, 0,10,5,56, 2,.246,.385,.300
2013,福岡ソフトバンク,104,337,298,48,88,19,2,11,144,41,10, 1,0, 0,32,7,96, 3,.295,.483,.377
2014,福岡ソフトバンク,144,615,524,91,166,18,4,15,237,70,33, 6,0, 3,72,16,131, 8,.317,.452,.413
2015,福岡ソフトバンク,138,605,502,110,182,31,1,34,317,99,32, 8,0, 1,88,14,101, 9,.363,.631,.469
2016,福岡ソフトバンク,120,536,428,82,131,31,4,18,224,73,23, 2,0, 0,100,8,97, 8,.306,.523,.446
2017,福岡ソフトバンク,93,400,317,73,105,23,0,25,203,80,14, 7,0, 7,70,6,86, 5,.331,.640,.453

■仕様

・選手紹介ページのURLを与える。
・対象ページから年別成績を抽出してCSVファイルに保存する。

■プログラム

コマンドライン入力

$ python3

>>> import lib_npb
>>> cPlayer = lib_npb.get_player_data("http://npb.jp/bis/players/31835133.html")
>>> cPlayer.output_record_to_csv_file("data.txt")

lib_npb.py

import urllib.request
from bs4 import BeautifulSoup
import csv

#////////////////////////////////////////////////////////////////
class NpbPlayer:
    def __init__(self, name):
        self.name = name
        self.ary_record = []

    def append_record(self, ary):
        self.ary_record.append(ary)

    def output_record_to_csv_file(self, fname):
        with open(fname, "w") as fh:
            writer = csv.writer(fh, lineterminator="\n")
            writer.writerows(self.ary_record)

#////////////////////////////////////////////////////////////////
def get_player_data_detail( resp ):
    bs = BeautifulSoup(resp.read(), "html.parser")
    # find name
    td = bs.find("td",{"class":"registerPlayer"})
    h1 = td.find("h1")
    cPlayer = NpbPlayer(h1.get_text())
    # find record
    div = bs.find("div",{"id":"registermaintbl"})
    for tr in div.findAll("tr",{"class":"registerStats"}):
        ary_1_record = []
        for td in tr.findAll("td"):
            ary_1_record.append(td.get_text())
        cPlayer.append_record(ary_1_record)
    return cPlayer

#////////////////////////////////////////////////////////////////
def get_player_data( url ):
    try:
        resp = urllib.request.urlopen(url)
    except urllib.error.HTTPError as e:
        print("HTTP-Error : ", e.code)
        return None
    except urllib.error.URLError as e:
        print("URL-Error : ", e.reason)
        return None
    else:
        cPlayer = get_player_data_detail(resp)
        return cPlayer

(85) BeautifulSoupでなんちゃってGoogle検索

(85) BeautifulSoupでなんちゃってGoogle検索

先の「(79) なんちゃってGoogle検索」をBeautifulSoupを使って実装してみる。
だいぶ楽に実装できる。開発者様に感謝感謝 m(_ _)m

>>> import urllib.request
>>> from bs4 import BeautifulSoup
>>> 
>>> url="https://www.google.co.jp/search?q=応仁の乱"
>>> 
>>> opener = urllib.request.build_opener()
>>> opener.addheaders = [('User-agent', 'Mozilla/5.0')]
>>> html = opener.open(url)
>>> bs = BeautifulSoup(html.read())
>>> 
>>> for h3 in bs.findAll("h3",{"class":"r"}):
>>> 	print(h3.a.get_text())
>>> 	print(h3.a['href'])

実行結果は以下の通り。

応仁の乱 - Wikipedia
/url?q=https://ja.wikipedia.org/wiki/%25E5%25BF%259C%25E4%25BB%2581%25E3%2581%25AE%25E4%25B9%25B1&sa=U&ved=0ahUKEwjOi_Hd47bQAhUDFpQKHaJBAsYQFggUMAA&usg=AFQjCNFx2YRTF34Q90Pc-ZVBNiK2mHmjVQ
応仁の乱とは (オウニンノランとは) [単語記事] - ニコニコ大百科
/url?q=http://dic.nicovideo.jp/a/%25E5%25BF%259C%25E4%25BB%2581%25E3%2581%25AE%25E4%25B9%25B1&sa=U&ved=0ahUKEwjOi_Hd47bQAhUDFpQKHaJBAsYQFggeMAE&usg=AFQjCNEEgMYcO6EozSu66kBx92gM4eXlkg
応仁の乱について
/url?q=http://www12.plala.or.jp/rekisi/ounin.html&sa=U&ved=0ahUKEwjOi_Hd47bQAhUDFpQKHaJBAsYQFggjMAI&usg=AFQjCNG72yMzIbhr1SXfTdNzdDSx11AQcw
応仁の乱 | ニューワイド学習百科事典 | 学研キッズネット
/url?q=https://kids.gakken.co.jp/jiten/1/10020780.html&sa=U&ved=0ahUKEwjOi_Hd47bQAhUDFpQKHaJBAsYQFggpMAM&usg=AFQjCNG-egrj_Tu5iFSB_x2msSdsYRkH7Q
第32回 応仁の乱と衰退する室町幕府 - 歴史研究所 - 裏辺研究所
/url?q=http://www.uraken.net/rekishi/reki-jp32.html&sa=U&ved=0ahUKEwjOi_Hd47bQAhUDFpQKHaJBAsYQFgguMAQ&usg=AFQjCNGXknEN9pNpL94HdINbriS9AceYvA
応仁の乱(おうにんのらん)とは - コトバンク
/url?q=https://kotobank.jp/word/%25E5%25BF%259C%25E4%25BB%2581%25E3%2581%25AE%25E4%25B9%25B1-38826&sa=U&ved=0ahUKEwjOi_Hd47bQAhUDFpQKHaJBAsYQFgg0MAU&usg=AFQjCNFq5UqUG4vYc1n8DGBmyzqRKc6U9w
菊池寛 応仁の乱 - 青空文庫
/url?q=http://www.aozora.gr.jp/cards/000083/files/1368_37258.html&sa=U&ved=0ahUKEwjOi_Hd47bQAhUDFpQKHaJBAsYQFgg5MAY&usg=AFQjCNEVNvDd9tsTtAHOzs-t_VtCyU25bQ
応仁の乱について|社会の部屋|学習教材の部屋 - Biglobe
/url?q=http://www7a.biglobe.ne.jp/~gakusyuu/rekisi/ouninnoran.htm&sa=U&ved=0ahUKEwjOi_Hd47bQAhUDFpQKHaJBAsYQFgg_MAc&usg=AFQjCNH9aKPuRrhptgrhIPVqxLW8ZEUTDw
わかる歴史【室町時代】応仁の乱 前編 - YouTube
/url?q=https://www.youtube.com/watch%3Fv%3DObDWAbDMBEo&sa=U&ved=0ahUKEwjOi_Hd47bQAhUDFpQKHaJBAsYQtwIIRjAI&usg=AFQjCNG60Ikro_cShe86uEEQEYv1VXKcyA
応仁の乱が起こった理由 / 中学社会 歴史 by 早稲男 |マナペディア|
/url?q=http://manapedia.jp/text/798&sa=U&ved=0ahUKEwjOi_Hd47bQAhUDFpQKHaJBAsYQFghJMAk&usg=AFQjCNEB8cZKBua4-Gs8tYx7cfwZkDiz7A

(84) BeautifulSoupでスクレイピングを効率化

(84) BeautifulSoupでスクレイピングを効率化

先の「(81) 野球選手の成績を主成分分析 (続編1/2)」ではNPB様のホームページに記載されている個人打撃成績を取得させていただいた。前回は lxmlと XPathを使って個人成績データを抽出していたが、今回はこの処理を BeautifulSoup モジュールを使って行う。


(1) BeautifulSoupをインストール

$ pip3 install beautifulsoup4

(2) 2015年セリーグ打撃成績を取得してみる

① まずは情報を取得する。

>>> from urllib.request import urlopen
>>> from bs4 import BeautifulSoup
>>> html = urlopen("http://npb.jp/bis/2015/stats/bat_c.html")
>>> bs = BeautifulSoup(html.read())

② 次に、取得した情報から必要な情報だけを抽出する。

>>> ary_players = []
>>> for tr in bs.findAll("tr",{"class":"ststats"}):
>>>     ary_score = []
>>>     for td in tr.findAll("td"):
>>>         ary_score.append(td.get_text())
>>>     ary_players.append(ary_score)

or next_siblingsを使って順位列以降の必要なデータのみを抽出してもよい。

>>> ary_players = []
>>> for td in bs.findAll("tr",{"class":"ststats"}):
>>>     ary_score = []
>>>     for td in tr.td.next_siblings:
>>>         ary_score.append(td.get_text())
>>>     ary_players.append(ary_score)

③ 最後に結果を表示する。ここでCSVファイルなどに保存すればよい。

>>> for score in ary_players:
>>>     print(score)

取得した結果は以下の通り。

['1', '川端\u3000慎吾', '(ヤ)', '.336', '143', '632', '581', '87', '195', '34', '1', '8', '255', '57', '4', '3', '2', '2', '43', '0', '3', '72', '15', '.439', '.383']
['2', '山田\u3000哲人', '(ヤ)', '.329', '143', '646', '557', '119', '183', '39', '2', '38', '340', '100', '34', '4', '0', '3', '81', '1', '5', '111', '11', '.610', '.416']
['3', '筒香\u3000嘉智', '(デ)', '.317', '138', '568', '496', '79', '157', '28', '1', '24', '259', '93', '0', '0', '0', '2', '68', '0', '2', '98', '5', '.522', '.400']
['4', 'ルナ', '(中)', '.292', '134', '564', '496', '61', '145', '26', '1', '8', '197', '60', '11', '0', '0', '6', '57', '2', '5', '77', '13', '.397', '.367']
['5', 'ロペス', '(デ)', '.291', '140', '565', '516', '63', '150', '29', '1', '25', '256', '73', '1', '1', '0', '3', '44', '3', '2', '82', '14', '.496', '.347']
['6', '平田\u3000良介', '(中)', '.283', '130', '559', '491', '76', '139', '27', '3', '13', '211', '53', '11', '7', '1', '0', '64', '1', '3', '86', '5', '.430', '.369']
['7', '鳥谷\u3000敬', '(神)', '.281', '143', '646', '551', '69', '155', '21', '4', '6', '202', '42', '9', '6', '2', '3', '89', '2', '1', '77', '8', '.367', '.380']
['8', '福留\u3000孝介', '(神)', '.281', '140', '569', '495', '53', '139', '24', '3', '20', '229', '76', '1', '2', '1', '7', '65', '1', '1', '75', '15', '.463', '.361']
['9', 'マートン', '(神)', '.276', '140', '583', '544', '46', '150', '27', '0', '9', '204', '59', '0', '1', '0', '5', '31', '0', '3', '77', '21', '.375', '.316']
['10', '梶谷\u3000隆幸', '(デ)', '.275', '134', '578', '520', '70', '143', '35', '2', '13', '221', '66', '28', '13', '2', '2', '54', '0', '0', '132', '4', '.425', '.342']
['11', '新井\u3000貴浩', '(広)', '.275', '125', '480', '426', '52', '117', '22', '2', '7', '164', '57', '3', '0', '0', '4', '48', '1', '2', '73', '15', '.385', '.348']
['12', '田中\u3000広輔', '(広)', '.274', '141', '590', '543', '61', '149', '33', '9', '8', '224', '45', '6', '7', '5', '1', '34', '2', '7', '105', '8', '.413', '.325']
['13', 'ゴメス', '(神)', '.271', '143', '601', '520', '49', '141', '28', '0', '17', '220', '72', '0', '1', '0', '3', '72', '1', '6', '134', '15', '.423', '.364']
['14', 'エルナンデス', '(中)', '.271', '138', '548', '498', '54', '135', '27', '2', '11', '199', '58', '5', '3', '10', '4', '35', '1', '0', '106', '13', '.400', '.317']
['15', '雄平', '(ヤ)', '.270', '141', '585', '551', '57', '149', '33', '4', '8', '214', '60', '7', '4', '1', '3', '27', '1', '3', '82', '7', '.388', '.307']
['16', '坂本\u3000勇人', '(巨)', '.269', '130', '558', '479', '50', '129', '21', '3', '12', '192', '68', '10', '4', '9', '5', '65', '1', '0', '79', '5', '.401', '.353']
['17', '畠山\u3000和洋', '(ヤ)', '.268', '137', '584', '512', '64', '137', '26', '0', '26', '241', '105', '0', '0', '0', '8', '62', '0', '2', '92', '10', '.471', '.344']
['18', '大島\u3000洋平', '(中)', '.260', '142', '620', '565', '70', '147', '20', '4', '6', '193', '27', '22', '8', '10', '1', '39', '1', '5', '65', '5', '.342', '.313']
['19', 'バルディリス', '(デ)', '.258', '139', '525', '465', '38', '120', '23', '0', '13', '182', '56', '0', '0', '0', '5', '43', '2', '12', '62', '12', '.391', '.333']
['20', '菊池\u3000涼介', '(広)', '.254', '143', '644', '562', '62', '143', '20', '3', '8', '193', '32', '19', '9', '49', '2', '29', '2', '2', '92', '7', '.343', '.292']
['21', '上本\u3000博紀', '(神)', '.253', '108', '452', '375', '44', '95', '18', '1', '4', '127', '31', '19', '11', '29', '0', '44', '1', '4', '69', '1', '.339', '.338']
['22', '長野\u3000久義', '(巨)', '.251', '130', '479', '434', '49', '109', '20', '3', '15', '180', '52', '3', '2', '5', '2', '34', '0', '4', '81', '12', '.415', '.310']
['23', '丸\u3000佳浩', '(広)', '.249', '143', '633', '530', '81', '132', '28', '1', '19', '219', '63', '15', '7', '4', '4', '94', '2', '1', '143', '4', '.413', '.361']
['24', '中村\u3000悠平', '(ヤ)', '.231', '136', '502', '442', '36', '102', '14', '0', '2', '122', '33', '3', '2', '14', '2', '40', '1', '4', '80', '9', '.276', '.299']