jsmでYahooファイナンスのデータを片っ端から取得してMongoDBに保存する
はじめに
jsm(Japanese Stock Market)という Yahooファイナンスをクロールして株関連データを取得できるライブラリーがある。 Brand、Finance、Priceデータが取得できるので、東証一部に絞ってデータを片っ端から取得するコードを書いた。
インストール
pipを更新してからjsmをインストールする。 スクレイピングはBeautifulSoup4で行っているので(再)インストール
$ sudo pip install --upgrade pip $ sudo pip install jsm $ sudo pip install beautifulsoup4 -U
MongoDBのラッパー
save, read, deleteを用意したMongoDBのラッパーファイル(db.py)を作成する。 DB名とCollection名を初期化で指定する。 saveはdictのlistをdataとして渡す。
# -*- coding: utf-8 -*- import sys import pymongo reload(sys) sys.setdefaultencoding('utf-8') class DB: def __init__(self, db_name, coll_name): self.db_name = db_name self.coll_name = coll_name def save(self, data): client = pymongo.MongoClient('localhost', 27017) db = client[self.db_name] coll = db[self.coll_name] coll.insert(data) def read(self): client = pymongo.MongoClient('localhost', 27017) db = client[self.db_name] coll = db[self.coll_name] data = [d for d in coll.find()] return data def delete(self): client = pymongo.MongoClient('localhost', 27017) db = client[self.db_name] coll = db[self.coll_name] coll.drop()
前準備
クローラーファイル(clawer.py)を作成し、必要なライブラリーと初期設定を行う。 上記のdb.pyもインポートしておく。 stockデータベースに、Brand、Finance、Priceコレクションを作成して取得したデータを保存する。
# -*- coding: utf-8 -*- import sys import jsm from progressbar import ProgressBar import pandas as pd import datetime from db import DB reload(sys) sys.setdefaultencoding('utf-8') DB_STOCK = 'stock' # Stock DB COLL_BRAND = 'brand' # Brand Collection COLL_FINANCE = 'finance' # Finace Collection COLL_PRICE = 'price' # Price Collection START_DATE = datetime.date(2014, 1, 1) # 株価の取得開始日
Brandデータを取得
get_brand()で全銘柄が取得できるが、取得状況の進捗を見たいのでカテゴリーごとに取得する。
def get_brands(self): db = DB(DB_STOCK, COLL_BRAND) #db.delete() categories = [ '0050', # 農林・水産業 '1050', # 鉱業 '2050', # 建設業 '3050', # 食料品 '3100', # 繊維製品 '3150', # パルプ・紙 '3200', # 化学 '3250', # 医薬品 '3300', # 石油・石炭製品 '3350', # ゴム製品 '3400', # ガラス・土石製品 '3450', # 鉄鋼 '3500', # 非鉄金属 '3550', # 金属製品 '3600', # 機械 '3650', # 電気機器 '3700', # 輸送機器 '3750', # 精密機器 '3800', # その他製品 '4050', # 電気・ガス業 '5050', # 陸運業 '5100', # 海運業 '5150', # 空運業 '5200', # 倉庫・運輸関連業 '5250', # 情報・通信 '6050', # 卸売業 '6100', # 小売業 '7050', # 銀行業 '7100', # 証券業 '7150', # 保険業 '7200', # その他金融業 '8050', # 不動産業 '9050' # サービス業 ] q = jsm.Quotes() pb = ProgressBar(maxval=len(categories)).start() for i in range(len(categories)): lis = [] try: brands = q.get_brand(categories[i]) except: pass for b in brands: dic = { 'category': categories[i], 'ccode': b.ccode, 'market': b.market, 'name': b.name, 'info': b.info } lis.append(dic) db.save(lis) pb.update(i+1)
Finaceデータを取得
Brandデータから東証一部の証券コード(ccode)のみを取得する。
def get_target_ccodes(self): data = DB(DB_STOCK, COLL_BRAND).read() df_brand = pd.DataFrame(data) df_brand = df_brand[df_brand['market']=='東証1部'] ccodes = df_brand['ccode'].tolist() return ccodes
取得した東証一部の証券コードを引数にしてFianceデータを取得する。
def get_finances(self, ccodes): db = DB(DB_STOCK, COLL_FINANCE) #db.delete() q = jsm.Quotes() pb = ProgressBar(maxval=len(ccodes)).start() lis = [] for i in range(len(ccodes)): try: f = q.get_finance(ccodes[i]) except: pass dic = { 'ccode': ccodes[i], 'market_cap': f.market_cap, 'shares_issued': f.shares_issued, 'dividend_yield': f.dividend_yield, 'dividend_one': f.dividend_one, 'per': f.per, 'pbr': f.pbr, 'eps': f.eps, 'bps': f.bps, 'price_min': f.price_min, 'round_lot': f.round_lot, 'years_high': f.years_high, 'years_low': f.years_low } lis.append(dic) pb.update(i+1) db.save(lis)
Priceデータを取得
Financeデータと同様、東証一部の証券コードを引数にしてPriceデータを取得する。
def get_prices(self, ccodes): start_date = START_DATE end_date = datetime.date.today() db = DB(DB_STOCK, COLL_PRICE) #db.delete() q = jsm.Quotes() pb = ProgressBar(maxval=len(ccodes)).start() for i in range(len(ccodes)): lis = [] try: prices = q.get_historical_prices(ccodes[i], jsm.DAILY, start_date, end_date) except: pass for p in prices: dic = { 'ccode': ccodes[i], 'date': p.date, 'open': p.open, 'high': p.high, 'low': p.low, 'close': p.close, 'volume': p.volume } lis.append(dic) db.save(lis) pb.update(i+1)
crawler.pyの全ソース
db.pyと同じ階層にファイルを置いて$ python crawler.py
を実行すればクロールを開始する。
# -*- coding: utf-8 -*- import sys import jsm from progressbar import ProgressBar import pandas as pd import datetime from db import DB reload(sys) sys.setdefaultencoding('utf-8') DB_STOCK = 'stock' COLL_BRAND = 'brand' COLL_PRICE = 'price' COLL_FINANCE = 'finance' START_DATE = datetime.date(2014, 1, 1) class Crawler: """ Refer to https://pypi.python.org/pypi/jsm/0.19 """ def __init__(self): pass def main(self): print 'getting brands...' self.get_brands() print 'getting finances...' ccodes = self.get_target_ccodes() self.get_finances(ccodes) print 'getting prices...' self.get_prices(ccodes) def get_brands(self): db = DB(DB_STOCK, COLL_BRAND) #db.delete() categories = [ '0050', # 農林・水産業 '1050', # 鉱業 '2050', # 建設業 '3050', # 食料品 '3100', # 繊維製品 '3150', # パルプ・紙 '3200', # 化学 '3250', # 医薬品 '3300', # 石油・石炭製品 '3350', # ゴム製品 '3400', # ガラス・土石製品 '3450', # 鉄鋼 '3500', # 非鉄金属 '3550', # 金属製品 '3600', # 機械 '3650', # 電気機器 '3700', # 輸送機器 '3750', # 精密機器 '3800', # その他製品 '4050', # 電気・ガス業 '5050', # 陸運業 '5100', # 海運業 '5150', # 空運業 '5200', # 倉庫・運輸関連業 '5250', # 情報・通信 '6050', # 卸売業 '6100', # 小売業 '7050', # 銀行業 '7100', # 証券業 '7150', # 保険業 '7200', # その他金融業 '8050', # 不動産業 '9050' # サービス業 ] q = jsm.Quotes() pb = ProgressBar(maxval=len(categories)).start() for i in range(len(categories)): lis = [] try: brands = q.get_brand(categories[i]) except: pass for b in brands: dic = { 'category': categories[i], 'ccode': b.ccode, 'market': b.market, 'name': b.name, 'info': b.info } lis.append(dic) db.save(lis) pb.update(i+1) def get_finances(self, ccodes): db = DB(DB_STOCK, COLL_FINANCE) #db.delete() q = jsm.Quotes() pb = ProgressBar(maxval=len(ccodes)).start() lis = [] for i in range(len(ccodes)): try: f = q.get_finance(ccodes[i]) except: pass dic = { 'ccode': ccodes[i], 'market_cap': f.market_cap, 'shares_issued': f.shares_issued, 'dividend_yield': f.dividend_yield, 'dividend_one': f.dividend_one, 'per': f.per, 'pbr': f.pbr, 'eps': f.eps, 'bps': f.bps, 'price_min': f.price_min, 'round_lot': f.round_lot, 'years_high': f.years_high, 'years_low': f.years_low } lis.append(dic) pb.update(i+1) db.save(lis) def get_prices(self, ccodes): start_date = START_DATE end_date = datetime.date.today() db = DB(DB_STOCK, COLL_PRICE) #db.delete() q = jsm.Quotes() pb = ProgressBar(maxval=len(ccodes)).start() for i in range(len(ccodes)): lis = [] try: prices = q.get_historical_prices(ccodes[i], jsm.DAILY, start_date, end_date) except: pass for p in prices: dic = { 'ccode': ccodes[i], 'date': p.date, 'open': p.open, 'high': p.high, 'low': p.low, 'close': p.close, 'volume': p.volume } lis.append(dic) db.save(lis) pb.update(i+1) def get_target_ccodes(self): data = DB(DB_STOCK, COLL_BRAND).read() df_brand = pd.DataFrame(data) df_brand = df_brand[df_brand['market']=='東証1部'] ccodes = df_brand['ccode'].tolist() return ccodes if __name__ == '__main__': Crawler().main()
所感
Priceデータを取得するのに15時間ぐらいかかったので、マルチスレッドにした方がいい。 上記のコードで継続的に最新のデータを収集し続けるには、もう少し改良が必要だけど、 とりあえず、サクッとデータを収集して分析したい人は使えると思う。 jsm自体は、 更新が2015年で止まっていて、GitHubからは削除されているが、ソース自体はPyPIに上がってるので、 これまで自作してた人は動かなくなっても改修できると思う。