日本株の株価をpythonでスクレイピングを使って取得しmatplotlibで可視化する(単一銘柄)
過去数年分の株価をスクレイピングで取得して可視化する手順を紹介します。最終的に以下のようなグラフを作成します。
各銘柄の時価総額や決算情報を取得する手順や、ライブラリ(pandas-datareader)を用いて株価を取得する手順も別の記事にまとめています。
[1] 取得する情報
[1-1] 株式投資メモから取得
株価は株式投資メモから取得させてもらいます。
株式投資メモでは、各銘柄の株価が過去数十年分にわたって閲覧することができます。CSVファイルでダウンロードすることも可能です。
[1-2] URL
URLはhttps://kabuoji3.com/stock/(証券コード)/(年)/
になっています。JR東日本(証券コード=9020)の2020年の株価であればhttps://kabuoji3.com/stock/9020/2020/
です。
[1-3] HTMLの構成
株価はtable要素に格納されています。tbody要素がありますが、1つ目以外はタグの開始側がなく機能していないので無視して構わないです。
<table class="stock_table stock_data_table"> <thead> <tr> <th>日付</th> <th>始値</th> <th>高値</th> <th>安値</th> <th>終値</th> <th>出来高</th> <th>終値調整</th> </tr> </thead> <tbody> <tr> <td>2020-01-06</td> <td>9785</td> <td>9816</td> <td>9670</td> <td>9676</td> <td>1179000</td> <td>9676</td> </tr> </tbody> <tr> <td>2020-01-07</td> <td>9727</td> <td>9801</td> <td>9701</td> <td>9774</td> <td>900000</td> <td>9774</td> </tr> </tbody> 〜〜〜(中略)〜〜〜 </table>
[2] 403 Forbiddenで上手くいかないので解決する
[2-1] ソースコードと事象
以下のソースコードを実行したところ「403 Forbidden」が返ってきてしまいました。アクセスが拒否されたということです。
code = 9020 year = 2020 # 指定URLのHTMLデータを取得 url = 'https://kabuoji3.com/stock/{0:d}/{1:d}/'.format(code, year) html = requests.get(url) print(html) # <Response [403]> print(html.content) # b'<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"> # <html><head>\n<title>403 Forbidden</title> # </head><body>\n<h1>Forbidden</h1> # <p>You don\'t have permission to access this resource.</p> # </body></html>\n'
[2-2] 原因
非ブラウザのリクエストを禁止しているようです。スクレイピングを防ぐ意図で禁止しているのかと思いましたが、規約やrobots.txtを確認するとそうではないようです。
そこで今回は、ブラウザからのリクエストと認識されるようにしていきます。クライアントが何なのかは、HTTPヘッダの'User-Agent'に書かれます。なので、'User-Agent'の内容をブラウザからのリクエストに書き換えます。
なお、'User-Agent'については以下の記事に詳しい説明が書かれておりました。
UserAgentからOS/ブラウザなどの調べかたのまとめ - Qiita
[2-3] 解決法
確認くんにアクセスすると「現在のブラウザー」欄に使用しているブラウザの情報が表示されています。その内容をHTMLヘッダにセットします。
以下のソースではhtml_headers
というディクショナリを用意し、keyを'User-Agent'にし、valueに確認くんで表示された内容をコピーしています。
# 指定URLのHTMLデータを取得 url = 'https://kabuoji3.com/stock/{0:d}/{1:d}/'.format(code, year) html_headers ={'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_0_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36'} html = requests.get(url, headers=html_headers)
[3] スクレイピング
[3-1] ソースコード
JR東日本(証券コード=9020)の2016年〜2020年の株価を取得してみます。
code = 9020 # 証券コード start_year = 2016 # 開始年 end_year = 2020 # 終了年 df = None headers = None # 指定した年数分の株価を取得する years = range(start_year, end_year+1) for year in years: # 指定URLのHTMLデータを取得 url = 'https://kabuoji3.com/stock/{0:d}/{1:d}/'.format(code, year) html_headers ={'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_0_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36'} html = requests.get(url, headers=html_headers) # BeautifulSoupのHTMLパーサーを生成 bs = BeautifulSoup(html.content, "html.parser") # <table>要素を取得 table = bs.find('table') # <table>要素内のヘッダ情報を取得する(初回のみ)。 # (日付, 始値, 高値, 安値, 終値, 出来高, 終値調整) if headers is None: headers = [] thead_th = table.find('thead').find_all('th') for th in thead_th: headers.append(th.text) # <tr>要素のデータを取得する。 rows = [] tr_all = table.find_all('tr') for i, tr in enumerate(tr_all): # 最初の行は<thead>要素なので飛ばす if i==0: continue # <tr>要素内の<td>要素を取得する。 row = [] td_all = tr.find_all('td') for td in td_all: row.append(td.text) # 1行のデータをリストに追加 rows.append(row) # DataFrameを生成する df_tmp = pd.DataFrame(rows, columns=headers) # DataFrameを結合する if df is None: df = df_tmp else: df = pd.concat([df, df_tmp]) # 1秒ディレイ time.sleep(1) print(df.head()) # 日付 始値 高値 安値 終値 出来高 終値調整 # 0 2016-01-04 11360 11410 11100 11125 748400 11125 # 1 2016-01-05 11100 11300 11035 11205 896700 11205 # 2 2016-01-06 11275 11400 11055 11135 699200 11135 # 3 2016-01-07 11255 11305 11040 11050 1095000 11050 # 4 2016-01-08 10950 11180 10855 10875 1100300 10875
[3-2] データ型を変換する
取得したデータの型を確認すると、すべて'object'型になっています。このままでは、グラフ表示や移動平均算出の際に都合が悪いです。
print(df.dtypes) # 日付 object # 始値 object # 高値 object # 安値 object # 終値 object # 出来高 object # 終値調整 object # dtype: object
日付はdatatime型、それ以外はfloat型に変換します。
# 列とデータ型の組み合わせを設定 dtypes = {} for column in df.columns: if column == '日付': dtypes[column] = 'datetime64' else: dtypes[column] = 'float64' # データ型を変換 df = df.astype(dtypes) # インデックスを日付に変更 df = df.set_index('日付')
[4] 可視化
[4-1] 終値をグラフ表示
まずは、終値を単純に折れ線グラフにしてみます。
# FigureとAxesを取得 fig = plt.figure() ax = fig.add_subplot(1,1,1) # 終値の折れ線グラフを追加 x = df['日付'] y = df['終値'] ax.plot(x, y, label='終値', linewidth=2.0) # 目盛り線を表示 ax.grid(color='gray', linestyle='--', linewidth=0.5) # 凡例を表示 ax.legend() # グラフを表示 fig.show()
[4-2] 移動平均を算出
続いて、チャートでよくみる移動平均を算出します。
df['5日移動平均'] = df['終値'].rolling(window=5).mean() df['25日移動平均'] = df['終値'].rolling(window=25).mean() df['75日移動平均'] = df['終値'].rolling(window=75).mean()
[4-3] 移動平均をグラフ表示
移動平均をグラフ表示するにあたり、2020/01/01以降のデータに絞ります。
# 2020/01/01以降のデータを抽出 df = df['2020-01-01':]
グラフ表示してみると以下のようになりました。以下はJR東日本の株価です。コロナウィルスの影響もあり、長期で見ると株価は下がり続けています。
# FigureとAxesを取得 fig = plt.figure() ax = fig.add_subplot(1,1,1) # 終値の折れ線グラフを追加 x = df['日付'] y = df['終値'] ax.plot(x, y, label='終値', linewidth=2.0) # 移動平均の折れ線グラフを追加 average_columns = ['5日移動平均', '25日移動平均', '75日移動平均'] for column in average_columns: x = df['日付'] y = df[column] ax.plot(x, y, label=column, linewidth=1.0) # 目盛り線を表示 ax.grid(color='gray', linestyle='--', linewidth=0.5) # 凡例を表示 ax.legend() # グラフを表示 fig.show()
終わりに
株価をスクレイピングで取得してグラフ表示することができました。
しかし、単一銘柄の株価を見るだけであれば、色々なところで簡単に見れるので、自分でわざわざ作る価値はそれほどないでしょう。
次回は複数銘柄の比較と可視化を行い、作る価値のあるデータを作っていきます。
出典
- アイキャッチはGerd AltmannによるPixabayからの画像
日本株の情報をスクレイピングで取得しmatplotlibで可視化する
前回の続きです。前回は、複数銘柄の決算情報と財務情報をスクレイピングで取得しました。今回は前回取得した情報を可視化します。
[1] 基本的な指標の可視化
まずは前々回取得した以下の基本的な指標データを可視化してみます。
配当利回り | PER(調整後) | PSR | PBR | 出来高(千株) | 時価総額(兆円) | |
---|---|---|---|---|---|---|
JR東日本 | 2.53 | 12.39 | 0.83 | 0.77 | 1965.8 | 2.458450 |
JR東海 | 1.04 | 7.07 | 1.60 | 0.76 | 701.9 | 2.957130 |
JR西日本 | 3.59 | 10.85 | 0.64 | 0.79 | 1247.9 | 0.970065 |
東急 | 1.67 | 19.61 | 0.73 | 1.05 | 1104.6 | 0.856696 |
近鉄 | 1.05 | 43.77 | 0.75 | 2.22 | 387.7 | 0.902784 |
平均値 | 1.98 | 18.74 | 0.91 | 1.12 | 1081.6 | 1.629025 |
標準偏差 | 1.09 | 14.71 | 0.39 | 0.63 | 599.1 | 1.001244 |
[1-1] matplotlibをインストール
pip3 install matplotlib --user
[1-2] PERの可視化
まずは、PERを棒グラフで可視化します。
import matplotlib.pyplot as plt # '標準偏差'の列を削除 df = df.drop(index='標準偏差') # FigureとAxesを取得 fig = plt.figure() ax = fig.add_subplot(111) # グラフ表示するデータを取得 per = df['PER(調整後)'] # PER xpos = np.arange(len(per)) # X軸上の位置(0, 1, 2, ...) # 棒グラフを作成 ax.bar(xpos, per) # X軸に銘柄名を表示 ax.set(xticks=xpos, xticklabels=df.index) # 補助線を描画 ax.grid(axis='y', color='gray', ls='--') # 凡例を表示 ax.legend(['PER(調整後)']) # グラフを表示 fig.show()
結果、次のようなグラフが得られます。
グラフ内の日本語は文字化けする場合があります。その場合は、日本語フォントのインストールが必要です(Amazon Linuxの場合は拙著参照)。
[1-3] PSRとPBRの可視化
次に、PSRとPBRを可視化します。
# 可視化する列 columns = ['PSR', 'PBR'] # FigureとAxesを取得 fig = plt.figure() ax = fig.add_subplot(111) # データ数を取得 num_data = df.shape[0] # 銘柄の数 num_column = len(columns) # 可視化する列の数 # 棒グラフを横並びで表示するためのパラメータ width = 0.8 / num_column # 棒グラフの幅 xpos = np.arange(num_data) # X軸上の位置 # 指定した列数分ループ for i in range(num_column): col = columns[i] x = xpos + width * i y = df[col] # 棒グラフを表示 ax.bar(x, y, width=width, align='center') # X軸の目盛位置を調整し、銘柄名を表示 labels = df.index.values offset = width / 2 * (num_column - 1) ax.set(xticks=xpos+offset, xticklabels=labels) # 補助線を描画 ax.grid(axis='y', color='gray', ls='--') # 凡例を表示 ax.legend(columns) # グラフを表示 fig.show()
結果、次のグラフが得られました。 棒グラフを横並びにするのには、少々手間がかかります。下記サイトを参考にさせていただきました。
【Python】matplotlibで色んな種類の棒グラフを表示する方法 | 侍エンジニアブログ
近鉄GHDのPBRが高いですね。PBR = 時価総額 / 株主資本ですから、B/S上の価値よりも市場に高く評価されていると言えます。
[2] 決算情報の可視化
続いて、前回得た決算情報・財務情報を可視化します。
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 |
[2-1] ROEの可視化
まずは、ROEを折れ線グラフで可視化します。
# FigureとAxesを取得 fig = plt.figure() ax = fig.add_subplot(1,1,1) # 銘柄の名称リスト brand_names = list(df.index.unique('名称')) # 可視化するデータの名称 data_name = 'ROE' # 全銘柄のデータを折れ線グラフに表示 for brand_name in brand_names: brand_df = df.loc[(brand_name,)] # 指定した銘柄のデータ x = brand_df.index # 決算期 y = brand_df[data_name] # 可視化するデータ # 折れ線グラフ表示 ax.plot(x, y, marker='o') # 補助線を描画 ax.grid(axis='y', color='gray', ls='--') # 軸ラベルをセット plt.xlabel(data_name, size=15) # 凡例を表示 ax.legend(brand_names) # グラフを表示 fig.show()
結果、次のグラフが得られました。
[2-2] ROEとROAの可視化
次に、ROEとROAをセットで可視化して、1つの図に収めます。
# 可視化するデータ data_names = ['ROE', 'ROA'] # Figurを取得 fig = plt.figure(figsize=(10, 4)) # 指定した全データをデータ別に折れ線グラフで表示する for i, data_name in enumerate(data_names): # Axesを取得 ax = fig.add_subplot(1, 2, i+1) # 銘柄の名称リスト brand_names = list(df.index.unique('名称')) # 全銘柄のデータを折れ線グラフに表示 for brand_name in brand_names: brand_df = df.loc[(brand_name,)] # 指定した銘柄のデータ x = brand_df.index # 決算期 y = brand_df[data_name] # 可視化するデータ # 折れ線グラフ表示 ax.plot(x, y, marker='o') # 補助線を描画 ax.grid(axis='y', color='gray', ls='--') # 軸ラベルをセット plt.xlabel(data_name, size=15) # 凡例を表示 ax.legend(brand_names) # 不要な余白を削る plt.tight_layout() # グラフを表示 fig.show()
結果、次のグラフが得られました。
JR東海は利益率が高いことで知られていますが、ROE・ROAが高いことからもそのことが窺えます。
また、2020年3月期はどの鉄道会社もROE・ROAが低下しています。これは、2020年2月頃からコロナウィルスの感染拡大があったことや、2019年10月の増税の影響によるものではないかと推測できます。
[2-3] 1銘柄の決算情報を可視化
詳細は割愛しますが、これまで紹介してきた内容を組み合わせると、次のようなグラフを作ることも可能です。
2018年度末(2019年3月期)までは、売上高・利益ともに伸びていましたが、2020年3月期は落ち込んでいます。
終わりに
今回は、スクレイピングで取得した情報を「matplotlib」で可視化しました。
今回行った可視化は複雑なものではありませんが、それでも可視化することによって得られる情報はあります。
株に関して言えば、個々の銘柄の情報は簡単に調べることが出来ますが、複数銘柄を比較できるサイトが少ないです。比較はできても、自分が比較したい情報は無いという場合もあります。
まずは、自力でExcelなどに手入力してみて、自動化したくなったら今回のようにするとよいのかもしれません。
*1:Lorenzo CafaroによるPixabayからの画像
日本株の決算情報と財務情報を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からの画像
日本株の基礎指標(PERや配当利回り)をpythonでスクレイピングを使って取得する (2)
前回の続きです。前回は「みんなの株式」から、単一銘柄のPERや配当利回りをスクレイピングで取得しました。今回は複数銘柄の情報を統合してみます。
[1] DataFrameにまとめる
pandasのDataFrameに複数銘柄の情報をまとめます。pandasはデータ分析用のライブラリです。DataFrameはpandasの基本構造で表形式のデータです。
[1-1] pandasのインストール
pip3 install numpy --user pip3 install pandas --user
[1-2] DataFrameの作成
鉄道関係 5銘柄の情報を取得し、DataFrameにまとめます。get_basic_info
は前回の内容を関数化したものです。
jre_dict = get_basic_info(9020) # JR東日本 jrc_dict = get_basic_info(9022) # JR東海 jrw_dict = get_basic_info(9021) # JR西日本 tokyu_dict = get_basic_info(9005) # 東急 kintetsu_dict = get_basic_info(9041) # 近鉄 jre_sr = pd.Series(jre_dict.values(), index=jre_dict.keys(), name='JR東日本') jrc_sr = pd.Series(jrc_dict.values(), index=jrc_dict.keys(), name='JR東海') jrw_sr = pd.Series(jrw_dict.values(), index=jrw_dict.keys(), name='JR西日本') tokyu_sr = pd.Series(tokyu_dict.values(), index=tokyu_dict.keys(), name='東急') kintetsu_sr = pd.Series(kintetsu_dict.values(), index=kintetsu_dict.keys(), name='近鉄') df = pd.DataFrame([jre_sr, jrw_sr, jrc_sr, tokyu_sr, kintetsu_sr])
結果、以下のような内容にまとめることができました。
始値 | 高値 | 安値 | 配当利回り | ... | |
---|---|---|---|---|---|
JR東日本 | 6,510.0円 | 6,580.0円 | 6,479.0円 | 2.53% | ... |
JR東海 | 14,280.0円 | 14,450.0円 | 14,280.0円 | 1.04% | ... |
JR西日本 | 5,000.0円 | 5,078.0円 | 4,959.0円 | 3.59% | ... |
東急 | 1,372.0円 | 1,379.0円 | 1,360.0円 | 1.67% | ... |
近鉄 | 4,745.0円 | 4,770.0円 | 4,720.0円 | 1.05% | ... |
[2] 単位を削る
'円'や'%'などの単位が付与された状態ですが、平均値を出す場合など計算する際には邪魔になります。DataFrameから単位を削っていきます。
[2-1] 正規表現を使う
正規表現を使って、不要なものを削っていきます。
以下は株価(始値, 高値)の例です。カンマと円が不要なので削っていきます。株価は銘柄ごとに異なっており、5桁のものもあれば3桁のものもあるため、どれにも対応できるようにしています。
import re yen = '1,234.5円' # 正規表現で'円'より前の文字列を抽出 yen_re = re.search(r"([+-]?\d{1,3}(,\d{3})*\.\d+)円", yen) yen = yen_re.group(1) # カンマを削除 yen = value.replace(',', '') # 実数に変換 yen = float(yen) print(yen) # 1234.5
[2-2] すべての列に適用する
[2-1]と同様の処理を、すべての列に適用します。DataFrameのmapメソッドを使い、列ごとに変換処理を行っていきます。
[2-2-1] 変換用の関数を用意する
まずは、以下のような変換関数を用意します。円, 株といった単位ごとに関数を用意してもよいのですが、面倒なので全単位に対応できる関数を用意しました。
# 単位を削除する関数 def trim_unit(x): # 単位=円を削除 yen_re = re.search(r"(\d{1,3}(,\d{3})*\.\d+)円", x) if yen_re: value = yen_re.group(1) value = value.replace(',', '') return np.float64(value) # 単位=%を削除 perc_re = re.search(r"(\d+\.\d+)%", x) if perc_re: value = perc_re.group(1) return np.float64(value) # 単位=株を削除 stock_re = re.search(r"(\d{1,3}(,\d{3})*)株", x) if stock_re: value = stock_re.group(1) value = value.replace(',', '') return np.int64(value) # 単位=倍を削除 times_re = re.search(r"(\d+\.\d+)倍", x) if times_re: value = times_re.group(1) return np.float64(value) # 単位=百万円を削除 million_yen_re = re.search(r"(\d{1,3}(,\d{3})*)百万円", x) if million_yen_re: value = million_yen_re.group(1) value = value.replace(',', '') value = np.int64(value) * 1000000 return value # 単位=千株を削除 thousand_stock_re = re.search(r"(\d{1,3}(,\d{3})*)千株", x) if thousand_stock_re: value = thousand_stock_re.group(1) value = value.replace(',', '') value = np.int64(value) * 1000 return value return x
[2-2-2] map関数で各列に変換処理を行う
pandasのmap関数を使用して、上に挙げたtrim_unit
を各列に対して適用します。
# 各列に対して、trim_unitを適用する new_df = df.copy() for col in df.columns: new_df[col] = new_df[col].map(lambda v : trim_unit(v))
[3] 各列の統計量を算出する
[3-1] 平均値と標準偏差の算出
単位を削り数値に変換できたので、各列の平均値と標準偏差を求めます。
mean = new_df.mean() # 平均値 std = new_df.std() # 標準偏差
求めた平均値と標準偏差をDataFrameにまとめると、以下の結果が得られました。
statistics = pd.DataFrame( {'平均値': new_df.mean(), '標準偏差': new_df.std()})
平均値 | 標準偏差 | |
---|---|---|
始値 | 6381.4 | 4798.0 |
高値 | 6451.4 | 4858.9 |
安値 | 6359.6 | 4806.1 |
配当利回り | 1.976 | 1.088 |
単元株数 | 100 | 0 |
PER(調整後) | 18.738 | 14.714 |
PSR | 0.91 | 0.39 |
PBR | 1.118 | 0.628 |
出来高 | 1.081580e+06 | 5.990959e+05 |
時価総額 | 1.629025e+12 | 1.001244e+12 |
発行済株数 | 3.181594e+08 | 1.887879e+08 |
[3-2] 統計量と各銘柄データを結合
求めた統計量を、各銘柄のデータと結合して比較してみます。
ここで比較に役に立たないと思われるデータは削ります。また、出来高と時価総額は桁数が多いので、見やすい桁数になるように単位を変換します。
# 各銘柄のデータと統計量を結合する。 new_df2 = new_df.append(statistics.T) # 出来高と時価総額の単位を変換する。 new_df2['出来高'] = new_df2['出来高'] / 1.0e+3 new_df2['時価総額'] = new_df2['時価総額'] / 1.0e+12 new_df2 = new_df2.rename(columns= {'出来高': '出来高(千株)', '時価総額': '時価総額(兆円)'}) # 不要な列を削除する。 new_df2 = new_df2.drop(columns= ['始値', '高値', '安値', '単元株数', '発行済株数', '購入金額'])
以下だけで読み取れる情報は未だ少ないですが、近鉄のみPER, PBRが大きいと分かります。
配当利回り | PER(調整後) | PSR | PBR | 出来高(千株) | 時価総額(兆円) | 株主優待 | |
---|---|---|---|---|---|---|---|
JR東日本 | 2.53 | 12.39 | 0.83 | 0.77 | 1965.8 | 2.458450 | 株主優待割引券、自社施設サービス券、人間ドック割引券 |
JR東海 | 1.04 | 7.07 | 1.60 | 0.76 | 701.9 | 2.957130 | 株主優待乗車証 |
JR西日本 | 3.59 | 10.85 | 0.64 | 0.79 | 1247.9 | 0.970065 | 鉄道優待、グループ優待 |
東急 | 1.67 | 19.61 | 0.73 | 1.05 | 1104.6 | 0.856696 | 株主優待乗車証、自社グループ優待券 |
近鉄 | 1.05 | 43.77 | 0.75 | 2.22 | 387.7 | 0.902784 | 株主優待乗車証、自社施設割引券 |
平均値 | 1.98 | 18.74 | 0.91 | 1.12 | 1081.6 | 1.629025 | NaN |
標準偏差 | 1.09 | 14.71 | 0.39 | 0.63 | 599.1 | 1.001244 | NaN |
終わりに
今回は複数銘柄の基本指標を取得し、比較するところまで行いました。次回は、決算情報の取得を行います。
*1:Lorenzo CafaroによるPixabayからの画像
日本株の基礎指標(PERや配当利回り)をpythonでスクレイピングを使って取得する (1)
本記事では、日本株の基礎指標をスクレイピングで取得する手順を紹介しています。また、スクレイピング先のサイトがスクレイピングを禁止していないか確認する方法にも触れています。
[1] きっかけ
今年はコロナウィルスやアメリカ大統領選挙の影響で、株価が大きく変動しましたね。 また、「人生100年時代」や「老後資金が2,000万円必要」とか言われたりしていますから、株式投資に興味を持ち始めた人も多いのではないかと思います。
株式投資をする際に何を基準にして売買するのかは人によって違います。「ドルコスト平均法で積立」「チャートを見る」「企業の業績を見る」など色々な方法があると思います。
私はこれまで、企業の業績を見る方法は取ってきませんでしたが、取り入れてみることにしました。ただ、どうせ取り入れるなら、面倒なところはプログラミングと絡めて自動化したいなと思いました。
しかし、下記サイトを参考にさせていただいたところ、APIを使ってお手軽に取得とはいかないようです。そこで、気象データ取得の際にも活用したスクレイピングで、企業情報を取得してみます。
別記事では、pandas-datareaderを用いたAPIでの株価取得、スクレイピングでの株価取得を紹介しています。よければ、そちらもご覧になってください。
[2] 時価総額やPERなどの指標を取得
「みんなの株式」から情報を取得させてもらいます。
今回は、時価総額やPERなどの基本的な指標を取得します。JR東日本をはじめとした鉄道会社の情報を集めます。
[3] スクレピング可能か確認する
スクレイピングを禁止しているサイトも多くあります。「みんなの株式」がスクレピング可能か否かを確認します。
スクレイピング可能であったとしても、取得元のWebサイトに負荷をかけないように気をつけましょう。
[3-1] 規約を確認する
まずは規約を確認します。例えば、Yahoo!ファイナンスではスクレイピングを禁止しています。利用規約やFAQ、ヘルプを探してみましょう。
Yahoo!ファイナンスはヘルプに書かれています(2020/11/23時点)。
Yahoo!ファイナンスでは、Yahoo!ファイナンスに掲載している株価やその他のデータを、プログラム等を用いて機械的に取得する行為(スクレイピング等)について、システムに過度の負荷がかかり、安定したサービス提供に支障をきたす恐れがあることから禁止しています。
[3-2] robots.txtの確認
Googleなどの検索エンジンのクローリング(Webサイトの巡回)を最適化するためのファイルが「robots.txt」です。robots.txtは各Webサイトで持っており、クローリング時にアクセスを許可 または 禁止するURLを定義しています。
「robots.txt」が見つからない場合もありますが、見ることができればスクレイピング先へのアクセスが禁止されていないか確認できます。
詳細は下記サイトの説明を参考にさせてもらいました。
スクレイピング、クローリングする時の注意点 — Pythonオンライン学習サービス PyQ(パイキュー)ドキュメント
みんなの株式のrobots.txtを確認してみます。スクレイピングしたいのは「stock/」以下です。具体的には「stock/銘柄のコード/」になりますが、禁止されていないようです。
User-Agent: * Allow: / Sitemap: https://assets.minkabu.jp/concerns/sitemap/sitemap.xml.gz ...(中略)... Disallow: /user/follow/ ...(中略)... Disallow: /stocks/pick/open_form Disallow: /stock/*/community/edit ...(中略)...
[4] ライブラリをインストールする
pip3コマンドでRequestsとBeautifulSoupをインストールします。
pip3 install requests --user pip3 install bs4 --user
また、取得したデータを加工しグラフ表示するために、pandasやmatplotlibもインストールします。
pip3 install numpy --user pip3 install pandas --user pip3 install matplotlib --user
[5] HTMLの中身を確認する
ピンク枠で囲った部分(単元株数やPER)のHTMLを、ブラウザのデベロッパーツールで確認します。
以下のように、リスト要素(ul, li)を使っており、li要素の中にさらにリスト(dt, dd)を持っています。欲しい情報はdt, dd要素に格納されていることが分かりました。
<div class="ly_col ly_colsize_6_fix"> <ul class=" md_list"> <li class="ly_vamd"> <dt class="ly_vamd_inner ly_colsize_3_fix wsnw">単元株数</dt><dd class="ly_vamd_inner ly_colsize_9_fix fwb tar wsnw">100株</dd> </li> <li class="ly_vamd"> <dt class="ly_vamd_inner ly_colsize_3_fix wsnw">PER<span class="fss">(調整後)</span></dt><dd class="ly_vamd_inner ly_colsize_9_fix fwb tar wsnw">12.39倍</dd> </li> <li class="ly_vamd"> <dt class="ly_vamd_inner ly_colsize_3_fix wsnw">PSR</dt><dd class="ly_vamd_inner ly_colsize_9_fix fwb tar wsnw">0.83倍</dd> </li> <li class="ly_vamd"> <dt class="ly_vamd_inner ly_colsize_3_fix wsnw">PBR</dt><dd class="ly_vamd_inner ly_colsize_9_fix fwb tar wsnw">0.77倍</dd> </li> </ul> </div>
[6] ソースコード
[6-1] HTML取得・解析開始
import requests from bs4 import BeautifulSoup # 指定URLのHTMLデータを取得 url = "https://minkabu.jp/stock/9020" html = requests.get(url) # BeautifulSoupのHTMLパーサーを生成 soup = BeautifulSoup(html.content, "html.parser")
[6-2] リスト要素の解析
ul要素のclass名"md_list"で抽出したいところですが、他の箇所でも使われていました。なので、全li要素を抽出して、li要素内のdd, dt要素を取り出していきます。
# データ格納用のディクショナリを準備 basic_info = {} # 全<li>要素を抽出 li_all = soup.find_all('li') for li in li_all: # <li>要素内の<dt>要素を抽出 dt = li.find('dt') if dt is None: # <dt>要素がなければ処理不要 continue # <li>要素内の<dd>要素を抽出 dd = li.find('dd') # <dt><dd>要素から文字列を取得 key = dt.text value = dd.text # ディクショナリに格納 basic_info[key] = value
[6-3] 取り出した結果
以下の情報が取得できました。
print(basic_info) #{'始値': '6,510.0円', # '高値': '6,580.0円', # '安値': '6,479.0円', # '配当利回り': '2.53%', # '単元株数': '100株', # 'PER(調整後)': '12.39倍', # 'PSR': '0.83倍', # 'PBR': '0.77倍', # '出来高': '1,965,800株', # '時価総額': '2,458,450百万円', # '発行済株数': '377,932千株', # '株主優待': '株主優待割引券、自社施設サービス券、人間ドック割引券', # '購入金額': '最安---'}
意図せず、広い範囲の情報が取れましたが、結果オーライです。
「購入金額」のみ情報を取ってこれていませんが、これはスクリプトによって表示する情報(HTMLの中身)を変えていることが理由です。
終わりに
今回は1銘柄の基本指標を取得するところまで行いました。次回は、複数銘柄の情報を取得し統合します。
*1:Lorenzo CafaroによるPixabayからの画像