見出し画像

Pythonで始めるプロ野球データの取得方法②~Webスクレイピングであの三冠王のプロフィールを取得しよう

前回の記事をお読みいただいている方はPythonの環境構築まで実施できていると思いますので、その前提でこの記事を進めて参ります。

この記事で確認できるPythonの技術

  1. 取得したURLのテキストから必要な情報を抜き出す

  2. テキストから取得したい情報を抜き出す

    1. 選手名の取得方法

    2. 選手名のカナの取得方法

    3. 選手のポジションの取得方法

    4. 選手の背番号の取得方法

    5. 選手の出身地の取得方法

    6. 選手の生年月日の取得方法

    7. 選手の身長と体重の取得方法

    8. 選手の血液型の取得方法

    9. 選手の投打スタイルの取得方法

    10. 選手のドラフト年度の取得方法

    11. 選手のプロ通算年数の取得方法

    12. 選手の経歴の取得方法

    13. 選手の主な獲得タイトルの取得方法

    14. 選手が甲子園に出場しているかを判別する方法

  3. ↑で得た情報を出力(print)する

  4. ↑で得た情報をCSVに保存する

まずは特定の1選手のみの情報を取得するプログラムを作成します。いきなり全選手の情報を取得しようとすると、プログラムが複雑になってきますので1選手のみを情報を取得できた、という成功体験をご経験いただきたいのと、基本的なスクレイピングの手法が理解できれば、その後複雑なプログラムを書く時にも応用が効くと思いますので、是非今回の記事を参考にしていただいてプログラミングの世界に入っていただけることを楽しみにしています。今回のプログラムでは初学者の方が躓くであろうループ処理は一切使用せずに処理を実行しています。

また、プログラムの記述方法は一通りではありません。今回学ぶ方法以外でも全く同じ情報が取得できます。余裕があれば別の方法を模索していただいても大丈夫ですが、初学者の方はまずは他人が書いたコードを見て真似る、ということを繰り返して自分なりのコードを書けるようにしていきましょう。人を真似る、ということは決して悪いことではありませんし、私も含めてプログラマーの方は最初は人のコードを真似てスキルアップを繰り返してきました。ですのでどんどん真似しちゃいましょうね。

プログラミングで挫折してしまう方ってもしかしたらすごく真面目な方なんだろうなって思います。例えば機械学習を学ぶために原理的なレベルから学必要があるから、中学校の数学から勉強をやり直して機械学習に臨む、、とか。確かにそこからやった方が力がつくとは思いますが、勉強やり直している間に諦めちゃう人が大半ですよね。Pythonは特に機械学習のライブラリーが多く揃っていますので、まずはそれを使いながら並行して数学を学び直す、ってやっていった方が長続きするのかなあと思います。

と、前置きが長くなってしまいました。

それでは実際のコードを用いて説明していきます。

特定の選手情報を取得するPythonコードの説明

コード全文は以下になります。

# GetNPBPlayer01.py
# yahooの選手情報からプロフィールを取得しCSVへ保存

import requests
from bs4 import BeautifulSoup
import csv

def get_player_details(soup):
    p_name = soup.select_one("h1:nth-child(4)").text if soup.select_one("h1:nth-child(4)") else ""
    p_name_kana = soup.select_one("rt").text.replace("(", "").replace(")", "").replace("'", "''") if soup.select_one("rt") else p_name
    p_position = soup.select_one(".bb-profile__position").text
    p_uni_number = soup.select_one(".bb-profile__number").text

    others = [item.text for item in soup.select(".bb-profile__text")]

    birth_place, born_date, height, weight, blood_type, pitch_bat = others[:6]
    pitching = pitch_bat[0]
    batting = pitch_bat[3]

    born_date = born_date.split("(")[0].replace("年", "/").replace("月", "/").replace("日", "")
    height = height.replace("cm", "")
    weight = weight.replace("kg", "")

    profile_titles = [item.text for item in soup.select(".bb-profile__title")]

    draft_year, draft_rank, pro_year, career_, major_title = "", "", "", "", ""
    if len(profile_titles) == 10:
        draft_year, draft_rank = others[6].split("年")
        draft_rank = draft_rank.replace("(", "").replace(")", "")
        pro_year, career_, major_title = others[7:10]
    elif len(profile_titles) == 9 and profile_titles[7] == "プロ通算年":
        draft_year, draft_rank = others[6].split("年")
        draft_rank = draft_rank.replace("(", "").replace(")", "")
        pro_year, career_ = others[7:9]
    elif len(profile_titles) == 9 and profile_titles[7] == "経歴":
        pro_year, career_, major_title = others[6:9]
    elif len(profile_titles) == 8:
        pro_year, career_ = others[6:8]

    koshien_f = 1 if "(甲)" in career_ else 0

    draft_year = draft_year.strip()
    draft_rank = draft_rank.strip()
    pro_year = pro_year.replace("年", "").strip()
    career_ = career_.strip()
    major_title = major_title.strip()

    return p_name, p_name_kana, p_position, p_uni_number, birth_place, born_date, height, weight, blood_type, pitching, batting, draft_year, draft_rank, pro_year, career_, koshien_f, major_title

def get_player_data(player_id):
    player_url = f"https://baseball.yahoo.co.jp/npb/player/{player_id}/top"
    res = requests.get(player_url)
    res.raise_for_status()

    soup = BeautifulSoup(res.text, "html.parser")
    return [player_id] + list(get_player_details(soup))

def save_to_csv(player_data):
    filename = player_data[0] + ".csv"
    with open(filename, "w", newline="", encoding='utf8') as f:
        writer = csv.writer(f)
        writer.writerow(player_data)

if __name__ == "__main__":
    player_id = "1700084"
    player_data = get_player_data(player_id)
    print(*player_data, sep="\n")
    save_to_csv(player_data)

コメント(頭に#がついている記述)について

コメントとは、頭に#を入れることでプログラムから処理の対象外とするために使用します。これは何を意味しているものなのかな?と人間がすぐに理解できるように追加しているものです。

# GetNPBPlayer01.py
# yahooの選手情報からプロフィールを取得しCSVへ保存

各関数についての説明

get_player_details 関数

この関数は、特定の選手の詳細情報を抽出するための関数です。soup(BeautifulSoupオブジェクト)を受け取り、必要な情報をHTML要素から抽出しています。

  • p_name, p_name_kana, p_position, p_uni_number:それぞれ選手の名前、カナ名、ポジション、ユニフォーム番号を抽出します。

  • birth_place, born_date, height, weight, blood_type, pitching, batting:出身地、生年月日、身長、体重、血液型、投げ方、打ち方を抽出します。

  • 生年月日は年月日のフォーマットに整形され、身長と体重は単位を取り除いて数字のみに整形されます。

  • draft_year, draft_rank, pro_year, career_, major_title:ドラフト年、ドラフト順位、プロ入り年、経歴、主要タイトルを条件に応じて抽出します。

  • koshien_f:選手が甲子園出場経験があるかどうかのフラグ(甲子園出場があれば1、なければ0)。

get_player_data 関数

選手のIDを受け取り、Yahoo! ベースボールの選手情報ページからデータを取得し、get_player_details 関数にHTMLを解析させる関数です。

今回はヤクルトの村上宗隆選手のページから情報を取得しています。
https://baseball.yahoo.co.jp/npb/player/1700084/top

save_to_csv 関数

抽出した選手データをCSVファイルに保存します。ファイル名は選手IDから作成されます。

メインブロック (__main__)

  • 特定の選手ID (1700084)※ここでヤクルトの村上宗隆選手 のデータを取得し、表示してからCSVファイルに保存します。

このコードを実行すると「1700084.csv」が出力され、中身を開くとカンマ区切りで取得した情報が記述されています。

このままのコードだと、エラーが発生した場合の処理が入っていないので、以下にエラー処理を追加したコードを記述します。

import requests
from bs4 import BeautifulSoup
import csv

def get_player_details(soup):
    try:
        p_name = soup.select_one("h1:nth-child(4)").text if soup.select_one("h1:nth-child(4)") else ""
        p_name_kana = soup.select_one("rt").text.replace("(", "").replace(")", "").replace("'", "''") if soup.select_one("rt") else p_name
        p_position = soup.select_one(".bb-profile__position").text
        p_uni_number = soup.select_one(".bb-profile__number").text

        others = [item.text for item in soup.select(".bb-profile__text")]

        birth_place, born_date, height, weight, blood_type, pitch_bat = others[:6]
        pitching = pitch_bat[0]
        batting = pitch_bat[3]

        born_date = born_date.split("(")[0].replace("年", "/").replace("月", "/").replace("日", "")
        height = height.replace("cm", "")
        weight = weight.replace("kg", "")

        profile_titles = [item.text for item in soup.select(".bb-profile__title")]

        draft_year, draft_rank, pro_year, career_, major_title = "", "", "", "", ""
        if len(profile_titles) == 10:
            draft_year, draft_rank = others[6].split("年")
            draft_rank = draft_rank.replace("(", "").replace(")", "")
            pro_year, career_, major_title = others[7:10]
        elif len(profile_titles) == 9 and profile_titles[7] == "プロ通算年":
            draft_year, draft_rank = others[6].split("年")
            draft_rank = draft_rank.replace("(", "").replace(")", "")
            pro_year, career_ = others[7:9]
        elif len(profile_titles) == 9 and profile_titles[7] == "経歴":
            pro_year, career_, major_title = others[6:9]
        elif len(profile_titles) == 8:
            pro_year, career_ = others[6:8]

        koshien_f = 1 if "(甲)" in career_ else 0

        draft_year = draft_year.strip()
        draft_rank = draft_rank.strip()
        pro_year = pro_year.replace("年", "").strip()
        career_ = career_.strip()
        major_title = major_title.strip()

        return p_name, p_name_kana, p_position, p_uni_number, birth_place, born_date, height, weight, blood_type, pitching, batting, draft_year, draft_rank, pro_year, career_, koshien_f, major_title
    except Exception as e:
        print(f"Error extracting player details: {e}")
        return ["" for _ in range(17)]  # Return empty strings for each field on failure

def get_player_data(player_id):
    try:
        player_url = f"https://baseball.yahoo.co.jp/npb/player/{player_id}/top"
        res = requests.get(player_url)
        res.raise_for_status()
        soup = BeautifulSoup(res.text, "html.parser")
        return [player_id] + list(get_player_details(soup))
    except requests.RequestException as e:
        print(f"Network error: {e}")
        return [player_id] + ["" for _ in range(17)]  # Include player ID and empty strings for failure

def save_to_csv(player_data):
    try:
        filename = player_data[0] + ".csv"
        with open(filename, "w", newline="", encoding='utf8') as f:
            writer = csv.writer(f)
            writer.writerow(player_data)
    except Exception as e:
        print(f"Error saving data to CSV: {e}")

if __name__ == "__main__":
    player_id = "1700084"
    try:
        player_data = get_player_data(player_id)
        print(*player_data, sep="\n")
        save_to_csv(player_data)
    except Exception as e:
        print(f"An unexpected error occurred: {e}")

追加されたエラーハンドリング

  1. ネットワークエラー: get_player_data関数でrequests.RequestExceptionをキャッチして、ネットワークリクエスト失敗時に適切なエラーメッセージを表示し、空のデータを返します。

  2. データ抽出エラー: get_player_details関数でExceptionをキャッチして、HTML要素の抽出でエラーが発生した場合に対処します。

  3. ファイル書き込みエラー: save_to_csv関数でファイルの保存中に発生する可能性のあるエラーをキャッチします。

  4. 予期せぬエラー: メインブロックで全体的なエラーハンドリングを行い、予期しない例外が発生した場合に対応します。

以上、簡単ではありますが、選手情報を取得するPythonコードを公開いたしました。

ありがとうございます。いただいたサポートはサイトの運営費に使用させていただきます。