Yahooファイナンスのスクレイピングを行います。
今回は、過去の株価を取得します。
いわゆる時系列データというモノです。
時系列データがあれば、チャートも描くことができます。
本記事の内容
- ここまでの流れ【Yahooファイナンスのスクレイピング】
- 時系列ページのスクレイピング仕様
- 時系列ページから株価時系列データを抽出
それでは、上記に沿って解説していきます。
ここまでの流れ【Yahooファイナンスのスクレイピング】
Yahooファイナンスのスクレイピングに関しては、段階を踏んで解説しています。
今回は、時系列ページのスクレイピングがメインです。
下記で過去の同シリーズを記載します。
第1弾
第2弾
第3弾
第1弾の記事は、スクレイピングをする上では必読の内容です。
その内容を理解していないと、犯罪者になってしまうかもしれません。
第2弾の記事は、銘柄コードのリストの作成方法を解説しています。
全部で3849件の銘柄リストを取得できました。
このようなリストを作成するのが、スクレイピングの基本中の基本です。
そして、このリストをもとに取得したいデータのページにアクセスが可能となります。
Yahooファイナンスなら、そのためのベースが銘柄コードなのです。
第3弾の記事では、銘柄コードをもとに企業情報ページへアクセスしています。
そして、企業情報ページから企業情報をスクレイピングしています。
第4弾となる今回は、時系列ページから情報を抽出していきます。
では、時系列ページからデータを抽出するための仕様を確認していきます。
時系列ページのスクレイピング仕様
まずは、時系列ページの確認からです。

上記を見ればわかるように、時系列データには次の二つが存在します。
- 株価時系列データ
- 信用残時系列データ
本記事では、株価時系列データを対象とします。
おそらく、時系列データと言えば、株価時系列データという認識の人がほとんどでしょう。
信用残データも株価との相関関係があるはずです。
その意味では、取得する価値はあるのかもしれません。
もちろん、他にも用途はあるでしょう。
しかし、今回は株価時系列データをターゲットにします。
株価時系列データをターゲットにする場合、考えるべきポイントは以下。
- 株価時系列データページのURL作成
- データ表示条件の決定
- 改ページ対応
- 株価時系列データの抽出
それぞれを下記で説明します。
株価時系列データページのURL作成
株価時系列データページのURLについて説明しておきます。
銘柄コードのリストから、自動的に各銘柄ごとの株価時系列データページのURLを作成します。
そのときの形式は、以下となります。
「https://stocks.finance.yahoo.co.jp/stocks/history/?code=●」
●は、銘柄コードです。
上記形式のURLで株価時系列データページにアクセス可能となります。
例えば、株式会社 極洋(1301)の場合は次のURLです。
https://stocks.finance.yahoo.co.jp/stocks/history/?code=1301
ただ、上記URLは利用しません。
次の「データ表示条件の決定」でその理由がわかります。
結論は、「まとめ」で説明します。
データ表示条件の決定
表示条件とは、以下のことです。

デフォルトだと、デイリーで1ヵ月がデータ表示の条件となっています。
この条件で「表示」ボタンをクリックすると、次のURLページへ遷移します。
「https://info.finance.yahoo.co.jp/history/?code=1301.T&sy=2021&sm=1&sd=14&ey=2021&em=2&ed=13&tm=d」
上記URLは、以下の2点で注目ポイントがあります。
- ドメイン
- クエリパラメータ
下記で説明します。
ドメイン
「株価時系列データページのURL作成」で確認したURLは、以下。
https://stocks.finance.yahoo.co.jp/stocks/history/?code=1301
「表示」ボタンをクリックして遷移した先のURLは、以下。
https://info.finance.yahoo.co.jp/history/?code=1301.T&sy=2021&sm=1&sd=14&ey=2021&em=2&ed=13&tm=d
それぞれのドメインを抽出すると以下となります。
- stocks.finance.yahoo.co.jp
- info.finance.yahoo.co.jp
正直、これには自分の目を疑いました。
でも、この事実を受け入れていきましょう。
あと、何気にドメイン以降のパスも異なります。
- /stocks/history/
- /history/
せめて、ここは同じにしましょうよ・・・
まあ、スクレイピング対策になっていると言えば、なっていますけどね。
クエリパラメータ
クエリパラメータは、以下。
| パラメータ | 値 |
| sy | 2021 |
| sm | 1 |
| sd | 14 |
| ey | 2021 |
| em | 2 |
| ed | 13 |
| tm | d |
説明するまでもありませんね。
ただ、tmだけは確認しておきましょう。
tmには、以下の値が設定可能です。
| d | デイリー |
| w | 週刊 |
| m | 月間 |
わかりやすいですね。
その意味では、Yahooファイナンスはスクレイピングが容易と言えます。
改ページ対応
データ表示条件を2020年の1年間でデイリーとした場合、件数部分が次のように表示されます。

株価時系列データページでは、1ページに20件しか表示しません。
そのため、改ページへの対応が必要となります。
対応方法は、二つあります。
- クエリパラメータに「&p=●」を付ける
- 「次へ」リンクをクリックする
※●はページ数(1,2,3・・・)
Yahooファイナンスでは、両方の対応を取ることができます。
今回は、「次へ」リンクをクリックする対応を採用します。
クエリパラメータの方式は、対応できないサイトも存在します。
汎用性を考えたら、「次へ」リンクをクリックする方式がベターです。
それにSeleniumを利用している以上は、できる限り利用して身に付けていきましょう。
株価時系列データの抽出
各データ行のhtmlタグは、以下。

class名指定ではスクレイピングはできませんね。
各tr毎に存在するtd要素の順番により、データ項目を決定できそうです。
| 順番(0スタート) | データ項目 |
| 0 | 日付 |
| 1 | 始値 |
| 2 | 高値 |
| 3 | 安値 |
| 4 | 終値 |
| 5 | 出来高 |
| 6 | 調整後終値* |
念のため、「class=boardFin」のtable要素以下で対象となるtr・tdを絞り込みます。
まとめ
改ページ処理は、「次へ」リンクをクリックしていく形式を採用します。
そのため、スクレイピングする上でアクセスするURLは最初の一つだけです。
銘柄コード1301の2020年におけるデイリーの株価時系列データを得る場合のURLは、以下。
https://info.finance.yahoo.co.jp/history/?code=1301&sy=2021&sm=1&sd=14&ey=2021&em=2&ed=13&tm=d
上記URLにアクセスして、あとは「次へ」リンクをクリックしていくことになります。
もちろん、各ページ最大20件の株価時系列データを抽出しながらです。
以上より、スクレイピングの仕様が決まりました。
次は、実際に株価時系列データをスクレイピングしていきましょう。
時系列ページから株価時系列データを抽出
時系列ページから株価時系列データを抽出するコードは、以下。
現時点(2021年2月13日)では元気モリモリ動いています。
サンプルコード
import sys
import bs4
import traceback
import re
import time
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
# ドライバーのフルパス
CHROMEDRIVER = "chromedriver.exeのパス"
# 改ページ(最大)
PAGE_MAX = 2
# 遷移間隔(秒)
INTERVAL_TIME = 3
# 開始年・月・日
SY = 2020
SM = 1
SD = 1
# 終了年・月・日
EY = 2020
EM = 12
ED = 31
# タイプ(d:デイリー、w:週刊、m:月間)
TM = "d"
# ドライバー準備
def get_driver():
# ヘッドレスモードでブラウザを起動
options = Options()
options.add_argument('--headless')
# ブラウザーを起動
driver = webdriver.Chrome(CHROMEDRIVER, options=options)
return driver
# 対象ページのソース取得
def get_source_from_page(driver, page):
try:
# ターゲット
driver.get(page)
driver.implicitly_wait(10) # 見つからないときは、10秒まで待つ
page_source = driver.page_source
return page_source
except Exception as e:
print("Exception\n" + traceback.format_exc())
return None
# ソースからスクレイピングする
def get_data_from_source(src):
# スクレイピングする
soup = bs4.BeautifulSoup(src, features='lxml')
# print(soup)
try:
info = []
table = soup.find("table", class_="boardFin")
if table:
elems = table.find_all("tr")
for elem in elems:
td_tags = elem.find_all("td")
if len(td_tags) > 0:
row_info = []
tmp_counter = 0
for td_tag in td_tags:
tmp_text = td_tag.text
if tmp_counter == 0:
# 年月日
tmp_text = tmp_text
else:
tmp_text = extract_num(tmp_text)
row_info.append(tmp_text)
tmp_counter = tmp_counter + 1
info.append(row_info)
return info
except Exception as e:
print("Exception\n" + traceback.format_exc())
return None
# 次のページへ遷移
def next_btn_click(driver):
try:
# 次へボタン
elem_btn = WebDriverWait(driver, 10).until(
EC.visibility_of_element_located((By.LINK_TEXT, '次へ'))
)
# クリック処理
actions = ActionChains(driver)
actions.move_to_element(elem_btn)
actions.click(elem_btn)
actions.perform()
# 間隔を設ける(秒単位)
time.sleep(INTERVAL_TIME)
return True
except Exception as e:
print("Exception\n" + traceback.format_exc())
return False
# 数値だけ抽出
def extract_num(val):
num = None
if val:
match = re.findall("\d+\.\d+", val)
if len(match) > 0:
num = match[0]
else:
num = re.sub("\\D", "", val)
if not num:
num = 0
return num
if __name__ == "__main__":
# 引数
args = sys.argv
# 銘柄コード
code = "1301"
if len(args) == 2:
# 引数があれば、それを使う
code = args[1]
# 対象ページURL
page = "https://info.finance.yahoo.co.jp/history/?code=" + code
page = page + "&sy=" + str(SY) + "&sm=" + str(SM) + "&sd=" + str(SD)
page = page + "&ey=" + str(EY) + "&em=" + str(EM) + "&ed=" + str(ED)
page = page + "&tm=" + TM
# ブラウザのdriver取得
driver = get_driver()
# ページのソース取得
source = get_source_from_page(driver, page)
result_flg = True
# ページカウンター制御
page_counter = 0
while result_flg:
page_counter = page_counter + 1
# ソースからデータ抽出
data = get_data_from_source(source)
# データ保存
print(data)
# 改ページ処理を抜ける
if page_counter == PAGE_MAX:
break
# 改ページ処理
result_flg = next_btn_click(driver)
source = driver.page_source
# 閉じる
driver.quit()
プログラム詳細は、「時系列ページのスクレイピング仕様」とコード上のコメントをご覧ください。
不明な点がある場合は、同シリーズの過去記事を確認してください。
ただ、次の「対象ページURL」に関しては説明しておきます。
page = "https://info.finance.yahoo.co.jp/history/?code=" + code
page = page + "&sy=" + str(SY) + "&sm=" + str(SM) + "&sd=" + str(SD)
page = page + "&ey=" + str(EY) + "&em=" + str(EM) + "&ed=" + str(ED)
page = page + "&tm=" + TM
クエリパラメータを指定したURLを作成しています。
そして、ここで用いられる定数は以下の部分です。
# 開始年・月・日 SY = 2020 SM = 1 SD = 1 # 終了年・月・日 EY = 2020 EM = 12 ED = 31 # タイプ(d:デイリー、w:週刊、m:月間) TM = "d"
特に問題はありませんね。
ここの値を変更すれば、取得したい条件でスクレイピングが可能です。
実行結果
サンプルコードを実行した結果は、以下。
[['2020年12月30日', '2947', '2960', '2923', '2951', '11100', '2951'], ['2020年12月29日', '2948', '2963', '2945', '2961', '15100', '2961'], ['2020年12月28日', '2929', '2950', '2910', '2950', '22900', '2950'], ['2020年12月25日', '2903', '2930', '2903', '2916', '8300', '2916'], ['2020年12月24日', '2913', '2937', '2909', '2917', '13900', '2917'], ['2020年12月23日', '2913', '2920', '2906', '2913', '6300', '2913'], ['2020年12月22日', '2947', '2947', '2907', '2913', '18000', '2913'], ['2020年12月21日', '2939', '2947', '2912', '2947', '11300', '2947'], ['2020年12月18日', '2935', '2938', '2907', '2937', '15800', '2937'], ['2020年12月17日', '2872', '2920', '2870', '2920', '18900', '2920'], ['2020年12月16日', '2882', '2882', '2871', '2871', '8900', '2871'], ['2020年12月15日', '2880', '2897', '2874', '2881', '12900', '2881'], ['2020年12月14日', '2830', '2888', '2830', '2888', '31200', '2888'], ['2020年12月11日', '2810', '2846', '2810', '2841', '18400', '2841'], ['2020年12月10日', '2812', '2828', '2806', '2815', '14900', '2815'], ['2020年12月9日', '2816', '2827', '2807', '2820', '6600', '2820'], ['2020年12月8日', '2807', '2824', '2805', '2816', '10400', '2816'], ['2020年12月7日', '2820', '2830', '2806', '2811', '12900', '2811'], ['2020年12月4日', '2819', '2819', '2800', '2811', '9200', '2811'], ['2020年12月3日', '2810', '2816', '2796', '2807', '9400', '2807']][['2020年12月2日', '2806', '2817', '2789', '2811', '25700', '2811'], ['2020年12月1日', '2824', '2824', '2780', '2787', '17900', '2787'], ['2020年11月30日', '2816', '2825', '2795', '2795', '18900', '2795'], ['2020年11月27日', '2807', '2825', '2803', '2819', '22000', '2819'], ['2020年11月26日', '2820', '2826', '2801', '2809', '8700', '2809'], ['2020年11月25日', '2839', '2846', '2795', '2831', '27800', '2831'], ['2020年11月24日', '2850', '2860', '2806', '2807', '20100', '2807'], ['2020年11月20日', '2848', '2848', '2820', '2832', '10200', '2832'], ['2020年11月19日', '2834', '2846', '2812', '2838', '11600', '2838'], ['2020年11月18日', '2813', '2838', '2790', '2838', '14400', '2838'], ['2020年11月17日', '2800', '2813', '2785', '2813', '13100', '2813'], ['2020年11月16日', '2820', '2820', '2788', '2804', '15800', '2804'], ['2020年11月13日', '2821', '2821', '2771', '2784', '10500', '2784'], ['2020年11月12日', '2807', '2839', '2805', '2814', '13900', '2814'], ['2020年11月11日', '2810', '2850', '2796', '2850', '27300', '2850'], ['2020年11月10日', '2795', '2819', '2772', '2793', '27000', '2793'], ['2020年11月9日', '2800', '2800', '2757', '2795', '15800', '2795'], ['2020年11月6日', '2771', '2786', '2671', '2782', '30900', '2782'], ['2020年11月5日', '2745', '2790', '2671', '2671', '31400', '2671'], ['2020年11月4日', '2763', '2768', '2744', '2746', '12000', '2746']]
最初の2ページ分の40件だけです。
それは、以下のように「2」と設定しているからです。
# 改ページ(最大) PAGE_MAX = 2
適当にここを「99999」などにすれば、全ページ分をスクレイピングします。
もしくは、PAGE_MAXの制御を無効にするかです。
まとめ
以下は、本ブログにおけるスクレイピングでは当たり前の定数です。
# ドライバーのフルパス CHROMEDRIVER = "chromedriver.exeのパス" # 改ページ(最大) PAGE_MAX = 2 # 遷移間隔(秒) INTERVAL_TIME = 3
この定数に関して、わからないところがある場合は過去記事をご覧ください。
メルカリのスクレイピングシリーズが、特に参考になります。
メルカリのスクレイピング第1弾
メルカリのスクレイピング第2弾
メルカリのスクレイピング第3弾







