どこにでもいる30代SEの学習ブログ

主にプログラミング関連の学習内容。読んだ本の感想や株式投資についても書いてます。

日本株の決算情報と財務情報をpythonでスクレイピングを使って取得する

f:id:predora005:20201123102206j:plain
*1

前回の続きです。前回は複数銘柄のPERや配当利回り等をスクレイピングで取得しました。今回は決算情報をスクレイピングで取得します。

predora005.hatenablog.com

[1] 取得する情報

前回以前に引き続き「みんなの株式」から情報を取得させてもらいます。

[1-1] 決算情報と財務情報

今回取得するデータは期ごとの決算情報と財務情報です。以下は、JR東日本のものです。

f:id:predora005:20201123164919p:plain

[1-2] HTMLの中身

決算情報の表に着目すると、以下のHTMLになっていました。

<div class="md_box">
  <table class="data_table md_table is_fix" data-table="">
    <caption class="md_sub_index"><div class="title_box">決算情報</div></caption>
    <thead>
      <tr>
        <th class="w100p">決算期<p class="fsm">(決算発表日)</p></th>
        <th class="vamd">売上高</th>
        <th class="vamd">営業利益</th>
        <th class="vamd">経常利益</th>
        <th class="vamd">純利益</th>
        <th class="vamd">1株益</th>
      </tr>
    </thead>
    <tbody>
        <tr>
          <th class="vamd">2020年<span class="fwn fsm">3月期</span><p class="fsm">(2020/04/28)</p></th>
          <td class="num vamd">2,946,639</td>
          <td class="num vamd">380,841</td>
          <td class="num vamd">339,525</td>
          <td class="num vamd">198,428</td>
          <td class="num vamd">524.91</td>
        </tr>
        <tr>
          <th class="vamd">2019年<span class="fwn fsm">3月期</span><p class="fsm">(2019/04/25)</p></th>
          <td class="num vamd">3,002,043</td>
          <td class="num vamd">484,860</td>
          <td class="num vamd">443,267</td>
          <td class="num vamd">295,216</td>
          <td class="num vamd">773.26</td>
        </tr>
        ...(中略)...
    </tbody>
  </table>
</div>

[2] スクレイピング

[2-1] 複数の表から目当ての表を特定する

table要素は決算情報だけでなく、財務情報など他にも複数の表にありました。まずは、table要素の中から決算情報の表を特定します。

caption要素のタイトルが格納されているので、caption要素の文字列が何かで判断します。

code = 9020 # JR東日本の証券コード

# 指定URLのHTMLデータを取得
url = "https://minkabu.jp/stock/{0:d}/settlement".format(code)
html = requests.get(url)

# BeautifulSoupのHTMLパーサーを生成
soup = BeautifulSoup(html.content, "html.parser")

# 全<table>要素を抽出
table_all = soup.find_all('table')

# 決算情報の<table>要素を検索する。
fin_table1 = None
for table in table_all:
    
    # <caption>要素を取得
    caption = table.find('caption')
    if caption is None:
        continue
    
    # <caption>要素の文字列が目的のものと一致したら終了
    if caption.text == '決算情報':
        fin_table1 = table
        break

[2-2] table要素からデータを取得する

table要素の中は、thead要素とtbody要素に分かれています。

f:id:predora005:20201123194619p:plain

まずは、thead要素のデータを抽出します。

# <table>要素内のヘッダ情報を取得する。
headers = []
thead_th = fin_table1.find('thead').find_all('th')
for th in thead_th:
    headers.append(th.text)

次に、tbody要素のデータを抽出します。

# <table>要素内のデータを取得する。
rows = []
tbody_tr = fin_table1.find('tbody').find_all('tr')
for tr in tbody_tr:
    
    # 1行内のデータを格納するためのリスト
    row = []
    
    # <tr>要素内の<th>要素を取得する。
    th = tr.find('th')
    row.append(th.text)
    
    # <tr>要素内の<td>要素を取得する。
    td_all = tr.find_all('td')
    for td in td_all:
        row.append(td.text)
    
    # 1行のデータを格納したリストを、リストに格納
    rows.append(row)

[2-3] DataFrameに格納する

抽出したデータをDataFrameに格納します。DataFrame作成後、先頭の列である決算期をインデックスに指定します。

# DataFrameを生成する
df = pd.DataFrame(rows, columns=headers)

# 先頭の列(決算期)をインデックスに指定する
df = df.set_index(headers[0])

結果、以下のようなDataFrameが得られます。

売上高 営業利益 経常利益 純利益 1株益
決算期(決算発表日)
2020年3月期(2020/04/28) 2,946,639 380,841 339,525 198,428 524.91
2019年3月期(2019/04/25) 3,002,043 484,860 443,267 295,216 773.26
2018年3月期(2018/04/27) 2,950,156 481,295 439,969 288,957 749.20
2017年3月期(2017/04/28) 2,880,802 466,309 412,311 277,925 713.96

[3] データの加工

[3-1] 不要なデータを削る

前回と同様に、数値のカンマや単位を削っていきます。まずは、数値からカンマを削除します。

# 数値のカンマを削除する関数
def trim_camma(x):
    # 2,946,639.3のようなカンマ区切り、小数点有りの数値か否か確認する
    comma_re = re.search(r"(\d{1,3}(,\d{3})*(\.\d+){0,1})", x)
    if comma_re:
        value = comma_re.group(1)
        value = value.replace(',', '') # カンマを削除
        return np.float64(value) # 数値に変換
    
    return x

# 各列に対して、trim_cammaを適用する
new_df = df.copy()
for col in df.columns:
    new_df[col] = df[col].map(lambda v : trim_camma(v))

次に、決算期に付属している括弧の情報を削除します。削除する理由は、他の銘柄のデータを結合する際に不都合だからです。企業によって決算発表日は異なるため削除しておきます。

# 括弧内の文字列を削除する関数(括弧自体も削除する)
def remove_inparentheses(s):
    
    # 文字列末尾の括弧を削除する
    result = re.search(r"(.+)(\(.+\))", s)
    if result:
        str = result.group(1)
        return str
    
    return s

# インデックス(決算情報)の括弧内要素を削除する。
new_df.index.name = remove_inparentheses(new_df.index.name)
new_df.index = new_df.index.map(lambda s : remove_inparentheses(s))

[3-2] 複数銘柄のデータを結合する

JR東日本以外の鉄道関係銘柄についても、同様に決算情報を取得し、DataFrameに格納します。

# 上記の処理
...(中略)...

# 名称を追加し、MultiIndexにする。
df['名称'] = name
df = df.set_index('名称', append=True)

# 全銘柄データ格納用のDataFrameに1銘柄のDataFrameを追加する
if whole_df is None:
    whole_df = df
else:
    whole_df = whole_df.append(df)

各銘柄の名称と決算期単位でデータを扱えるようにMultiIndexにしておきます。MultiIndexにすると次のような構造になります。

売上高 営業利益 経常利益 ...
名称 決算期
JR東日本 2020年3月期 2,946,639 380,841 339,525 ...
2019年3月期 3,002,043 484,860 443,267 ...
JR東海 2020年3月期 1,844,647 656,163 574,282 ...
2019年3月期 1,878,137 709,775 632,653 ...

全銘柄のデータをDataFrameに追加した後、以下の処理を行うことで、名称と決算期でソートされたデータになります。

# indexを入れ替えてソートする。
whole_df = whole_df.swaplevel('名称', '決算期').sort_index()

[3-3] 財務情報も取得する

決算情報と同様に、財務情報も取得します。caption要素の文字列が'決算情報'から'財務情報'に変わっただけで、処理内容は同じです。

1株純資産 総資産 純資産 ...
名称 決算期
JR東日本 2020年3月期 8,396.81 8,537,059 3,173,427 ...
2019年3月期 8,187.64 8,359,676 3,094,378 ...
JR東海 2020年3月期 18,796.61 9,603,126 3,872,103 ...
2019年3月期 17,029.44 9,295,745 3,508,065 ...

[3-4] ROAROEを算出する

財務情報を使って、ROAROEを求めます。

  • ROA = 純利益 / 総資産
  • ROE = 純利益 / 純資産
# ROAとROEを求める
df['ROA'] = df['純利益'] / df['総資産'] * 100
df['ROE'] = df['純利益'] / df['純資産'] * 100

ROAROEの列が追加されます。

ROA ROE
名称 決算期
JR東日本 2020年3月期 2.32 6.25
2019年3月期 3.53 9.54
JR東海 2020年3月期 4.14 10.28
2019年3月期 4.72 12.51

終わりに

複数銘柄の決算情報を取得し、ROAROEを求めることができました。次回は取得したデータを可視化します。

predora005.hatenablog.com

*1:Lorenzo CafaroによるPixabayからの画像