見出し画像

Pythonで株価をWebスクレイピングしてローソク足チャートを作成してみた。

こんにちは、蝉の店です。株価予測のためのプログラミングの勉強を行っていますが、備忘録としてソースコードや解説等を残しておきたいと思いました。いずれは株価を取得して予測を行い、株価売買までコーディングしたいと思っております。

ブログとして残す以上、下手なコーディングはできない!と思い、例外処理などを勉強しました。参考としたWebページも載せています。

私としても勉強不足である点は多々ありますが、Webスクレイピングやローソク足チャートの作成などの際に私が詰まった部分等に解説を入れました。参考になればこれほど嬉しいことはありません。

本記事の目的

Pythonで日ごとに株価データをWebスクレイピングで取得してCSVファイルとして保存します。また取得した株価データから日足ローソクチャートを作成します。

① 株式投資メモから日ごとの株価データを取得する。
② 株価データを銘柄、年ごとにCSVファイルとして保存する。
③ 株価データから日足チャート図を作成し、銘柄、月ごとにjpgファイルとして保存する。

この順番にて解説していきます。動作環境はpython 3.7.7です。

ソースコード

まずは実際のソースコードを全文公開いたします。

import os
import requests
import pandas as pd
import pyppdf.patch_pyppeteer
import mplfinance as mpf
from bs4 import BeautifulSoup
from datetime import datetime
from requests_html import HTMLSession
from requests.exceptions import RequestException

# ユーザーエージェント情報
dict_headers = {
   "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
   + "AppleWebKit/537.36 (KHTML, like Gecko) "
   + "Chrome/81.0.4044.138 Safari/537.36"
}


def getStockData(str_stockCode):
   # 株式投資メモのホームページから株価データを取得する
   # 2015年から2017年のデータを銘柄ごとに取得

   dict_stockData = {}
   list_head = ['Date', 'Open', 'High', 'Low',
                'Close', 'Volume', 'Adjustment']  # ヘッダーを定義する
   list_year = list(range(2015, 2018))  # 2015年から2017年

   for int_year in list_year:
       try:
           # 株式投資メモのURL
           str_url = 'https://kabuoji3.com/stock/{}/{}/'.format(
               str_stockCode, int_year)
           # headers情報を入れないと403エラー発生
           response_kabuoji = requests.get(url=str_url, headers=dict_headers)
           response_kabuoji.raise_for_status()  # ステータスコートが200番台以外はRequestException発生
           bs_kabuoji = BeautifulSoup(response_kabuoji.content, 'html.parser')
       except RequestException as err:
           print('HTTPステータスエラー:{}'.format(err))
           return (None, -1)
       except Exception as err:
           print('予期せぬ例外が発生しました:{}'.format(err))
           return (None, -1)

       # 'tr'タグをすべて取得する
       resultSet_trTag = bs_kabuoji.find_all('tr')

       # 株価データを取得する
       list_tmp = []
       for i in range(1, len(resultSet_trTag)):  # tag_tr[0]はタイトルなのでスキップ
           list_tmp.append(
               [d.text for d in resultSet_trTag[i].find_all('td')])
       df_stockData = pd.DataFrame(list_tmp, columns=list_head)

       # 株価データを数値に変換する
       list_priceColumn = ['Open', 'High', 'Low',
                           'Close', 'Volume', 'Adjustment']
       for str_column in list_priceColumn:
           df_stockData[str_column] = df_stockData[str_column].astype(float)

       # 日付はdatetime型に変換する
       df_stockData['Date'] = [datetime.strptime(
           i, '%Y-%m-%d') for i in df_stockData['Date']]

       df_stockData = df_stockData.set_index('Date')  # 日付データをindexとする
       # str_keyは「銘柄コード_年」フォーマット
       dict_stockData['{}_{}'.format(str_stockCode, int_year)] = df_stockData

   return (dict_stockData, 1)


def saveCsvFile(dict_stockData):
   # 株価データをCSVファイルとして保存する

   for str_key, df_stockData in dict_stockData.items():
       try:
           # 銘柄ごとにフォルダ分けを行う
           str_filePath = './csvData/{}/'.format(str_key[:4])
           os.makedirs(str_filePath, exist_ok=True)
           
           # 株価データを銘柄、年ごとにCSVファイルとして保存する
           df_stockData.to_csv('{}{}.csv'.format(
               str_filePath, str_key), index=True)
       except FileNotFoundError as err:
           print('ディレクトリが存在しません:{}'.format(err))
           return -1
       except Exception as err:
           print('予期せぬ例外が発生しました:{}'.format(err))
           return -1
   return 1


def plotCandleStickChart(dict_stockData):
   # 日足チャート図を作成する

   for str_key, df_stockData in dict_stockData.items():
       try:
           # 銘柄ごと,年ごとにフォルダ分けを行う
           str_filePath = './plotImage/{}/{}/'.format(str_key[:4],str_key[-4:])
           os.makedirs(str_filePath, exist_ok=True)
           for int_month in range(1, 13):
               df_monthData = df_stockData[df_stockData.index.month == int_month]
               # 日足チャート図を銘柄、月ごとにjpgファイルとして保存する
               mpf.plot(df_monthData, type='candle', style='charles',
                       savefig='{}{}_{}.jpg'.format(str_filePath, str_key, int_month))
       except FileNotFoundError as err:
           print('ディレクトリが存在しません:{}'.format(err))
           return -1
       except Exception as err:
           print('予期せぬ例外が発生しました:{}'.format(err))
           return -1
   return 1


def execute(str_stockCode):
   # 実行関数

   # 1.日ごとの株価データをWebスクレイピングで取得する
   dict_stockData, int_result = getStockData(str_stockCode)
   if int_result == -1:
       print("株価の取得で失敗しました")
       return - 1

   # 2.株価データを銘柄、年ごとにCSVファイルとして保存する
   int_result = saveCsvFile(dict_stockData)
   if int_result == -1:
       print("CSV出力で失敗しました")
       return - 1

   # 3.株価データから銘柄、月ごとに日足チャート図を作成する
   int_result = plotCandleStickChart(dict_stockData)
   if int_result == -1:
       print("グラフ出力で失敗しました")
       return - 1

   return 1


if __name__ == '__main__':

   list_stockCode = ['2211','8228'] # 銘柄コード
   for str_stockCode in list_stockCode:
       int_result = execute(str_stockCode) # 実行関数
       if int_result == -1:
           print("実行結果:失敗")
           break
   if int_result == 1:
       print("実行結果:成功")

必要なライブラリはpipコマンド等でインストールしてください。例えばmplfinanceをインストールしたい場合は以下のように入力します。

$ pip install mplfinance

私が最初に学習した言語はC言語でした。(研究の数値計算で使用) そのため、私は変数の型が明記されていた方が分かりやすいので、変数名の「 _ 」の前に変数の型を明記しています。

では1つ1つコードを解説していきます。

株式投資メモから日ごとの株価データを取得する。

まずは日足チャートを作成するための株価データを収集します。データは株式投資メモというWebサイトから収集いたします。本サイトは無料で上場株式の日ごとの株価を提供してくれています。今回はこのサイトからWebスクレイピングを行ってみましょう。

関数 getStockData(str_stockCode) は銘柄コードをインプットとして銘柄コード、年度ごとの株価データ(dictionary型)と実行結果を返す関数です。

一番上に記載しているのはUA(ユーザーエージェント)です。

# ユーザーエージェント情報
dict_headers = {
   "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
   + "AppleWebKit/537.36 (KHTML, like Gecko) "
   + "Chrome/81.0.4044.138 Safari/537.36"
}

株式投資メモではリクエストを送る際にヘッダーにUAをつけないと403エラーが発生してしまいます。記載しているのは私のUAですが、自分のUAを確認するには「確認くん」から「現在のブラウザー」を参照します。

株価投資メモでは、日ごと(Date)の始値(Open)、高値(High)、安値(Low)、終値(Close)、出来高(Volume)、調整値(Adjustment)を掲載していますのでここでヘッダーを定義しています。また取得したい年度もここで定義しています。掲載している年度の範囲内であれば設定が可能です。

dict_stockData = {}
list_head = ['Date', 'Open', 'High', 'Low',
             'Close', 'Volume', 'Adjustment']  # ヘッダーを定義する
list_year = list(range(2015, 2018))  # 2015年から2017年
   
for int_year in list_year:

株式投資メモのURLを指定して、リクエストを取得します。先ほど上述したようにUAを指定しないと403エラーが出てしまうので指定しています。RequestExceptionはステータスコードを参照します。正常である200番台以外では例外処理を行います。またほかの予期せぬ例外に備えてExceptionを定義しています。その後 tr タグに株価データが格納されているので取り出します。

try:
    # 株式投資メモのURL
    str_url = 'https://kabuoji3.com/stock/{}/{}/'.format(
        str_stockCode, int_year)
     # headers情報を入れないと403エラー発生
     response_kabuoji = requests.get(url=str_url, headers=dict_headers)
     response_kabuoji.raise_for_status()  # ステータスコートが200番台以外はRequestException発生
     bs_kabuoji = BeautifulSoup(response_kabuoji.content, 'html.parser')
except RequestException as err:
     print('HTTPステータスエラー:{}'.format(err))
     return (None, -1)
except Exception as err:
     print('予期せぬ例外が発生しました:{}'.format(err))
     return (None, -1)
           
# 'tr'タグをすべて取得する
resultSet_trTag = bs_kabuoji.find_all('tr')

ネットワークが切断されている場合やURLが正しくない場合、以下のような実行結果となります。

HTTPステータスエラー:HTTPSConnectionPool(host='kabuoji3.com', port=443): 
Max retries exceeded with url: /stock/2211/2015/ (Caused by New
ConnectionError('<urllib3.connection.VerifiedHTTPSConnection object 
at 0x00000223E1FC1248>: Failed to establish a new connection: [Errno 
11001] getaddrinfo failed'))

またUAを指定しない場合、以下のような実行結果となります。

HTTPステータスエラー:403 Client Error: Forbidden for url: 
https://kabuoji3.com/stock/2211/2015/

取り出した株価データは、list形式で取り出してから最後にDataFrame型に変換しています。(この部分の実行時間は私の環境では約8msでした)

# 株価データを取得する
list_tmp = []
for i in range(1, len(resultSet_trTag)):  # tag_tr[0]はタイトルなのでスキップ
    list_tmp.append([d.text for d in resultSet_trTag[i].find_all('td')])
df_stockData = pd.DataFrame(list_tmp, columns=list_head)

一方上記部分は以下のようにDataFrameのappendを用いた実装も可能ですが、パフォーマンスとしてはよろしくありません。(この部分の実行時間は私の環境では約300msでした)

# 株価データを取得する
df_stockData = pd.DataFrame([], columns=list_head)
for i in range(1, len(resultSet_trTag)):  # tag_tr[0]はタイトルなのでスキップ
    df_tmp = pd.DataFrame([d.text for d in resultSet_trTag[i].find_all('td')], 
                            index=df_stockData.columns).T
    df_stockData = df_stockData.append(df_tmp)

最後に日足チャート図を作成するための準備として、株価データを数値変換および日付変換、Indexを設定します。

# 株価データを数値に変換する
list_priceColumn = ['Open', 'High', 'Low',
                    'Close', 'Volume', 'Adjustment']
for str_column in list_priceColumn:
    df_stockData[str_column] = df_stockData[str_column].astype(float)

# 日付はdatetime型に変換する
df_stockData['Date'] = [datetime.strptime(
    i, '%Y-%m-%d') for i in df_stockData['Date']]

df_stockData = df_stockData.set_index('Date')  # 日付データをindexとする
# str_keyは「銘柄コード_年」フォーマット
dict_stockData['{}_{}'.format(str_stockCode, int_year)] = df_stockData

これにてWebスクレイピング部分の実装は終了です。とある銘柄、年度のdf_stockDataを参照すると以下のような結果が返ってくると思います。

             Open   High    Low  Close     Volume  Adjustment
Date
2015-01-05  197.0  197.0  195.0  196.0   308000.0      1960.0
2015-01-06  195.0  196.0  194.0  194.0   495000.0      1940.0
2015-01-07  194.0  195.0  194.0  194.0   378000.0      1940.0
2015-01-08  194.0  196.0  194.0  195.0   235000.0      1950.0
2015-01-09  195.0  195.0  194.0  195.0   311000.0      1950.0
...           ...    ...    ...    ...        ...         ...
2015-12-24  202.0  203.0  201.0  202.0   685000.0      2020.0
2015-12-25  201.0  202.0  200.0  200.0  1873000.0      2000.0
2015-12-28  198.0  198.0  193.0  194.0  1589000.0      1940.0
2015-12-29  192.0  194.0  191.0  194.0   631000.0      1940.0
2015-12-30  194.0  194.0  192.0  192.0   301000.0      1920.0

株価データを銘柄、年ごとにCSVファイルとして保存する。

次に取得した株価データをCSVファイルに保存しましょう。ここでは銘柄、年度ごとにファイルを分けて保存します。

関数 saveCsvFile(dict_stockData) は先ほど解説した関数 getStockData(str_stockCode) にて取得した銘柄コード、年度ごとの株価データ(dictionary型)をインプットに、CSVファイルとして保存した結果を返す関数です。

銘柄ごとにフォルダ分け(および作成)を行います。この時 os.makedirs の exist_ok=True によって、すでにフォルダが作成されていてもエラーは発生しません。

その後CSVファイルに出力しています。ここでも例外をキャッチしています。

def saveCsvFile(dict_stockData):
   # 株価データをCSVファイルとして保存する

   for str_key, df_stockData in dict_stockData.items():
       try:
           # 銘柄ごとにフォルダ分けを行う
           str_filePath = './csvData/{}/'.format(str_key[:4])
           os.makedirs(str_filePath, exist_ok=True)
           
           # 株価データを銘柄、年ごとにCSVファイルとして保存する
           df_stockData.to_csv('{}{}.csv'.format(
               str_filePath, str_key), index=True)
       except FileNotFoundError as err:
           print('ディレクトリが存在しません:{}'.format(err))
           return -1
       except Exception as err:
           print('予期せぬ例外が発生しました:{}'.format(err))
           return -1
   return 1

実行した結果、以下のようなファイル構成となっています。(指定した銘柄おコード及び年度によって変化します)

main.py(実行ファイル名)
csvData
 |----2211
 |  |--2211_2015.csv
 |  |--2211_2016.csv
 |  |--2211_2017.csv
 |----8228
    |--8228_2015.csv
    |--8228_2016.csv
    |--8228_2017.csv 

またCSVファイルの中身は以下画像のようになっています。

画像1

株価データから日足チャート図を作成し、銘柄、月ごとにjpgファイルとして保存する。

最後に日足チャート図を作成しましょう。ここでは銘柄、月度ごとにファイルを分けて保存します。
※年度ごとに分けた場合、図が小さくなる恐れがあります。(私はそうでした)

関数 plotCandleStickChart(dict_stockData) は先ほど解説した関数 getStockData(str_stockCode) にて取得した銘柄コード、年度ごとの株価データ(dictionary型)をインプットに、日足チャート図を作成しjpgで保存した結果を返す関数です。

銘柄、年度ごとにフォルダ分け(および作成)を行います。先ほど上述しましたが、 os.makedirs の exist_ok=True によって、すでにフォルダが作成されていてもエラーは発生しません。

その後銘柄、月ごとに日足チャート図を作成します。この時 type='candle' と指定します。また株価の上昇、下降をわかりやすくするために style='charles' を選択しています。このあたりは好みでいいと思います。

def plotCandleStickChart(dict_stockData):
   # 日足チャート図を作成する

   for str_key, df_stockData in dict_stockData.items():
       try:
           # 銘柄ごと,年ごとにフォルダ分けを行う
           str_filePath = './plotImage/{}/{}/'.format(str_key[:4],str_key[-4:])
           os.makedirs(str_filePath, exist_ok=True)
           for int_month in range(1, 13):
               df_monthData = df_stockData[df_stockData.index.month == int_month]
               # 日足チャート図を銘柄、月ごとにjpgファイルとして保存する
               mpf.plot(df_monthData, type='candle', style='charles',
                       savefig='{}{}_{}.jpg'.format(str_filePath, str_key, int_month))
       except FileNotFoundError as err:
           print('ディレクトリが存在しません:{}'.format(err))
           return -1
       except Exception as err:
           print('予期せぬ例外が発生しました:{}'.format(err))
           return -1
   return 1

実行した結果、以下のようなファイル構成となっています。新しくplotImageというフォルダができていることを確認しましょう。(指定した銘柄おコード及び年度によって変化します)
※...部分は省略しています。

main.py(実行ファイル名)
csvData
 |----2211
 |  |--2211_2015.csv
 |  |--2211_2016.csv
 |  |--2211_2017.csv
 |----8228
    |--8228_2015.csv
    |--8228_2016.csv
    |--8228_2017.csv
plotImage
 |----2211
 |  |----2015
 |  |  |--2211_2015_01.jpg
 |  |  |--2211_2015_02.jpg
 |  |  |--2211_2015_03.jpg
 |  |  |--...
 |  |----2016
 |  |  |--...
 |  |----2017
 |  |  |--...
 |----8228
    |----2015
    |  |--...
    |----2016
    |  |--...
    |----2017
    |  |--...

またjpgファイルを開くと日足チャート図がプロットできていることがわかります。赤が株価下降で緑が株価上昇を表しています。

画像2

補足 実行関数部分

各関数を呼び出す部分も補足で説明しておきます。(といっても大したことを明記はしませんが・・・)

全体的な構想としては、処理が成功していれば1を、失敗していれば-1を返します。この考え方は他部分でも同様です。

もしすべての処理が問題なく実行できた場合、「実行結果:成功」のみプロンプト上に出力されます。どこかで例外等が吐かれて失敗した場合、どこの関数で失敗したかわかるようになっています。

また、 if __name__ == '__main__' 部分の冒頭で銘柄コードを指定することで取得したい銘柄を選択することが可能です。複数選択にも対応しています。

参考記事


この記事が気に入ったらサポートをしてみませんか?