【Python】スクレイピングで株ドラゴンの出来高急増 初動 銘柄 ランキングに初登場した銘柄を抽出する

スクレイピング
この記事は約19分で読めます。

株価上昇の初動を捉えるには、株ドラゴンの出来高急増 初動 銘柄 ランキングを見たほうが良い

以前の記事で株ドラゴンの年初来安値からの値上がり率ランキングのランキング推移を可視化しました。

ただ、株価上昇の初動を捉えるということを考えると出来高急増銘柄を監視しておいたほうが良いのではと思い、前回のプログラムを少し修正して、可視化してみました。

株ドラゴンの出来高急増 初動 銘柄 ランキングページをスクレイピング

モジュールのインポート

まずは必要なモジュールをインポートします。スクレイピングにはBeautifulSoupというモジュールを用います。

import requests
from datetime import datetime, timedelta
from bs4 import BeautifulSoup
import numpy as np
import pandas as pd

URLの確認

株ドラゴンの出来高急増 初動 銘柄 ランキングのページのURLは、以下のように(日付)の箇所に表示されるランキングの日付が入るようなリンク担っています。
(日付)の書き方は2019/01/01というようなスラッシュで区切られた日付になります。

url = https://www.kabudragon.com/ranking/(日付)/dekizou200.html

したがって、以下のように日付を1日ずつかえながら変えながら各日付のurlにアクセスしていくことにします。

st_date = '2019/01/01'
end_date = '2019/01/10'
st_date = datetime.strptime(st_date, '%Y/%m/%d')
end_date = datetime.strptime(end_date, '%Y/%m/%d')
while st_date <= end_date:
    date = datetime.strftime(st_date, '%Y/%m/%d' )
    url = 'https://www.kabudragon.com/ranking/{}/yl_age200.html'.format(date)
    st_date = st_date + timedelta(days = 1)
    print(url)

各ページの情報をデータフレーム化

先程のURLにアクセスし、そのページのhtmlタグをみると以下の項目がテーブル構造で存在していることがわかります。
「順位、コード、名称、市場、日付、終値、前日比(円)、前日比(%)、5日平均比出来高急増率、出来高、高値、安値」
ページの詳細な構成がどうなっているかの説明は特にしませんが、下記のコードでpandasのデータフレームとして取得することができます。

if文を入れている理由は、土日などの市場が休場しているときはデータが存在していないため、エラーを回避するためです。

url = 'https://www.kabudragon.com/ranking/2019/01/10/dekizou200.html'.format(date)

soup = BeautifulSoup(requests.get(url).text, "lxml")
if len(soup.body.find_all('tr')) > 20:
    start_line = [i  for i in range(14,20) if soup.body.find_all('tr')[i].text.splitlines()[1] == '順位']
    items = [v.text.splitlines() for i, v in enumerate(soup.body.find_all('tr')) if i > start_line[0]]
    cols = ['', '順位','コード', '名称', '市場','日付','終値','前日比(円)','前日比(%)','5日平均比出来高急増率','出来高','高値','安値']
    df = pd.DataFrame(np.array(items[0]).reshape(1,-1),columns=cols)
    for i in range(1,200):
        df_add = pd.DataFrame(np.array(items[i]).reshape(1,-1),columns=cols)
        df = pd.concat([df, df_add],axis = 0)
else:
    print('休場')  

df.head()

上記を実行すると以下のような結果が得られると思います。

このテーブルを取得するコードを関数化し、先程の日付を1日ずつ変更していくコードと合わせると以下のように書くことができます。

def get_table(url):
    soup = BeautifulSoup(requests.get(url).text, "lxml")
    if len(soup.body.find_all('tr')) > 20:
        start_line = [i  for i in range(14,20) if soup.body.find_all('tr')[i].text.splitlines()[1] == '順位']
        items = [v.text.splitlines() for i, v in enumerate(soup.body.find_all('tr')) if i > start_line[0]]
        cols = ['', '順位','コード', '名称', '市場','日付','終値','前日比(円)','前日比(%)','5日平均比出来高急増率','出来高','高値','安値']
        df = pd.DataFrame(np.array(items[0]).reshape(1,-1),columns=cols)
        print(items[0]) #テーブルの1行目だけ表示
        for i in range(1,200):
            df_add = pd.DataFrame(np.array(items[i]).reshape(1,-1),columns=cols)
            df = pd.concat([df, df_add],axis = 0)
        return df,True
    else:
        print('休場')
        return np.nan, False

#データ取得開始日と終了日を設定
st_date = '2019/01/01' #データ取得開始日
end_date = '2019/01/10' #データ取得終了日
st_date = datetime.strptime(st_date, '%Y/%m/%d') #datetimeのフォーマットに変換
end_date = datetime.strptime(end_date, '%Y/%m/%d') #datetimeのフォーマットに変換

dfs = []

while st_date <= end_date:
    date = datetime.strftime(st_date, '%Y/%m/%d')
    url = 'https://www.kabudragon.com/ranking/{}/dekizou200.html'.format(date)
    st_date = st_date + timedelta(days = 1) 
    
    df, flg = get_table(url) #各日付のデータフレームと市場が休場かどうかのフラグを取得(休場のときはflg = False)
    if flg:
        dfs.append(df) #市場が空いているときのみデータフレームをdfsに追加していく

おそらく以下のような結果が出力されると思います。
※テーブルの1行目だけがprintされるようにしています。
各ページのテーブルをdfsに追加していっています。

2019/01/01
休場
2019/01/02
休場
2019/01/03
休場
2019/01/04
['', '1', '7851', 'カワセコンピュータサプライ', '東2', '1/4(金)', '424', '+45', '+11.87%', '437.12%', '171,000', '424', '381']
2019/01/05
休場
2019/01/06
休場
2019/01/07
['', '1', '7895', '中央化学', 'JQ', '1/7(月)', '296', '+61', '+25.96%', '413.72%', '147,200', '313', '259']
2019/01/08
['', '1', '3377', 'バイク王&カンパニー', '東2', '1/8(火)', '170', '+17', '+11.11%', '468.65%', '1,898,400', '189', '169']
2019/01/09
['', '1', '9969', 'ショクブン', '東2', '1/9(水)', '177', '+40', '+29.20%', '488.34%', '6,395,800', '186', '140']
2019/01/10
['', '1', '7602', 'カーチスホールディングス', '東2', '1/10(木)', '183', '+5', '+2.81%', '472.99%', '492,000', '228', '177']

取得した1日毎のデータフレームを統合します。

df = pd.concat(dfs).reset_index(drop = True)
print(df.shape)
>> (1000, 13)

これで複数の日にちのデータフレームを1つのデータフレームに変換することができ、1000行のデータフレームになっていると思います。

初登場の日付を取得する

先程のデータはすべての日付のランキングを1つのテーブルにしているので、毎日ランキングに登場しているような銘柄はテーブルの中に何度も登場しています。
初めてランキングに登場した日付を知りたいので、重複している銘柄は初登場の日付以外は削除することとします。

#初登場の日付のみに絞る
df = df.drop(df[df.duplicated(subset = 'コード')].index,axis=0)
pritn(df.shape)
>>(816, 13)

すると816行のデータになったので、184データは重複していたということになります。

初登場の日付を「年/月/日」の形式に変換する

これまでの処理で、データは初登場した銘柄と日付が取得できているはずですが、日付が月/日(曜日)というような表記になっているため扱いづらいです。なので「年/月/日」のフォーマットに変換します。

#初登場の日付をdatetimeの日付にする
df['date'] = [datetime.strptime('2019/' + i.split('(')[0],'%Y/%m/%d') for i in df['日付']]
df.head()

すると以下のようなデータフレームにdateという列が追加されているはずです。

株ドラゴンの出来高急増 初動ランキング 2019年7月ランキング初登場銘柄

それでは2019年1月1日〜2019年7月31日の期間においてランキングに登場した銘柄のデータテーブルを取得し、そのうち7月1日〜7月31日の登場した銘柄だけをピックアップしたいと思います。
これまでのコードをまとめる形で書きたいと思います。

# -*- coding: utf-8 -*-
import requests
from datetime import datetime, timedelta
from bs4 import BeautifulSoup
import numpy as np
import pandas as pd

def get_table(url):
    soup = BeautifulSoup(requests.get(url).text, "lxml")
    if len(soup.body.find_all('tr')) > 20:
        start_line = [i  for i in range(14,20) if soup.body.find_all('tr')[i].text.splitlines()[1] == '順位']
        items = [v.text.splitlines() for i, v in enumerate(soup.body.find_all('tr')) if i > start_line[0]]
        cols = ['', '順位','コード', '名称', '市場','日付','終値','前日比(円)','前日比(%)','5日平均比出来高急増率','出来高','高値','安値']
        df = pd.DataFrame(np.array(items[0]).reshape(1,-1),columns=cols)
        print(items[0])
        for i in range(1,200):
            df_add = pd.DataFrame(np.array(items[i]).reshape(1,-1),columns=cols)
            df = pd.concat([df, df_add],axis = 0)
        return df,True
    else:
        print('休場')
        return np.nan, False
    
st_date = '2019/01/01'
end_date = '2019/07/31'
st_date = datetime.strptime(st_date, '%Y/%m/%d')
end_date = datetime.strptime(end_date, '%Y/%m/%d')

dfs = []

while st_date <= end_date:
    date = datetime.strftime(st_date, '%Y/%m/%d' )
    print(date)
    url = 'https://www.kabudragon.com/ranking/{}/dekizou200.html'.format(date)
    st_date = st_date + timedelta(days = 1)
    
    df, flg = get_table(url)
    if flg:
        dfs.append(df)
        
#複数ページのデータを1つのデータフレームにする
df = pd.concat(dfs).reset_index(drop = True)

#初登場の日付のみに絞る
df = df.drop(df[df.duplicated(subset = 'コード')].index,axis=0)

#初登場の日付をdatetimeの日付にする
df['date'] = [datetime.strptime('2019/' + i.split('(')[0],'%Y/%m/%d') for i in df['日付']]

#対象の期間の指定
target_start = '2019/07/01'
target_end = '2019/07/31'

data = df[(df['date']>=target_start) & (df['date']<=target_end)]
output = data.loc[:,['コード', '名称', '市場', 'date']]
output = output.rename(columns = {'コード':'code', '名称':'name','市場':'market'}).set_index('date', drop = True)
print(output)

実行すると少し時間がかかりますが、以下のような結果が得られていると思います。
全部で50銘柄となります。
今後の動きにチェックしていくのが良いかと思います。

datecodenamemarket
2019-07-018566リコーリース東1
2019-07-022974大英産業福岡
2019-07-028890レーサムJQ
2019-07-039384内外トランスライン東1
2019-07-047065ユーピーアール東2
2019-07-054931新日本製薬東マ
2019-07-059621建設技術研究所東1
2019-07-058585オリエントコーポレーション東1
2019-07-087677ヤシマキザイ東2
2019-07-082769ヴィレッジヴァンガードコーポレーションJQ
2019-07-089007小田急電鉄東1
2019-07-088267イオン東1
2019-07-088904サンヨーハウジング名古屋東1
2019-07-103110日東紡東1
2019-07-108331千葉銀行東1
2019-07-109987スズケン東1
2019-07-108074ユアサ商事東1
2019-07-114927ポーラ・オルビスホールディングス東1
2019-07-162201森永製菓東1
2019-07-164443Sansan東マ
2019-07-167908KIMOTO東1
2019-07-188342青森銀行東1
2019-07-183708特種東海製紙東1
2019-07-188346東邦銀行東1
2019-07-187751キヤノン東1
2019-07-193436SUMCO東1
2019-07-222502アサヒグループホールディングス東1
2019-07-226197ソラスト東1
2019-07-227067ブランディングテクノロジー東マ
2019-07-234445リビン・テクノロジーズ東マ
2019-07-245631日本製鋼所東1
2019-07-256358酒井重工業東1
2019-07-268864空港施設東1
2019-07-262002日清製粉グループ本社東1
2019-07-264206アイカ工業東1
2019-07-263408サカイオーベックス東1
2019-07-264559ゼリア新薬工業東1
2019-07-262413エムスリー東1
2019-07-267266今仙電機製作所東1
2019-07-298355静岡銀行東1
2019-07-297911凸版印刷東1
2019-07-296861キーエンス東1
2019-07-308591オリックス東1
2019-07-308113ユニ・チャーム東1
2019-07-307012川崎重工業東1
2019-07-319934因幡電機産業東1
2019-07-312281プリマハム東1
2019-07-315929三和ホールディングス東1
2019-07-314902コニカミノルタ東1
2019-07-311973NECネッツエスアイ東1

良かったと思ったらポチッと押してもらえるとうれしいです。

 

おすすめ書籍

私がスクレイピングで参考にした書籍はこちらになります。
もっと勉強したいという方は参考にしていただければと思います。

コメント

  1. NYSE より:

    記事読ませていただきました。

    本記事内にあるコードを実行したところ、

    latest.remove(i)
    の部分ですべての証券コードが取り除かれているため、新しくラインクインした銘柄
    の処理の実行が行われず、絞り込みが行えませんでした。(日時等の変更は行っていません。)

    実際に使用されている全体コードを提示いただくことはできませんでしょうか。

    • topixtopix より:

      NYSEさん、

      読んでいただきありがとうございます。
      少し確認しますのでお時間ください。

      • NYSE より:

        返信いただきありがとうございます

        ご確認よろしくお願いいたします。

        • topixtopix より:

          やや汚いコードですが、以下のように書けば再現できると思います。
          一方で、本記事でやっている内容を大幅に変更しようと思っておりましたので、また記事が更新された際はそちらをご覧いただいた方が有用かと思います。
          よろしくおねがいします。


          # -*- coding: utf-8 -*-
          import requests
          from datetime import datetime, timedelta
          from bs4 import BeautifulSoup
          import numpy as np
          import pandas as pd
          import pickle

          def get_table(url):
          soup = BeautifulSoup(requests.get(url).text, "lxml")
          if len(soup.body.find_all('tr')) > 20:
          start_line = [i for i in range(14,20) if soup.body.find_all('tr')[i].text.splitlines()[1] == '順位']
          items = [v.text.splitlines() for i, v in enumerate(soup.body.find_all('tr')) if i > start_line[0]]

          #'値上がり'の列を'5日平均比出来高急増率'に変更
          cols = ['', '順位','コード', '名称', '市場','日付','終値','前日比(円)','前日比(%)','5日平均比出来高急増率','出来高','高値','安値']
          df = pd.DataFrame(np.array(items[0]).reshape(1,-1),columns=cols)
          print(items[0])
          for i in range(1,200):
          df_add = pd.DataFrame(np.array(items[i]).reshape(1,-1),columns=cols)
          df = pd.concat([df, df_add],axis = 0)

          #以下の'値上がり'の列の処理が不要
          '''
          df['値上がり率(円)'] = [v.split('+')[1] for v in df['値上がり']]
          df['値上がり率(%)'] = [v.split('+')[-1] for v in df['値上がり']]
          df = df.drop('値上がり', axis = 1)
          '''

          return df,True
          else:
          print('休場')
          return np.nan, False

          def get_data(date, st_date, end_date):
          dfs = []
          dates = []
          while date != end_date:
          strp_date = datetime.strptime(date, '%Y/%m/%d') + timedelta(days = 1)
          date = datetime.strftime(strp_date, '%Y/%m/%d' )
          print(date)

          #urlの変更
          url = 'https://www.kabudragon.com/ranking/{}/dekizou200.html'.format(date)
          df, flg = get_table(url)
          if flg:
          dfs.append(df)
          dates.append(strp_date)

          # 対象期間でランキングに入った銘柄コードの辞書作成
          dic = {}
          for df in dfs:
          keys = df['コード']
          values = df['名称']
          buf = dict(zip(keys, values))
          dic.update(buf)

          return dic, df

          # ランキングの推移を可視化したい日付(開始と終わり)を設定
          date = '2019/05/19'
          st_date = '2019/05/19'
          end_date = '2019/05/23'

          dic, df = get_data(date, st_date, end_date)
          #一旦、銘柄コードの辞書をpickleで保存
          sd = st_date.replace('/','')
          ed = end_date.replace('/','')
          with open('dic_{}-{}.pkl'.format(sd,ed), mode='wb') as f:
          pickle.dump(dic, f)

          date = '2019/05/19'
          st_date = '2019/05/19'
          end_date = '2019/05/24'

          dic, df = get_data(date, st_date, end_date)
          #一旦、銘柄コードの辞書をpickleで保存
          sd = st_date.replace('/','')
          ed = end_date.replace('/','')
          with open('dic_{}-{}.pkl'.format(sd,ed), mode='wb') as f:
          pickle.dump(dic, f)

          #過去の銘柄コードの辞書をロード
          pre_ed = '20190523'
          with open('dic_{}-{}.pkl'.format(sd,pre_ed),'rb') as f:
          pre_dic = pickle.load(f)

          #過去の銘柄辞書と最新の銘柄辞書を比較して、追加されたものだけにする
          latest = list(dic.keys())
          previous = list(pre_dic.keys())
          new_code = latest
          for i in previous:
          latest.remove(i)

          #新しくランクインした銘柄
          for k in new_code:
          code = k
          name = dic[code]
          print(code, name)

          _df = df.set_index('コード') #一旦銘柄コードをindexにする
          _df = _df.loc[new_code,:] #new_codeの銘柄コードを抽出する
          _df['出来高'] = _df['出来高'].str.replace(',','') #出来高のカンマを消す(数値に変換する時にエラーになるため)
          _df['出来高'] = _df['出来高'].astype(int) #出来高の数字が文字列なので、数値に変換する
          _df[_df['出来高'] > 500000] #出来高が500,000以上の銘柄を抽出する。

          • NYSE より:

            コード提示いただきありがとうございます。

            for i in previous:
            latest.remove(i)

            #新しくランクインした銘柄
            for k in new_code:
            code = k
            name = dic[code]
            print(code, name)

            _df = df.set_index(‘コード’) #一旦銘柄コードをindexにする
            _df = _df.loc[new_code,:] #new_codeの銘柄コードを抽出する
            _df[‘出来高’] = _df[‘出来高’].str.replace(‘,’,”) #出来高のカンマを消す(数値に変換する時にエラーになるため)
            _df[‘出来高’] = _df[‘出来高’].astype(int) #出来高の数字が文字列なので、数値に変換する
            _df[_df[‘出来高’] > 500000] #出来高が500,000以上の銘柄を抽出する。

            この部分のネスト関係が理解出来ないため、うまく抽出出来ませんでした。

            for i in previous:
             latest.remove(i)

            この部分は、if文を入れないとエラーになるのですが、再現しませんでしょうか。
            エラー内容は、latest配列にremove対象のiが存在しない というエラーです。

            記事を新たに書かれるということなので、返信頂けなくても大丈夫です。

            記事およびソースコード参考にさせていただきます。

  2. NYSE より:

    何度もコメント申し訳ありません。

    _df = df.set_index(‘コード’) #一旦銘柄コードをindexにする
    _df = _df.loc[new_code,:] #new_codeの銘柄コードを抽出する
    _df[‘出来高’] = _df[‘出来高’].str.replace(‘,’,”) #出来高のカンマを消す(数値に変換する時にエラーになるため)
    _df[‘出来高’] = _df[‘出来高’].astype(int) #出来高の数字が文字列なので、数値に変換する
    _df[_df[‘出来高’] > 500000] #出来高が500,000以上の銘柄を抽出する。

    この部分の
    _df[_df[‘出来高’] > 500000] での絞り込みが上手くできていない見たいです。
    最終的な出力結果は_dfに格納されていると考えていますが、相違はないでしょうか。
    処理後の_dfに対して,”5000000″より大きいでフィルタリングかけたところ、
    記事内に提示されている結果と若干の相違はありますが、近似値が出力結果に出ました。

    以下は、出力結果です。

    4833 1563800
    3782 1707300
    3656 9114900
    4712 5298200
    2193 4121400
    3099 4216900
    3762 693900
    3989 1374500
    5955 782700
    3995 837600
    2432 5780700
    8165 1078200
    8848 54905800

    以上、ご確認お願いいたします。

    • topixtopix より:

      長い返信になりますが、失礼します。

      わかりずらい書き方をしてしまい恐縮なんですが、最終結果をデータフレームで取得するためには、最後の行の結果をちゃんと変数に代入する必要があります。
      例えばoutputというデータフレームに入れるのであれば、

      output = _df[_df[‘出来高’] > 500000]

      とする必要があります。
      _df[_df[‘出来高’] > 500000] の処理だけでは結果がどこにも代入されていません。(ただ、jupyter notebookで実行するとその結果が表示されるので、私はその結果を表示してしまっておりました。)

      ただ、NYSEさんの出力結果をみると上記が原因ではないように思います。
      そこでいくつか確認させていただきたいのですが、その前に2点コードに不適切な部分があったので、修正します。
      まず、以下のようにlatestのうしろに.copy()を付けます。
      その後、latest.remove(i)ではなく、new_code.remove(i)とします。
      こうしないとlatestとnew_codeが同じリストとして扱われてしまうため、latest.remove(i)をしたことで、new_codeも同じようにnew_code.remove(i)の処理がされてしまいます。
      #過去の銘柄辞書と最新の銘柄辞書を比較して、追加されたものだけにする
      latest = list(dic.keys())
      previous = list(pre_dic.keys())
      new_code = latest.copy()
      for i in previous:
      new_code.remove(i)

      幸い、2箇所間違えていたので、以下の銘柄コードを抽出するところではnew_codeは追加された銘柄のみになっているので、結果は同じになるはずです。

      上記を踏まえて以下の出力結果がどんな値になっているか教えていただけますか?
      参考までに私の値を記載しておきます。

      print(len(dic)) => 831
      print(len(pre_dic)) => 680
      print(len(latest)) => 831
      print(len(previous)) => 680
      print(len(new_code)) => 151
      print(df.shape) => (200,13)
      print(_df.shape) => (151,12)
      print(output.shape) => (8,12)

      • NYSE より:

        ご丁寧に回答いただきありがとうございます。

        #過去の銘柄辞書と最新の銘柄辞書を比較して、追加されたものだけにする
        latest = list(dic.keys())
        previous = list(pre_dic.keys())
        new_code = latest.copy()
        for i in previous:
          new_code.remove(i)

        記述を変更しても、for文の中でのremove処理で同様のエラーが出力されました。
        そのため、

        for i in previous:
        if i in new_code:
        new_code.remove(i)

        のような判定を追加してコードの出力を行いました。

        私の環境での出力値は以下となりました。
        print(len(dic)) => 200
        print(len(pre_dic)) => 200
        print(len(latest)) => 200
        print(len(previous)) => 200
        print(len(new_code)) => 174
        print(df.shape) => (200,13)
        print(_df.shape) => (174,12)

        此方の環境はVisualStudio2019上実行しています。
        何度もご丁寧に返答いただき、大変恐縮です。

        以上、よろしくお願いします。

        • topixtopix より:

          dic, pre_dicともに200というのが気になります。
          1ページ分しか取得できていないので、おそらくget_table関数かget_data関数(返信のところで記述したもの)のどちらかがうまく機能していないように思います。
          返信だと何故かコードがすべて前揃えになってしまって見にくく、正しくお伝えできないので、今週末に本文の方をきれいなコードに書き直すようにしますね。
          申し訳ないです。

          • NYSE より:

            topixさん

            こちらこそご丁寧に解説頂いているのになかなか結果が出せず申し訳ありません。
            週末にコードの書き直しをいただけるとのことなので、お待ちしております。

            何度もご丁寧に本当にありがとうございます。

          • topixtopix より:

            プログラム更新しました。
            前回とかなり変わっていて、出来高の絞り込みなどはないので、もし別途知りたいことがあれば言ってください。

  3. NYSE より:

    topixさん

    コードの更新ありがとうございます。

    出力結果と同じ内容のデータが取得できました。
    大変助かりました。

    また、何か質問事項があればここに書かせていただきます。

    本当に助かりました。 ありがとうございます。

  4. […] […]

  5. […] […]

タイトルとURLをコピーしました