見出し画像

【データの集め方講座】Node.jsを使って株データを収集, MySQLに保存

はじめに


ごあいさつ

ご高覧いただきありがとうございます.
ソフトウェアエンジニアのKitaharaです.
本日はNode.jsを使った株式情報の収集方法を解説します!

==================================================

今回の記事は下記の記事のNode.jsの実装になります.

==================================================

株式情報を取得するメリット・取得方法


株式を取得すると何がいいのか

今回のテーマの株式ですが, 収集してみたいという方も多いのではないでしょうか?

ですが, 株式がどんなものか知らない方もいらっしゃると思うので, まずは株式が何かを紹介してから株式情報を収集することの有用性についてお話いたします.

株式の定義ですが, SMBC日興証券のサイトに用語集があり, 掲載されていましたので引用させていらだきます.

株式とは、株式会社が資金を出資してもらった人に対して発行する証券のことです。
株式の発行は、その企業が事業を行うために必要な資金を集める手段のひとつで、例えば会社が工場を建てて製品をつくる、お店を出して販売するといった場合に必要な資金を集める際に行われます。株式を発行して得た資金は、銀行借入や社債を発行して得た資金とは異なり、返済の義務はありません。出資した人(株主)はお金が返ってこない代わりに、保有株式の割合に応じた経営参加ができ、利益が出たときには保有株式数に応じて配当がもらえます。また、企業が成長し株式の価値が上がったときには、その株式を売却して利益を得ることができます。

SMBC日興証券 太字は筆者が追加

この用語集から分かる通り, 成長すると思った会社の株式を買ってその後会社が成長すると株式の値段が上がって売却益を得ることができます. 多くの人が株式情報を収集したいとなる理由はおそらく株式データを使って分析をし, 高騰しそうな株を見つけたいからではないでしょうか?

==================================================

株式情報をスクレイピングで取得するのは難しい

しかしながら, 現在日本において株式情報を取得することは難しいです.
その理由は多くの証券会社・株式情報サイトでスクレイピングが禁止されているからです.

例えばYahooファイナンスでは以下のように記載がされています.

Yahoo!ファイナンスでは、Yahoo!ファイナンスに掲載している株価やその他のデータを、プログラム等を用いて機械的に取得する行為(スクレイピング等)について、システムに過度の負荷がかかり、安定したサービス提供に支障をきたす恐れがあることから禁止しています。

また、Yahoo!ファイナンスに掲載する情報の著作権その他一切の権利は、ヤフー株式会社、情報提供者またはその他の権利者に帰属します。
当該掲載情報の転用、複製および外部配信ならびに販売を含む商用利用等の一切を固く禁じています。

株価データのダウンロードを利用したい場合は、「VIP倶楽部」の「時系列データダウンロード」をご利用ください。

YahooFinance 太字は筆者によるもの

理由はここに書かれている通り, システムに負荷がかかるからです.
株式情報を欲しい人(特にリアルタイム)だと1秒間に何回もリクエストを送ることが想定されますので, 負荷がかかってしまうのです. そのため, 他のサイトでもスクレイピングが禁止されていることが多いです.

補足ですが, そもそも大量のリクエストによってによってサーバーに負荷をかけること自体迷惑行為(Dos攻撃とみなされることもあります)ですのでやらないように気を付けるべきです. もしリクエストを何回も投げることになる場合は専用のモジュール等で時間をかけるように調整し, サイト制作者様に迷惑をかけないようにしましょう.

なお, 今回利用する無尽蔵に関しては規約上問題がなく, かつリクエストの回数も一日一回で済むのでサイト制作者様に迷惑をかけることはないです.

==================================================

どのようにして取得するのか

今回は以下の流れでデータを取得し, 保存します.

(1) サイト無尽蔵から当日のデータをzipファイルで取得
(2) zipファイルを解凍し, CSVファイルを別ディレクトリに保存
(3) zipファイルを削除
(4) データをMySQLに格納
(5) csvファイルを削除

無尽蔵の特徴としてデータをCSVファイルで配布しているということがあります. CSVファイルの取得には一回のリクエストで済むわけですから実行時間が短いです.

作るものの説明


当日の株式データをcsvファイル形式で取得するプログラムとcsvファイルに記述されたデータをMySQLにアップロードするプログラムを作成します.

使用するものの説明


  • 無尽蔵

    • 色々なデータを掲載しているデータサイト

      • CSVファイル形式で当日の株価データを取得することができます

    • 管理人によるとデータの更新が確約されているわけではないです

  • Node.js

  • MySQL

    • オラクル社によって開発されているSQLデータベース管理システム

    • SQLのデータベース管理システムの中でも根強い人気を誇っています

    • 公式ドキュメント

環境構築


言語のバージョン

Node.js のバージョンとmysqlのバージョンです.
OSはUbutntu 20.04を使っています.

~/KabuBot$ node --version
v14.18.0


mysql> select version();
+-------------------------+
| version()               |
+-------------------------+
| 8.0.28-0ubuntu0.20.04.3 |
+-------------------------+
1 row in set (0.01 sec)


ディレクトリの構成

適宜ファイルを作成してください.
(ファイルの名前は必ず下記のようにする必要はありません.)
最終的なディレクトリ構成がこのようになっていれば大丈夫です.

.
├── csv
├── download.js
├── extract_file.js
├── node_modules
├── package-lock.json
├── package.json
└── zipfile


テーブルの作成

利用するデータベースとテーブルを作成しておきましょう.
(私はDB名はkabuにしました.)

mysql> create table YOUR_DATABASE_NAME.stocks (
    id int AUTO_INCREMENT not null PRIMARY KEY,
    date date not null,
    stock_name varchar(50) not null,
    stock_code int not null,
    open float not null, 
    high float not null,  
    low float not null,
    close float not null,   
    volume float not null, 
    market varchar(10) not null
);

プログラムの作成


INPUT (download.js)

var request = require('request');
const fs = require('fs');
var unzipper = require('unzipper');

console.log('library has been loaded!');

var dt = new Date();
var year = dt.getFullYear();
var month = ("0" + (dt.getMonth()+1)).slice(-2)
var date = ("0" + dt.getDate()).slice(-2);
var year_last_two_degits = String(year).substr(2,2);

const zip_file_name = 'T'+year_last_two_degits+month+date+'.zip';
const csv_file_name = 'T'+year_last_two_degits+month+date+'.csv';

var url = 'http://mujinzou.com/d_data/'+year+'d/'+year_last_two_degits+'_'+month+'d/'+zip_file_name;
var zip_file_path = './zipfile/kabu_data.zip';
var csv_dir_path = './csv';
var csv_file_path = csv_dir_path+csv_file_name

console.log('vars have been loaded!');

const rqt = (target_url) => {
    return new Promise((resolve, reject) => {
        request(
            {method: 'GET', url: target_url, encoding: null},
            function (error, response, body){
                if(!error && response.statusCode === 200){
                    fs.writeFileSync(zip_file_path, body, 'binary');
                    fs.createReadStream(zip_file_path)
                        .pipe( unzipper.Extract({ path: csv_dir_path }) )
                    resolve()
                }
            }
        );
    })
}

(async () => { 
    const body = await rqt(url)
    console.log('Done!');
})()


INPUT (extract_file.js)

var mysql = require('mysql');
const fs = require('fs');
const csv = require('csv');
const iconv = require('iconv-lite');
const encoding = require('encoding-japanese');
console.log('library has been loaded!');

// MySQLの設定
var connection = mysql.createConnection({
    host: 'localhost',
    user: 'your_user_name',
    password: 'your_password',
    database:'kabu'
});

var csv_dir_path = './csv';
var csv_file_path = csv_dir_path+csv_file_name

const parser = csv.parse((error, data) => {
    //ループしながら1行ずつ処理
    data.forEach((element, index, array) => {
        connection.query(
            'INSERT INTO stocks (date, stock_name, stock_code, open, high, low, close, volume, market) VALUES (?,?,?,?,?,?,?,?,?)',
            [
                element[0],
                String(element[3]).substr(0,5),
                element[1],
                element[4],
                element[5],
                element[6],
                element[7],
                element[8],
                element[9]
            ],
            (error, results) => {
                if (error) {
                    console.log(error)
                    return
                }
                console.log(results)
                return
            }
        );
    })
    connection.end();
});

var buf = fs.readFileSync(csv_file_path);
fs.readFile(csv_file_path, function(err, data){
    if (err) throw err;
    var buf    = new Buffer.from(data, 'binary'); 
    var retStr = iconv.decode(buf, "Shift_JIS"); //作成したバッファを使い、iconv-liteでShift-jisからutf8に変換
    fs.writeFile(fixed_csv_file_path,retStr,(error)=>{
        console.log('処理データをCSV出力しました。');
    });
    fs.createReadStream(fixed_csv_file_path).pipe(parser);
});


INPUT (TERMINAL)

~/KabuBot$ node download.js
~/KabuBOt$ node extract_file.js
mysql> use YOUR_DATABASE
mysql> select * from your_table

プログラムの解説


download.js

最初のurlを設定する部分の解説です.

var dt = new Date();
var year = dt.getFullYear();
var month = ("0" + (dt.getMonth()+1)).slice(-2)
var date = ("0" + dt.getDate()).slice(-2);
var year_last_two_degits = String(year).substr(2,2);

const zip_file_name = 'T'+year_last_two_degits+month+date+'.zip';
const csv_file_name = 'T'+year_last_two_degits+month+date+'.csv';

var url = 'http://mujinzou.com/d_data/'+year+'d/'+year_last_two_degits+'_'+month+'d/'+zip_file_name;

無尽蔵のURLは

  • year: 年

  • month: 月(常に2桁)

  • day: 日付(常に2桁)

  • year_last_tow_digits: yearの下二桁

とするときに

"http://mujinzou.com/d_data/"+year+"d/"+year_last_two_digits+"_"+month+"d/T"+year_last_two_digits+month+day+".zip"

となっています. これを作るためにDate型のインスタンスを作成しています.
下記の部分は不思議に感じる方がいるかもしれませんが, これは1月が0と表示される使用のためです.

var month = ("0" + (dt.getMonth()+1)).slice(-2)

node.jsの日付処理が気になる方は以下の記事を参考にしてみてください.

次にデータをダウンロードする部分です.

const rqt = (target_url) => {
    return new Promise((resolve, reject) => {
        request(
            {method: 'GET', url: target_url, encoding: null},
            function (error, response, body){
                if(!error && response.statusCode === 200){
                    fs.writeFileSync(zip_file_path, body, 'binary');
                    fs.createReadStream(zip_file_path)
                        .pipe( unzipper.Extract({ path: csv_dir_path }) )
                    resolve()
                }
            }
        );
    })
}

(async () => { 
    const body = await rqt(url)
    console.log('Done!');
})()

requestメソッドでHTTPリクエストを投げ, zipファイルをダウンロードします. zipファイルはそのまま使えないので解凍して中身をcsv/に保存します.

extract_file.js

mysqlのコネクション設定

// MySQLの設定
var connection = mysql.createConnection({
    host: 'localhost',
    user: 'your_user_name',
    password: 'your_password',
    database:'kabu'
});

作成したテーブルにアクセスできるか試してみましょう.
動かない場合は以下のことを確認してみてください.

  • MySQLは起動しているか?

    • 起動していないときはsystemctlなりserviceなりで起動してみましょう

  • ユーザー名とパスワードは正しいか

    • MySQLにログインできるか試すとよいです

  • DB名はあっているか

    • 作ったDB内にテーブルがあるか show tables; で確認しましょう

次にデータを登録する部分を見ていきます.
データの登録はcsvファイルを読み込んだ段階で行います.

const parser = csv.parse((error, data) => {
    //ループしながら1行ずつ処理
    data.forEach((element, index, array) => {
        connection.query(
            'INSERT INTO stocks (date, stock_name, stock_code, open, high, low, close, volume, market) VALUES (?,?,?,?,?,?,?,?,?)',
            [
                element[0],
                String(element[3]).substr(0,5),
                element[1],
                element[4],
                element[5],
                element[6],
                element[7],
                element[8],
                element[9]
            ],
            (error, results) => {
                if (error) {
                    console.log(error)
                    return
                }
                console.log(results)
                return
            }
        );
    })
    connection.end();
});

Insert 文の Valuesの部分ですが, 使用する言語によって変わることがあります. (Pythonでの実装だと%sを書いています)

また, 最後のconnection.end()を書かないと処理が終了しないので書き忘れないようにしてください.

おわりに


今回はNode.jsとMySQLを使って株式情報を取得し保存する方法を解説しました!参考になったという方はぜひハートボタンを押していってください!

モチベーションが上がります!

記事内で不明な点等ございましたら気軽にご連絡ください.

Twitter: @kitahara_devemail: kitahara.main1@gmail.com


参考文献



この記事が気に入ったらサポートをしてみませんか?