AB型技術系 主に備忘録

ほぼプログラム関連の備忘録

スクレイピングした株価情報をjson形式で保存できるようにした

前回保有している株の株価情報をスクレイピングすることができました。

freelancer.hatenablog.jp

管理・編集しやすいように結果をjson形式のファイルに出力してみました。

サンプルプログラム

import requests
import json
import time
from bs4 import BeautifulSoup
    
def getText(el):
    """指定した要素内の文字列を返す

    Args:
        el (bs4.element.ResultSet): 要素

    Returns:
        str: 要素内の文字列
    """
    if el is not None:
        return el.getText()
    return ""

# 処理件数
cnt = 0

# システム日付
sysDate = ""

# 銘柄情報リスト
stockInfoList = []

# 保有している銘柄コードを取得する自前のAPI 
# スターサーバー上のMySQLからデータを取得し下記の形式で返します
# [{"stock_cd": "1942"}, {"stock_cd": "2730"}
res = requests.get("http://xxx.yyy.zzz/codeList")

# JSONに変換
data = json.loads(res.text)

# 銘柄コード毎に株価情報を取得
for cd in data:

    # 上限設定
    if cnt == 50:
        break

    # 株価情報取得
    res = requests.get("https://kabutan.jp/stock/?code=" + cd["stock_cd"])
    
    # レスポンスの解析
    doc = BeautifulSoup(res.text, "html.parser")
    
    # 株価情報の取得
    elPrice = doc.select("#kobetsu_left tr")
    
    # タイトルの取得 [<th scope="row">終値</th>]
    elTitle = doc.select("#kobetsu_left th")
    
    # 銘柄名の取得
    name = doc.select_one(".si_i1_1 h2")

    # 銘柄名が取得できない場合はスキップ
    if name is None:
        continue

    # 銘柄情報
    stockInfo = {}

    # 銘柄名を設定 銘柄コード+銘柄名で文字列を取得するので銘柄コードをブランクにする
    stockInfo["stock_cd"] = cd["stock_cd"]
    stockInfo["stock_na"] = name.getText().replace(cd["stock_cd"], "")

    # システム日付が未指定の場合は日付を取得
    if sysDate == "":

        # システム日付の取得 [<time datetime="2021-10-08">10月08日</time>]
        elDate = doc.select("#kobetsu_left h2>time")
        if elDate is None:
            break;
        
        # システム日付を(yyyymmdd形式)にする
        sysDate = elDate[0].attrs["datetime"].replace("-", "")
        
    # タイトルの解析
    if  elTitle is not None:

        # 始値、高値、安値、終値、出来高が存在しない場合はスキップする
        if len(elTitle) < 5:
            continue

        if elTitle[0].getText() != "始値":
            continue

        if elTitle[1].getText() != "高値":
            continue

        if elTitle[2].getText() != "安値":
            continue

        if elTitle[3].getText() != "終値":
            continue

        if elTitle[4].getText() != "出来高":
            continue

    # 銘柄情報の設定
    if elPrice is not None and len(elPrice) >= 5:
        stockInfo["open_price"] = getText(elPrice[0].select_one("td")).replace(",", "")
        stockInfo["high_price"] = getText(elPrice[1].select_one("td")).replace(",", "")
        stockInfo["low_price"] = getText(elPrice[2].select_one("td")).replace(",", "")
        stockInfo["closing_price"] = getText(elPrice[3].select_one("td")).replace(",", "")
        stockInfo["vol"] = getText(elPrice[4].select_one("td")).replace("\u00a0\u682a", "").replace(",", "")
        stockInfoList.append(stockInfo)
    
    # カウントアップ
    cnt+=1

    # 1秒停止
    time.sleep(1)

if sysDate != "":
    dt = {}
    dt["dt"] = sysDate
    dt["stock_info"] = stockInfoList
    filePath = "C:/data/"
    fileNa = sysDate + "_stock_price.json"
    with open(filePath + fileNa, mode='a') as fo:
        fo.write(json.dumps(dt))

出力したjsonファイル

{
    "dt": "20211012",
    "stock_info": [
        {
            "stock_cd": "1942",
            "stock_na": "\u95a2\u96fb\u5de5",
            "open_price": "918",
            "high_price": "922",
            "low_price": "914",
            "closing_price": "914",
            "vol": "184300"
        },
        {
            "stock_cd": "2730",
            "stock_na": "\u30a8\u30c7\u30a3\u30aa\u30f3",
            "open_price": "1097",
            "high_price": "1103",
            "low_price": "1081",
            "closing_price": "1084",
            "vol": "473000"
        }
    ]
}

日付、銘柄コード、銘柄名、始値、高値、安値、終値出来高を保存するようにしました。
取得した株価情報はこの後スターサーバー上のMySQLに登録する予定です。
本当は全部サーバー上でやりたいのですが、BeautifulSoupのインストールが難しそうなのでローカルで解析するようにしました。

次にやりたいこと

jsonファイルをFTPでサーバーにアップロードする


pythonで株価をスクレイピングする

前回Webページのスクレイピングまでできたので今回は株価のスクレイピングを試します。

freelancer.hatenablog.jp

株価を取得できるサイト

スクレイピングが許可されているサイトを調べてみると下記が見つかりました。

株探

kabutan.jp

みんかぶ

minkabu.jp

この中からページのソースを見て解析しやすそうだった株探から株価を取得することにしました。

株探のページ情報

URL

https://kabutan.jp/stock/?code=銘柄コード

武田薬品工業の株価情報(2021年10月7日時点)

kabutan.jp

武田薬品工業は現在保有中の銘柄です。

ソース(一部)

<div id="kobetsu_left">

<dl>
  <dt>前日終値</dt>
  <dd class="floatr">3,340.0&nbsp;(<time datetime="2021-10-06">10/06</time>)</dd>
</dl>


<h2><time datetime="2021-10-07">10月07日</time></h2>

<table>
  <tbody>
    <tr>
      <th scope="row">始値</th>
      <td>3,200.0</td>
      <td class="mark">&nbsp;</td>
      <td>(<time datetime="2021-10-07T09:03+09:00">09:03</time>)</td>
    </tr>
    <tr>
      <th scope="row">高値</th>
      <td>3,245.0</td>
            <td class="mark">&nbsp;</td>
            <td>(<time datetime="2021-10-07T14:52+09:00">14:52</time>)</td>
    </tr>
    <tr>
      <th scope="row">安値</th>
      <td>3,157.0</td>
            <td class="mark">&nbsp;</td>
            <td>(<time datetime="2021-10-07T09:06+09:00">09:06</time>)</td>
    </tr>
    <tr>
            <th scope="row">終値</th>
                  <td>3,221.0</td>
            <td class="mark">&nbsp;</td>
      <td>(<time datetime="2021-10-07T15:00+09:00">15:00</time>)</td>
    </tr>
  </tbody>
</table>
・
・
・
</div>

始値、高値、安値、終値辺りが取得したい項目

株探の解析

サンプルプログラム

import requests
from bs4 import BeautifulSoup

res = requests.get("https://kabutan.jp/stock/?code=4502")
doc = BeautifulSoup(res.text, "html.parser")
el = doc.select("#kobetsu_left tr")
if el is not None:
    for row in el:
        print(row.select("td"))

selectメソッドにCSSセレクタ("#kobetsu_left tr")を指定して要素を取得します。
取得した要素の中からさらにselectメソッドにCSSセレクタ("td")を指定して取得した要素を出力してみます。

実行結果

[<td>3,200.0</td>, <td class="mark"> </td>, <td>(<time datetime="2021-10-07T09:03+09:00">09:03</time>)</td>]
[<td>3,245.0</td>, <td class="mark"> </td>, <td>(<time datetime="2021-10-07T14:52+09:00">14:52</time>)</td>]
[<td>3,157.0</td>, <td class="mark"> </td>, <td>(<time datetime="2021-10-07T09:06+09:00">09:06</time>)</td>]
[<td>3,221.0</td>, <td class="mark"> </td>, <td>(<time datetime="2021-10-07T15:00+09:00">15:00</time>)</td>]
・
・
・
・

株価部分を含んだ要素が取得できてますね。
この結果からさらに株価部分を取得できれば目的は達成できます。

サンプルプログラム

import requests
from bs4 import BeautifulSoup

res = requests.get("https://kabutan.jp/stock/?code=4502")
doc = BeautifulSoup(res.text, "html.parser")
el = doc.select("#kobetsu_left tr")
if el is not None:
    print("始値" + el[0].select("td")[0].getText())
    print("高値" + el[1].select("td")[0].getText())
    print("安値" + el[2].select("td")[0].getText())
    print("終値" + el[3].select("td")[0].getText())

実行結果

始値3,200.0
高値3,245.0
安値3,157.0
終値3,221.0

必要なデータが無事取得できました。
保有している銘柄コードの配列作ってURLの銘柄コード部分を動的に変えれば保有している銘柄の株価が取得できます。
スクレイピングが許可されているとは言え大量にアクセスすると負荷をかけてしまうので数秒に1回くらいにアクセスするようにする必要はありそうです。


保有中の株の株価を自動集計したいのでpythonでスクレイピングする方法を調査

保有中の株の株価を自動で取得してデータベース化したいのでpythonスクレイピングする方法を調査しました。

requestsモジュール

最低限必要なのはrequestsモジュール

pipコマンドを使ってインストールします。

pip3 install requests

スクレイピング対象のページ

freelancer.hatenablog.jp

この記事の中で作ったサンプルーページをスクレイピング対象にします。

http://sample.kansai-fan.com/py/pymysql.py

サンプルプログラム

scrape_html.py

import requests
import json

res = requests.get("http://sample.kansai-fan.com/py/pymysql.py")
print(res.text)

requestsモジュールのgetメソッドで指定したURLのレスポンスを取得
レスポンスのtextプロパティを出力すればhtmlの内容が出力されます。

実行結果

<html><meta charset='utf-8'><body>
<h1>PyMySQLサンプル</h1>
件数2
</body></html>

BeautifulSoupモジュール

次にやりたいのはhtmlから特定のデータを抽出すること。
これはBeautifulSoupモジュールを使えば簡単に実装できます。

こちらもpipコマンドを使ってインストールします。

pip3 install beautifulsoup4

サンプルプログラム

scrape_html.py

import requests
import json
from bs4 import BeautifulSoup

res = requests.get("http://sample.kansai-fan.com/py/pymysql.py")
doc = BeautifulSoup(res.text, "html.parser")
el = doc.find("h1")
if el is not None:
    print(el.get_text())

コンストラクタで取得したhtmlとパーサーを指定すれば解析は完了。
後はfindメソッドを使って要素を取得、get_text()メソッドで内容を表示させます。

実行結果

PyMySQLサンプル

<h1>の内容が出力されました。
findメソッドにタグを指定すると最初の要素のみ取得。
複数の要素を取得したいときはfind_allメソッドを使う必要があります。

これでスクレイピングの準備は完了。
次回は株価取得に挑戦する予定です。


スターサーバーでpythonからMySQLに接続できた!

前回はpythonで使えるMySQLライブラリを調べてみました。

freelancer.hatenablog.jp

結論から言うとmysql-connector-pythonとPyMySQLを使ってMySQLに接続してデータを取得することができました。

ライブラリのダウンロードと設置

mysql-connector-python

下記からmysql-connector-python-8.0.26.tar.gz をダウンロードする。

pypi.org

解凍してmysqlディレクトリをサーバーにアップロードする。

f:id:freelancer13:20211003211620p:plain
mysql-connector-python

PyMySQL

下記からPyMySQL-1.0.2.tar.gz をダウンロードする。

pypi.org

解凍してpymysqlディレクトリをサーバーにアップロードする。

f:id:freelancer13:20211003211818p:plain
PyMySQL

アップロード後

f:id:freelancer13:20211003213757p:plain
アップロード後のディレクトリ構成

動作確認用のテーブル

f:id:freelancer13:20211003214914p:plain
動作確認用のテーブル

サンプルプログラム(mysql-connector-python)

mysqlconnector.py

#!/usr/bin/python3.6
import mysql.connector as mydb
import sys, io

sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding = 'utf-8')

connection = mydb.connect(host='aaa.bbb.ccc',
                          port='3306',
                          user='dbuser',
                          password='dbpassword',
                          database='db_sample',
                          charset='utf8'
)
cursor = connection.cursor()
cursor.execute("select count(*) as cnt from sample1")

cnt = 0
row = cursor.fetchone()
if row is not None:
     cnt = row[0]

cursor.close()
connection.close

print("Content-Type: text/html; charset=utf-8\n")
print("<html><meta charset='utf-8'><body>")
print("<h1>mysql-connector-pythonサンプル</h1>")
print("件数" + str(cnt))
print("</body></html>")

サンプルプログラム(PyMySQL)

pymysql.py

#!/usr/bin/python3.6
import pymysql
import pymysql.cursors

import sys, io

sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding = 'utf-8')

# コネクションの作成
connection = pymysql.connect(host='aaa.bbb.ccc',
                             user='dbuser',
                             port=3306,
                             password='dbpassword',
                             db='db_sample',
                             charset='utf8',
                             cursorclass=pymysql.cursors.DictCursor)
     
cursor = connection.cursor()
cursor.execute("select count(*) as cnt from sample1")

cnt = 0
row = cursor.fetchone()
if row is not None:
     cnt = row["cnt"]

print("Content-Type: text/html; charset=utf-8\n")
print("<html><meta charset='utf-8'><body>")
print("<h1>PyMySQLサンプル</h1>")
print("件数" + str(cnt))
print("</body></html>")

コネクション関連の設定はmysql-connector-pythonとほぼ同じ。
ポートの指定が数値型なのとcursorclassにpymysql.cursors.DictCursorを指定することにより結果を辞書型で返してくれます。

実行結果

f:id:freelancer13:20211003220723p:plain
mysqlconnector.pyの実行結果

f:id:freelancer13:20211003221644p:plain
pymysql.pyの実行結果

件数がちゃんと出力されました。
これでスターサーバーでpythonMySQLを使ってWebアプリを作る環境が整いました。


スターサーバーでpythonからMySQLに接続するのは無理なのか?

前回はBottleのルーティング機能を試しました。

freelancer.hatenablog.jp

後はMySQLからデータを取得できればそれなりのアプリが作れるはず

調べてみるとpythonの標準ライブラリにはMySQLに接続するライブラリがなさそう

pythonで使えるMySQLライブラリを調べてみると以下が見つかりました。

MySQLライブラリ一

基本的にはpipを使ってインストールする必要があるのでpipが使えない?スターサーバーではインストールができない

pypi.org

こちらで各ライブラリのソースをダウンロードできるのでソースをダウンロードしてFTPでアップロードすれば動くんじゃないか?

と思い動作検証することにしました。

次回はMySQLdbを使ってスターサーバーでMySQLに接続できるかを検証したいと思います。