PYTHON + Selenium で 株式銘柄を取得・保存する

概要

 python と Selenium で株式銘柄情報を取得し、sqlite3 の DBファイルにデータ保存してみます。ちなみに株のことは全く判っていません。
 今回、元になるデータは下記ページから取得します。

 株式投資メモ(価DB): https://kabuoji3.com/

 上の図の「個別株価データ」をクリックすると、次のページに移動します。図の中段には、各銘柄毎のコード・名称・市場・始値・etc のリストがあります。下段には [1] ~ [31] の頁を選択するボタンがあります。
 頁選択ボタンをクリックすると、リストが各頁に対応する内容に変わります。 本記事作成時、基本 120(件/頁)と最終頁に 6件 の銘柄が表示されていますので、合計 3606(件)の銘柄のデータがありました。
 これらのデータを自動で取得してみます。

https://kabuoji3.com/stock/

実行結果

 下の図は python プログラム実行 時に、ブラウザから取得したデータを python -IDLE に出力している状況です。最下行に処理時間を出力していますが、 3606(件)のデータを取得・保存するのに、約15分(=928(秒))掛かっています。

 次の図は、SQLiteブラウザー(DB Browser for SQLite)を使って、登録したデータテーブルを開いた状況です。詳細確認はしていませんが、3606(件)のデータが登録されていますので、とりあえず、“良し” とします。


プログラム

 python プログラムは次の通りです。

import time
import sys
import ctypes
import os

import sqlite3

import tkinter as tk
from tkinter import messagebox
import tkinter.simpledialog as simpledialog

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import Select
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.alert import Alert


# プログラム終了処理
def prog_end( drv , title , msg ):
    if title != "" and msg != "":
        messagebox.showerror( title , msg )

    if drv != "" :
        drv.quit()
    sys.exit()

# ブラウザ起動
def connect_browser( drv , url , wait_id ):
    try:
        drv.get(url);
        WebDriverWait(driver, 30).until(EC.presence_of_element_located((By.ID, wait_id)))
    except:
        prog_end( drv , "起動異常" , "待機時間を超過しました。\r\n\r\n処理を中断します。\r\n\r\n" + url )


# ページ内リスト項目取得
def get_list_item( drv , idx_pag , idx_itm ):
    tmp = []
    try:
        stk_fct = drv.find_element_by_xpath("//*[@id='base_box']/div/div[3]/div/div/div/table/tbody/tr[" + idx_itm + "]/td[1]/a").text
        
        tmp.append( ("00" + idx_pag)[-2:] + "-" + ("000" + idx_itm)[-3:] )                                  # 取得頁・行番号
        tmp.append(stk_fct.split(' ',1)[0])                                                                 # コード
        tmp.append(stk_fct.split(' ',1)[1])                                                                 # 銘柄名称

        for idx in range(5):
            idx_td = str(2 + idx)
            stk_tmp = drv.find_element_by_xpath("//*[@id='base_box']/div/div[3]/div/div/div/table/tbody/tr[" + idx_itm + "]/td[" + idx_td + "]").text
            tmp.append(stk_tmp)                                                                             # 市場,始値,高値,安値,終値

    except:
        prog_end( drv , "リスト項目取得異常" , "リスト項目取得中に異常が発生しました。\r\n\r\n処理を中断します。" )

    return tmp


def sqlite3_process( ope , dat_ary , drv ):
    db_path = "stk_test.sqlite"                                                                             # DBファイルパス
    tbl_nam = "list_company"                                                                                # テーブル名

    con = sqlite3.connect(db_path)                                                                          # DB接続
    csr = con.cursor()                                                                                      # カーソル生成

    try:
        if ( ope == "crt" ) :
            csr.execute("DROP TABLE IF EXISTS " + tbl_nam)                                                  # 既にテーブルがある場合、抹消
            sql = "CREATE TABLE IF NOT EXISTS " + tbl_nam + " "
            sql += "( ITM_NO TEXT , CODE TEXT PRIMARY KEY , CMPANY TEXT , MARKET TEXT , P_S TEXT , P_H TEXT , P_L TEXT , P_E TEXT )" 
            csr.execute(sql)                                                                                # 新規テーブル作成

        elif( ope == "ins_many" ) :
            # 同時に複数データ挿入(連続的に行うと失敗するので保留)
            csr.executemany("INSERT INTO " + tbl_nam + "( ITM_NO , CODE , CMPANY , MARKET ) VALUES (?, ?, ?, ?)", dat_ary)

        elif( ope == "ins_mono" ) :
            sql = "INSERT INTO " + tbl_nam + " VALUES ('" + dat_ary[0] + "','" + dat_ary[1] + "','" + dat_ary[2] + "','"
            sql += dat_ary[3] + "','" + dat_ary[4] + "','" + dat_ary[5] + "','" + dat_ary[6] + "','" + dat_ary[7] + "')"
            csr.execute(sql)

        con.commit()
        con.close()
    
    except:
        prog_end( drv , "SQLITE3_ERROR" , "SQLITE3 処理中に異常が発生しました。(" + ope + ")\r\n\r\n処理を中断します。" )


#■■■ MAIN PART ■■■
time_s = time.time()                                                                                        # 開始時間
 
# メッセージボックス最前面・不要枠非表示
root = tk.Tk()
root.attributes('-topmost',True)
root.withdraw()
root.lift()
root.focus_force()

driver = webdriver.Chrome("./driver/chromedriver.exe")                                                      # ■WEB DRIVER 設定■

connect_browser( driver , 'https://kabuoji3.com/stock/?page=1' , 'contents_wrap' )                          # ブラウザ頁開く
time.sleep(3)
pg_pth = driver.find_elements_by_xpath("//*[@id='base_box']/div/ul/li")                                     # 頁内 対応要素取得→頁数

sqlite3_process( "crt" , [] , driver )

stk_inf = []

# 1ページ毎にページを開き、リスト項目取得
for idx_li in range(len(pg_pth)):
    pg_cur = str(idx_li+1)
    connect_browser( driver , 'https://kabuoji3.com/stock/?page=' + pg_cur , 'contents_wrap' )              # ブラウザ頁開く
    time.sleep(3)
    stk_list = driver.find_elements_by_xpath("//*[@id='base_box']/div/div[3]/div/div/div/table/tbody/tr")   # 頁内 対応要素取得→リスト項目数

    # ページ内リスト項目取得
    for idx_tr in range(len(stk_list)):
        itm_inf = get_list_item( driver , pg_cur , str(idx_tr + 1) )
        sqlite3_process( "ins_mono" , itm_inf , driver )
        stk_inf.append(itm_inf)

        print(stk_inf[len(stk_inf)-1])


elapsed_time = time.time() - time_s                                                                         # 経過時間
print("")
print ("処理時間 : {0}".format(elapsed_time) + "[sec]")

messagebox.showinfo('正常終了','処理を終了しました。')
prog_end( driver , "" , "" )

 

 概要のところで、頁選択ボタンについて記載しましたが、実際のプログラムでは次の様に頁番号を指定し、 URLを開けばよいので、 ボタンをクリックする様な処理は行いません。

‘https://kabuoji3.com/stock/?page=’ + ’頁番号’

 行番101で先頭頁を開いた後、行番103で頁数(ボタンの要素数)を取得し、行番110からのループ内で全頁のデータを取得していきます。行番112で頁番号を指定し1頁から順に開きます。
 行番114で各頁のリスト項目数(要素数)を取得し、行番117からのループ内で各リスト項目毎のデータを取得します。
 行番118関数でデータを取得し、行番119関数でSQLiteデータベースファイルにデータを書き込みます。
 行番120は2次元配列にデータを追加します。
 最初 “executemany” を使って、2次元配列をまとめてデータ登録するテストをしましたが、連続して実施するとエラーが発生するので断念しています。従って、あまり2次元配列に追加する理由はないのですが、2次元配列の追加方法としてプログラム内には残しています。
 

まとめ

 最初、頁が完全に開く前にデータ取得処理を開始していた様で、保存データ数が少なかったので、プログラム行番113に遅延処理を入れて対応しました。
 ネット接続環境・利用時間帯等によっても、ブラウザの開く時間も変わるので、留意する必要があると感じました。
 また、株式コードは4桁と思っていましたが、5桁の場合もある様です。取得文字列の前から4桁をKeyCodeとして、DBファイル登録すると、重複エラーが発生するので気づきました。