🐾

リアルタイムではなくYahooファイナンスから当日の分単位株価ゲット

に公開

分単位の株価データはなかなか手に入らない

Yahooファイナンスから毎日の始値・高値・安値・終値+出来高の株価の時系列データを、Pythonのyfinanceモジュールでダウンロードできることは、すでにいろいろな情報から知られている有名な方法です。ところが、日毎でなくて一日の中のリアルタイムな変動データを手に入れたいとなると、これがなかなか見つからないようです。有料サービスでリアルタイムデータを買う方法はあるらしいですが冗談で買える値段ではないようですし、無料で手に入るものはネットにはなかなか落ちてないようです。取引時間中に何度も売買するデイトレーダーにとっては、トレンド読みよりも瞬間的な板読みの方が重要なのかもしれないので、そもそも必要とされていないのかもしれませんが。

リアルタイムな株価をデータとして記録保管する方法には、すぐ思いつく方法としては、Yahooファイナンスに限らない日経など各社株価情報サイトの取引中のリアルタイムな株価表示を、curlやwget、あるいはPythonのurllibを使って周期的にページをGETしてパースして株価を抜き出して保存するような方法があります。

株価のリアルタイムスクレイピングは禁止されている

しかし、このような行為は多くの株価情報サイトで禁止しています。負荷がかかりすぎるという理由なようです。たしかに、それを大勢で一斉にやったらWebサーバがどうにかなってしまいそうなのは容易に想像できます。

そうではない別の方法として、もっとゆっくり、取引が終わった後にその日の分ごとの株価データをまとめて取得できる方法ならあります。

今のところこの方法なら静的に取得できる

その方法は、いや、もうその界隈の方々ではもうご存知かもしれませんけど。Yahooファイナンスのグラフを表示するページのグラフに表示するためのデータをHTMLから抜き出す方法があります。これなら1回のリクエストで得るページから、9:00〜15:30の分単位のデータをまとめて得ることができます。

これはPythonで簡単に実現できます。yfinanceモジュールは使わずに標準のurllib.requesthtml.parserjsonモジュールを使います。

yahooget.py
import re
import os
import time
import urllib.request
import datetime 
from html.parser import HTMLParser
import json

codes = [
    (1111, "日本"),
    (2914, "日本たばこ産業"),
    (3382, "セブン&アイ・ホールディングス"),
    (4063, "信越化学工業"),
    (6098, "リクルートホールディングス"),
    (6367, "ダイキン工業"),
    (9042, "阪急阪神ホールディングス"),
    (9934, "因幡電機産業"),
]

prices = []

class YFinanceHtml(HTMLParser):
    startscript = False
    def handle_starttag(self, tag, attrs):
        if tag == "script":
            YFinanceHtml.startscript = True
    def handle_data(self, data):
        if YFinanceHtml.startscript:
            m = re.search('__PRELOADED_STATE__ =', data)
            if m:
                data = data[m.end()::]
                try:
                    jsn = json.loads(data)
                    if "mainItemDetailChartSetting" in jsn: 
                        detailchart = jsn["mainItemDetailChartSetting"]
                        if "timeSeriesData" in detailchart: 
                            timeseries = detailchart["timeSeriesData"]
                            if "histories" in timeseries:
                                for pricedata in timeseries["histories"]:
                                    price = pricedata["closePrice"]
                                    if  price:
                                        tm = pricedata["baseDatetime"]
                                        m = re.search("T[0-2][0-9]:[0-5][0-9]", tm)
                                        prices.append(f"{tm[m.start()+1:m.end()]}, {price}\n")
                except json.JSONDecodeError:
                    pass
    def handle_endtag(self, tag):
        YFinanceHtml.startscript = False
    def handle_startendtag(self, tag, attrs):
        pass
    def handle_comment(self, data):
        pass

# 保存ディレクトリYGETyyyymmddを作成
t = datetime.datetime.now()
savedir = f"YGET{t.year:04}{t.month:02}{t.day:02}"
if not os.path.exists(savedir):
    os.mkdir(savedir)

# codesリストの銘柄についてYahooファイナンスのグラフページから分単位株価を抜き出し
for tcode in codes:
    url = f"https://finance.yahoo.co.jp/quote/{tcode[0]}.T?term=1d"
    try:
        dlbin = urllib.request.urlopen(url).read()
        dltxt = dlbin.decode()
        print(f"T.{tcode[0]},{tcode[1]} ", end="")
        prices.clear()
        pars = YFinanceHtml() 
        pars.feed(dltxt)
        with open(f"{savedir}/T{tcode[0]}.txt", mode="w") as fo:
            fo.write(f"T.{tcode[0]} {tcode[1]}\n")
            for pricedata in prices:
                fo.write(pricedata)
        print("..done")
        time.sleep(1)
    except:
        pass

このように実行します。

$ python3 yahooget.py 
T.2914,日本たばこ産業 ..done
T.3382,セブン&アイ・ホールディングス ..done
T.4063,信越化学工業 ..done
T.6098,リクルートホールディングス ..done
T.6367,ダイキン工業 ..done
T.9042,阪急阪神ホールディングス ..done
T.9934,因幡電機産業 ..done
$

結果は、YGET20250624の日付別のディレクトリの中に、銘柄コードごとの時系列データが格納されます。

$ ls -l YGET20250624/
合計 36
-rw-rw-r-- 1 midori midori 4013  6月 24 17:05 T2914.txt
-rw-rw-r-- 1 midori midori 4381  6月 24 17:05 T3382.txt
-rw-rw-r-- 1 midori midori 4010  6月 24 17:05 T4063.txt
-rw-rw-r-- 1 midori midori 4031  6月 24 17:05 T6098.txt
-rw-rw-r-- 1 midori midori 4342  6月 24 17:05 T6367.txt
-rw-rw-r-- 1 midori midori 4028  6月 24 17:05 T9042.txt
-rw-rw-r-- 1 midori midori 4010  6月 24 17:05 T9934.txt

たとえばJTは、

$ cat YGET20250624/T2914.txt | head -n 10
T.2914 日本たばこ産業
09:00, 4396
09:01, 4386
09:02, 4393
09:03, 4386
09:04, 4388
09:05, 4397
09:06, 4394
09:07, 4395
09:08, 4389

何をGETしてどこを抜き出しているか?

現在のYahooファイナンスの、たとえばJTの2914ならば、

https://finance.yahoo.co.jp/quote/2914.T?term=1d

はJTの株価情報がグラフとともに表示するページです。これをurllib.requestで取得してテキストにデコードします。
このテキストは、そのページのHTMLファイルそのものです。これをhtml.parserモジュールのHTMLParserクラスを継承するクラスを作ってパースします。

class YFinanceHtml(HTMLParser):

<script>タグはいくつもありますが、その中に

window.__PRELOADED_STATE__ = {'

からはじまるscriptタグが一箇所だけあります。この「= {」の先がJSON形式データになっていて、

"mainItemDetailChartSetting":
  {
    "timeSeriesData":
      {
        "histories":[
		{"baseDatetime":時間,"closePrice":株価},
		{"baseDatetime":時間,"closePrice":株価},

ネストする辞書を、
mainItemDetailChartSetting⇒timeSeriesData ⇒histories
でたどったhistoriesの値のリストに9:00からの時系列データが格納されています。これをjsonモジュールでパースしてhistoriesの値のリストをループで抜き出します。

この実装でなくても、mainItemDetailChartSettingのワードから順に文字列サーチしてリストの位置を探すような方法でもよいでしょう。

この方法の弱点はその日のうちに取らなければならないことです。翌日になればグラフ表示がリセットされるので、HTMLに埋め込まれたデータ列も消えてしまいます。

なお、分単位でも日単位でも、株価データそのものを販売することはと法に抵触する行為なので、こうやって得たデータは自分個人の機械学習のネタに使う程度にとどめておくべきでしょう。

冒頭でも警告しましたが、上のPythonコードも、この方法自体もそのうち使えなくなることが予想されます。

Discussion