日本株の株価を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からの画像