株式投資を実践していく上で、ヤフーファイナンスの情報はとても有用で利用しない手はありませんが、自分好みの情報を得ようと思うと、どうしても痒いところに手が届きません。
例えば僕の場合、上場数年以内の企業でサステナブルな事業を行っている企業に投資をしたいと思っています。
この場合、ヤフーファイナンスのランキング機能を利用すれば、上場数年以内の企業を新しい順に割り出すことが可能です。
しかしサステナブルかどうかを判断するために、特色や詳細の事業内容、業績といった情報にアクセスするには、どうしてもひと手間を要します。
この課題を解決するために、ヤフーファイナンスの情報を整理し、自分好みの情報にまとめるシステムをPythonのスクレイピングで実現しました。
本記事では上場数年以内の企業の特色を一覧化し、詳細情報に素早くアクセス可能なシステムの構築方法について説明します。
システムイメージ
システムイメージは下記のとおりです。
・定期的にヤフーファイナンスからWebスクレイピングによりデータ取得し、JSONデータにまとめます。
・HTMLファイル(JavascriptとCSS)でJSONファイルを読み込んで自分好みに整形し、Webサーバに読み込ませ、ブラウザにより表示できるようにします。

インフラ部分は前回の記事で作成したDockerに乗せます。
定期実行については、前回Docker上に構築したCron専用コンテナを利用します。
今回作成する新規コンテナを「成長株コンテナ」とすると、イメージとしては下記のようになります。

作成方法
まず、前回同様、ソースコードの入れ物となる7つの空ファイルを作成します。vscodeを開き、下記のようなフォルダ階層で作成します。
僕の環境では下記フォルダ構成で利用します。
E:.
└─corder
├─etc
└─projects
└─YoungStock
├─docker-compose.yml
├─Dockerfile
└─src
├─bottlerun.py
├─youngstock.py
├─youngstock.sh
├─dynamic
| └─youngstock.json
└─views
└─youngstock.html
下記のような感じになります。

続いて、ファイルの中身を記載していきます。
■docker-compose.yml
Dockerfileを構成し、コンテナを立ち上げるための設定です。ポート部分は環境に合わせて変更します。
version: '3' | |
services: | |
maccounter: | |
restart: always | |
build: . | |
container_name: 'youngstock' | |
working_dir: '/root/src/' | |
command: python3 bottlerun.py | |
tty: true | |
ports: | |
- '8134:8134' | |
volumes: | |
- /E/coder/projects/YoungStock/src:/root/src |
■Dockerfile
コンテナイメージの定義です。Python3のコンテナをベースとして、必要なパッケージをpipインストールしています。
FROM python:3 | |
USER root | |
RUN apt-get update | |
RUN apt-get -y install locales && \ | |
localedef -f UTF-8 -i ja_JP ja_JP.UTF-8 | |
ENV LANG ja_JP.UTF-8 | |
ENV LANGUAGE ja_JP:ja | |
ENV LC_ALL ja_JP.UTF-8 | |
ENV TZ JST-9 | |
ENV TERM xterm | |
RUN apt-get install -y vim less | |
RUN pip install --upgrade pip | |
RUN pip install --upgrade setuptools | |
RUN pip3 install bottle | |
RUN pip3 install requests | |
RUN pip3 install beautifulsoup4 | |
RUN pip3 install mojimoji |
■bottlerun.py
単純なWebサーバです。簡単のため、Bottleフレームワークを利用しています。ポート部分は環境に合わせて変更します。
# -*- coding: utf-8 -*- | |
from wsgiref import simple_server | |
from socketserver import ThreadingMixIn | |
from wsgiref.simple_server import WSGIServer | |
from bottle import HTTPResponse, route, run, template, request, response, view, default_app, static_file, redirect | |
class ThreadedWSGIServer(ThreadingMixIn, WSGIServer): | |
"""マルチスレッド化した WSGIServer""" | |
pass | |
# メイン | |
@route('/') | |
@view('youngstock') | |
def youngstock(): | |
pass | |
# 動的ファイル | |
@route('/dynamic/<filename:path>') | |
def dynamic(filename): | |
return static_file(filename, root="dynamic") | |
if __name__ == '__main__': | |
server = simple_server.make_server('', 8134, default_app(), server_class=ThreadedWSGIServer) | |
server.serve_forever() |
■youngstock.py
Webスクレピングの実装部分です。
基本ライブラリや細かい処理の解説は割愛しますが、大まかな流れとしては下記のようになっています。
① ヤフーファイナンスの上場年月日ランキング表を1ページから取得する
② 会社名、ヤフーファイナンスの会社個別URLをハッシュマップに代入する
③ 会社個別URLから特色を取得し、ハッシュマップに代入する
④ 「会社名 HP」のGoogleWeb検索結果から、会社HPのURLを取得し、ハッシュマップに代入する
⑤ ①に戻り15ページまで繰り返す
⑥ ハッシュマップをJSONファイルに書き出す
# -*- coding: utf-8 -*- | |
import requests | |
from bs4 import BeautifulSoup | |
import json | |
import mojimoji | |
import re | |
import datetime | |
def makejson(page_num = 1): | |
# JSON | |
''' | |
{ | |
"date": "7/13 15:00", | |
"data": [ | |
{ | |
"name": "会社名", | |
"hp_url": "会社のURL", | |
"yf_url": "会社のヤフーファイナンスURL" | |
"feature": "特色" | |
}, | |
{}, | |
..., | |
{} | |
] | |
} | |
''' | |
ret = {} | |
ret["date"] = datetime.datetime.now().strftime("%m/%d %H:%M") | |
ret["data"] = [] | |
# 指定ページまで繰り返す | |
for page in range(1,page_num+1): | |
# ヤフーファイナンスの設立年月日ランキングのページを取得 | |
urlstr = 'https://finance.yahoo.co.jp/stocks/ranking/listingDate?market=all&term=daily&page=' + str(page) | |
html_text = requests.get(urlstr).text | |
soup = BeautifulSoup(html_text, 'html.parser') | |
# テーブルを取得 | |
for selecttable in soup.find_all("table"): | |
if "順位" in selecttable.text: | |
table = selecttable | |
break | |
# アンカーリストを取得 | |
for ancs in table.find_all("a"): | |
# 掲示板のアンカーを排除 | |
if "掲示板" not in ancs: | |
#------------------------------ | |
# 各会社の基本情報を取得 | |
#------------------------------ | |
company = {} | |
company["name"] = mojimoji.zen_to_han(ancs.text, kana=False).replace("&","&") | |
company["yf_url"] = ancs.attrs["href"] | |
#------------------------------ | |
# 特色を取得 | |
#------------------------------ | |
company_code = re.sub(".*/", "", company["yf_url"]) | |
urlstr2 = "https://finance.yahoo.co.jp/quote/" + company_code + "/profile" | |
html_text2 = requests.get(urlstr2).text | |
soup2 = BeautifulSoup(html_text2, 'html.parser') | |
# 特色を取得 | |
company["feature"] = "" | |
for selecttable in soup2.find_all("table"): | |
if "特色" in selecttable.text or "概要" in selecttable.text: | |
for selecttr in selecttable.find_all("tr"): | |
if "特色" in selecttr.text or "概要" in selecttr.text: | |
company["feature"] = selecttr.find_all("td")[0].text.replace("【特色】", "") | |
break | |
break | |
# 特色が未反映の会社は除外する | |
if company["feature"] == "---": | |
continue | |
#------------------------------ | |
# 会社URLを取得 | |
#------------------------------ | |
urlstr3 = "https://www.google.com/search?q=" + company["name"] + "+hp" | |
html_text3 = requests.get(urlstr3).text | |
soup3 = BeautifulSoup(html_text3, 'html.parser') | |
# 会社URLを取得 | |
company["hp_url"] = "" | |
for selectdiv in soup3.select("div > a"): | |
href = selectdiv.attrs["href"] | |
if href.startswith("/url?q=") and "google" not in href and "wikipedia" not in href: | |
tmpstr = href.replace("/url?q=", "") | |
tmpstr = re.sub("&sa=U.*", "", tmpstr) | |
company["hp_url"] = tmpstr | |
break | |
#------------------------------ | |
# 標準出力 | |
#------------------------------ | |
print(company["name"]) | |
print(company["hp_url"]) | |
print(company["yf_url"]) | |
print(company["feature"]) | |
print("----------------------") | |
ret["data"].append(company) | |
# JSON出力 | |
f = open("dynamic/youngstock.json", "w", encoding='utf-8') | |
f.write(json.dumps(ret, ensure_ascii=False)) | |
f.close() | |
if __name__ == "__main__": | |
# JSONファイル作成 | |
makejson(page_num=15) |
■youngstock.sh
Cron定義記載のためのyoungstock.pyを起動するシェルです。
#!/bin/bash | |
proc="python3" | |
rundir="/root/src/" | |
op="youngstock.py" | |
cd $rundir | |
$proc $op | |
exit $RETVAL |
■youngstock.json
自動生成されるファイルです。空ファイルのままで問題ありません。
■youngstock.html
データを見やすく表示するための肝の部分となります。
会社名、特色を一覧表示し、会社名をタップ or クリックすると、会社ホームページおよびヤフーファイナンスのリンクがポップアップで表示されるようにしました。
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> | |
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, maximum-scale=1"> | |
<title>上場数年以内の会社一覧</title> | |
<style type="text/css"> | |
table { | |
border-collapse: collapse; | |
width:100%; | |
font-size : 11px; | |
} | |
th { | |
border-bottom: #e3e3e3 1px dotted; | |
text-align: left; | |
padding: 10px; | |
font-weight: normal; | |
width: 34%; | |
} | |
td { | |
border-bottom: #e3e3e3 1px dotted; | |
text-align: left; | |
padding: 5px; | |
width: 34%; | |
} | |
td.s { | |
border-bottom: #e3e3e3 1px dotted; | |
text-align: left; | |
padding: 2px; | |
width: 66%; | |
} | |
tr:hover { | |
background: #3D80DF; | |
color: #FFFFFF; | |
} | |
span { | |
font-size : 11px; | |
} | |
.modal { | |
display: block; | |
width: 600px; | |
max-width: 100%; | |
height: 200px; | |
position: fixed; | |
z-index: 100; | |
left: 50%; | |
top: 70%; | |
transform: translate(-50%, -10%); | |
/* margin: -200px 0 0 -200px; */ | |
background: white; | |
box-shadow: 0 0 60px 10px rgba(0, 0, 0, 0.9); | |
} | |
.closed { | |
display: none; | |
} | |
.modal-overlay { | |
position: fixed; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
z-index: 50; | |
background: rgba(0, 0, 0, 0.6); | |
} | |
.modal-guts { | |
position: absolute; | |
top: 20px; | |
left: 80px; | |
width: 100%; | |
overflow: auto; | |
padding: 30px 50px 50px 50px; | |
} | |
.modal .close-button { | |
position: absolute; | |
z-index: 1; | |
top: 10px; | |
right: 20px; | |
border: 0; | |
background: black; | |
color: white; | |
padding: 5px 10px; | |
font-size: 1.3rem; | |
} | |
</style> | |
</head> | |
<body> | |
<span id="date"></span> | |
<table id="asset"> | |
<tr> | |
<th>会社名</th> | |
<td>特色</td> | |
</tr> | |
</table> | |
<div class="modal-overlay closed" id="modal-overlay"></div> | |
<div class="modal closed" id="modal"> | |
<button class="close-button" id="close-button">閉じる</button> | |
<div class="modal-guts"> | |
<h2 id="hp"><a id="hp_url" href="" target="_blank">会社ホームページ</a></h4> | |
<h2 id="yf"><a id="yf_url" href="" target="_blank">ヤフーファイナンス</a></h4> | |
</div> | |
</div> | |
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script> | |
<script type="text/javascript"> | |
ads = ""; | |
$(function(){ | |
$.get("/dynamic/youngstock.json", null, function(data){ | |
//JSONのパース不要 | |
//var obj = JSON.parse(data); | |
///////////////// | |
//データ表示 | |
///////////////// | |
ads = data.data | |
ads.forEach((ad,i,ads) => { | |
var addtag = "<tr><td id='" + i + "' class='ob'>" + ad.name + "</td><td class='s'>" + ad.feature + "</td></tr>"; | |
$('#asset').append(addtag); | |
}); | |
///////////////// | |
//取得日時 | |
///////////////// | |
$('#date').html(data.date); | |
}); | |
}); | |
// ポップアップを開く | |
$(document).on("click", ".ob", function(){ | |
$("#modal").toggleClass("closed"); | |
$("#modal-overlay").toggleClass("closed"); | |
var id = Number($(this).attr('id')) | |
$("#hp_url").attr("href", ads[id].hp_url); | |
$("#yf_url").attr("href", ads[id].yf_url); | |
}); | |
// ポップアップを閉じる | |
$(document).on("click", "#close-button", function(){ | |
$("#modal").toggleClass("closed"); | |
$("#modal-overlay").toggleClass("closed"); | |
}); | |
</script> | |
</body> | |
</html> |
次に、Cron設定を施します。Cron専用コンテナのcrontabに下記を追記します。
18 16 * * 5 docker exec -i youngstock /root/src/youngstock.sh |
毎週金曜16:18に実行する例です。曜日や時間は適宜変更してください。
今回の内容(企業の上場頻度)であれば、高頻度の更新は必要ないので、週次で実行することにします。なるべく低頻度にして、ヤフーのサーバーに負担をかけないよう気をつけます。
Cron専用コンテナのcrontabを変更した際には、下記コマンドで反映してください。
cd Cron
sudo docker-compose down
sudo docker-compose build
sudo docker-compose up -d
最後に、成長株コンテナを起動して作成完了です。
cd YoungStock
sudo docker-compose up -d
システム利用方法
Cron設定時刻の経過後に、スマホのブラウザを開き、下記URLに接続します。
http://[サーバのIPアドレス]:[ポート]/
上場数年以内の会社一覧が、整形された形で表示されたら成功です。

会社名をタップすると、ポップアップが表示され、会社ホームページのリンクとヤフーファイナンスのリンクが表示されます。
スマホを片手で操作できるよう、下の方にポップアップし、リンクを右側に寄せてます。

これによりスピーディーに株式投資の情報を得られるようになりました。
まとめ
本記事では、PythonによるWebスクレイピングを利用して取得したデータを元に、個人利用Webサイトの作成方法を説明しました。
よく利用するWebサイトに改善案がある場合は、今回の方法を応用することで便利な個人利用Webサイトを作成可能と思いますので、試してみてはいかがでしょうか。
コメント