日本株の決算情報と財務情報をpythonでスクレイピングを使って取得する
前回の続きです。前回は複数銘柄のPERや配当利回り等をスクレイピングで取得しました。今回は決算情報をスクレイピングで取得します。
[1] 取得する情報
前回以前に引き続き「みんなの株式」から情報を取得させてもらいます。
[1-1] 決算情報と財務情報
今回取得するデータは期ごとの決算情報と財務情報です。以下は、JR東日本のものです。
[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要素に分かれています。
まずは、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] ROAとROEを算出する
# ROAとROEを求める df['ROA'] = df['純利益'] / df['総資産'] * 100 df['ROE'] = df['純利益'] / df['純資産'] * 100
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 |
終わりに
複数銘柄の決算情報を取得し、ROAとROEを求めることができました。次回は取得したデータを可視化します。
*1:Lorenzo CafaroによるPixabayからの画像