BeautifulSoupで全上場銘柄の過去の株価を20年分取得してみた

最近PythonのスクレイピングライブラリであるBeautifulSoupを勉強しているとかなり面白くて、何かに活用してみたくなったので、全上場銘柄の株価を取得するツールを作ってみました。取得したデータを使ってどういう分析をするかはまだ検討中ですが、色々なことができそうです。

# 概要

- pandasを用いたデータの入出力
まず、[こちら](https://www.jpx.co.jp/markets/statistics-equities/misc/01.html)から東証上場銘柄一覧が記載されたCSVを保存します。私は今回ETFのデータは不要だったので事前に消しておきました。
pandasでは構造化データが扱いやすいDataFrameや、簡単にファイルの入出力ができるread_csv, to_csvメソッドが用意されており、非常に便利です。

- requests, BeautifulSoupによるHTML取得・解析
requestsモジュールで指定したURLからHTMLを取得し、BeautifulSoupによって必要なデータを抽出します。
[こちら](https://kabuoji3.com/stock/)に各銘柄の年度別株価データが載っているのですが、銘柄・年度ごとにページが分かれているので全銘柄の20年分のデータを手作業で集めるのはまず無理です。
そこで、事前に保存したコード一覧を読み込み、各コードに対して年度ごとに異なるURLにアクセスしてデータを取得します。

今回アクセスするページ数、取得するデータ量は非常に多いため、この自動化ツールを使っても私の環境では半日以上かかりました。笑
マルチプロセス等を使えばもう少し速くできるかもしれません。

# ソースコード
```python
# ライブラリをインポート
from bs4 import BeautifulSoup
import requests
import time
import os
import pandas as pd

if __name__ == '__main__':
    # 開始時刻
    start_time = time.time()
    # 銘柄・コード一覧を取得
    df_input = pd.read_csv('data_j.csv', encoding='shift_jis')
    # 調べたいデータを指定
    codes = df_input['コード'].to_list()
    # 20年間の株価を取得する
    years = range(2000, 2020)

    # 各銘柄（コード）に対して
    for code in codes:
        df = pd.DataFrame(columns=['日付', '終値'])  # 日付と終値を格納するDataFrame
        row_start = 1  # 各年の最初のデータの行

        flg_print = False

        file_name = ''  # 保存するファイル名
        # 各年に対して
        for year in years:
            try:
                # URLを取得
                url = "https://kabuoji3.com/stock/" + str(code) + "/" + str(year) + "/"
                # headers情報は確認くん（https://www.ugtop.com/spill.shtml）で確認
                # headers = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1 Safari/605.1.15 "}
                headers = {
                    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36"
                }
                # HTML解析用オブジェクト
                soup = BeautifulSoup(requests.get(url, headers=headers).content, 'html.parser')
                # タイトルを取得
                title = soup.select_one("span.jp").text
                # タイトルを出力
                if not flg_print:
                    print(title)
                    flg_print = True
                # ファイル名を指定
                file_name = '{0}.csv'.format(str(title))
                # 既にファイルが存在する場合はスキップ
                if os.path.exists(file_name):
                    break
                # 株価を取り出す
                all_tr = soup.find_all('tr')
                # 先頭要素はヘッダなので削除
                all_tr.remove(all_tr[0])
                # その年のデータの最終行を取得
                row_finish = row_start + len(all_tr)
                # 各行に日付・終値のデータを書き込む
                for i in range(row_start, row_finish):
                    tr = all_tr[i - row_start].find_all('td')  # リスト型
                    date = tr[0].text  # 日付
                    fp = tr[4].text  # 終値
                    # DataFrameの末尾に追加
                    df = df.append({'日付': date, '終値': fp}, ignore_index=True)
                # 次の年のデータの開始行を更新
                row_start = row_finish
            except:
                # その年のデータが存在しなくてもエラー復帰せずに次の年のデータの取得に移る
                pass

        # データを保存
        df.to_csv('data\\' + file_name, encoding='shift_jis', index=False)

    stop_time = time.time()  # 終了時刻
    elapsed = stop_time - start_time  # 経過時間

    # 経過時間の出力
    print('処理が完了しました。経過時間: {0:.1f}秒'.format(elapsed))
```