Python
スクレイピング
BeautifulSoup
金融

pythonで楽天証券にログインしてスクレイピングする

楽天証券で積立NISAを始めたのですが、評価損益など時系列データが得られないので、スクレイピングすることに。

欲しい情報

  • 評価損益 (要ログイン)
  • 積立NISA対応投資信託の基準価格

※ログインは出来たのですが、他のページへの遷移が私には出来なかったためアドバイス頂けますと嬉しいです。

使うもの

スクレイピング

  • BeautifulSoup
  • requests
  • pandas

スプレッドシートの操作

  • import gspread
  • oauth2client

ログイン

こちらのサイトを参考にさせて頂きました。

from bs4 import BeautifulSoup
import requests
import lxml.html

session = requests.session()
r = session.get('https://www.rakuten-sec.co.jp/ITS/V_ACT_Login.html')

# 文字化け対策 
r.encoding
r.apparent_encoding
r.encoding = r.apparent_encoding


root = lxml.html.fromstring(r.text)
[a.get('name') for a in root.xpath('//form')]

uts = [(a.get('type'), a.get('name'), a.get('value')) for a in root.xpath('//form[@name="loginform"]//input')]
inputs

original = dict ([a[1:] for a in inputs])
original

custom = {
    'loginid': 'XXXXXXXX',
    'passwd': 'XXXXXXXX'}

posts = original.copy ()
posts.update (custom)

r = session.post ('https://member.rakuten-sec.co.jp/app/Login.do', data=posts, headers=headers)
r.encoding = r.apparent_encoding
soup = BeautifulSoup(r.content,'html.parser')

r.encoding
r.apparent_encoding
r.encoding = r.apparent_encoding
print (r.text)

ページ遷移 (この手法では出来ない)

ログイン後のページのリンクを確認するとSessionIDのところがXXXXXXとなっていたので、SessionIDが記載されているところから取り出し、目的のリンクと組み合わせることにしました。しかし・・・

soup = BeautifulSoup(r.content, "html.parser")
# SessionID取り出し
id_head = soup.find(id="nav-main-menu").find_all('li')[0].a.get("href").find('ID=')
id_tail = soup.find(id="nav-main-menu").find_all('li')[0].a.get("href").find('?eventType')
session_id = soup.find(id="nav-main-menu").find_all('li')[0].a.get("href")[id_head+3:id_tail]

# トータルリターンのページのURLを取得
for i,li in enumerate(soup.find(id="mdd-01").find_all('li')):
    if li.string == 'トータルリターン':
       break

url_head = soup.find(id="mdd-01").find_all('li')[i].a.get("onclick").find('Jump')
url_tail = soup.find(id="mdd-01").find_all('li')[i].a.get("onclick").find(');s.lidTrack')
url = soup.find(id="mdd-01").find_all('li')[i].a.get("onclick")[url_head+6:url_tail-1]

r = session.get (url)
print(BeautifulSoup(r.content,'html.parser'))

うまくいかなかったため、ログイン情報は諦めログインせずに取得できる情報に注力することにしました。

積立NISA対応投資信託の基準価格スクレイピング

検索ページで、積立NISA対応銘柄122種を抽出したURLを基にデータを収集します。

import datetime

fand_dict = {}
m_f_url = 'https://www.rakuten-sec.co.jp/web/fund/find/search/result.html?x=136&y=0&form-text-01=&fdcflag3_form11=on&chk_tsumitateNISA=on&fdcflag1_form12=&form-select-12=0&form-select-01=0&recsperpage=20&focus=table1&condition1=&condition2=&condition3=&condition4=&condition5=&condition6=&condition7=&condition8=&condition9=&condition10=&condition11=&condition12=&condition13=&condition14=&condition15=&condition16=&condition17=&condition18=&condition19=&condition20=&condition21=&condition22=&condition23=&condition24=&condition25=&condition26=null&condition27=null&condition28=null&condition29=null&condition31=&condition32=&condition33=&condition34=&condition35=&condition36=&condition37=&condition38=&condition39=&condition40=&condition41=&condition42=&condition43=&condition44=&condition45=&condition46=&condition47=&condition48=&condition49=&condition50=&condition51=&condition52=&condition53=&condition54=fdcflag3%5Eampsymbol%3B13&condition55=&condition56=&condition57=nisa_tsumitate%3D1&pg=1&selectedCondition=s15_1%2Cs16_1&sort=%EF%BE%8C%EF%BD%A7%EF%BE%9D%EF%BE%84%EF%BE%9E%E5%90%8D%E7%A7%B0%3Ddown&sortcolumn=col1&compareCondition=&t='
m_f_url = requests.get(m_f_url)
m_fs_soup = BeautifulSoup(m_fs_url.content,'html.parser')
# 検索件数を取得
brand_count = int(m_fs_soup.find(class_='result').find('strong').string)
# ページ数の算出
page_count = int(brand_count/20) if brand_count%20==0 else int(brand_count/20)+1

for page in range(1,page_count+1):
    m_f_url = requests.get('https://www.rakuten-sec.co.jp/web/fund/find/search/result.html?pg='+str(page)+'&x=136&y=0&fdcflag3_form11=on&chk_tsumitateNISA=on&selectedCondition=s15_1,s16_1&form-select-12=0&form-select-01=0&condition54=fdcflag3^ampsymbol;13&condition57=nisa_tsumitate=1&recsperpage=20&focus=table1&sort=%EF%BE%8C%EF%BD%A7%EF%BE%9D%EF%BE%84%EF%BE%9E%E5%90%8D%E7%A7%B0%3Ddown&sortcolumn=col1')
    m_fs_soup = BeautifulSoup(m_f_url.content,'html.parser')
    fand_list = m_fs_soup.find(id='search-right-column').find(class_='v-align-M').find_all('tr')
    for i in fand_list:
        fand_name,fand_value =i.find('th').string,int(re.match('.*?円' ,i.find(class_='align-R').get_text()).group()[:-1].replace(',','')) 
#         print(fand_name,fand_value)
        fand_dict[fand_name] = [fand_value]

sorted(fand_dict.items(), key=lambda x: x[0])

# ローカルのcsvファイルに書き込む
df = pd.read_csv('FILENAME.csv',index_col='date')
today_df = pd.DataFrame(fand_dict,index=[datetime.date.today()])
df = pd.concat([df, today_df])
df.to_csv('FILENAME.csv',index_label='date')

ローカルのcsvに保存されても使いにくいのでgoogleのスプレッドシートに書き込ませることにしました。

# スプレッドシート書き込み
import gspread
from oauth2client.service_account import ServiceAccountCredentials
scope = ['https://spreadsheets.google.com/feeds']
credentials = ServiceAccountCredentials.from_json_keyfile_name('FILE PATH.json', scope)
gc = gspread.authorize(credentials)
worksheet = gc.open("SHETT NAME").sheet1

records = worksheet.get_all_values()
# 範囲の取得
sheet_range = 'B'+ str(len(records)+1) + ':' + 'DS'+ str(len(records)+1)
cell_list = worksheet.range(sheet_range)
# 書き込み
worksheet.update_cell(str(len(records)+1), 1, datetime.date.today())
for i,(cell,value) in enumerate(zip(cell_list,list(today_df.values.flatten()))):
    cell.value = value
worksheet.update_cells(cell_list)

スクリーンショット 2018-01-26 0.16.57.png

cronの設定

出来上がったスクリプトを決まった時間に実行させるためにcronの設定を行います。

awsのインスタンスでは、時刻合わせが必要でしたのでこちらの記事を参考にしました。

sudo cp /etc/crontab /home/ec2-user/cron.txt
sudo vi cron.text

cron.txt
SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
HOME=/

0 21 * * 1-5  cd ec2-user/myApps/investment_trust/ && python FILENAME.py

こちらの例では、平日の21時に実行するように設定しました。

crontab cron.txt
crontab -l

おまけ

せっかくなのでslackで変動比の大きい銘柄を通知させることにしました。

import json
today_data = df / df.shift() - 1
today_data = today_data.tail(1).T
today_data = today_data.sort_values(by=[datetime.date.today()],ascending=False)
top = today_data[:3][datetime.date.today()]
bottom = today_data[len(today_data)-3:len(today_data)][datetime.date.today()]

text = str(datetime.date.today())+'  基準価格変動\r'+'{:*^20}'.format('上位3投資信託(前日比)')+'\r'
for i,v in zip(top.index,top):
    text = text + i +': {:.2%}\r'.format(v * 1) 
text = text + '{:*^20}'.format('下位3投資信託(前日比)')+'\r'
for i,v in zip(bottom.index,bottom):
    text = text + i +': {:.2%}\r'.format(v * 1) 

requests.post('https://hooks.slack.com/services/XXXXXXXXXXXXXXXXXXX',\
              data = json.dumps({'text': text}))

ダミーデータの不適切な情報なのでモザイク処理。
スクリーンショット_2018-01-26_10_58_10.png

:weary:oO(pandasの操作とかでまごついているのでもっとスマートに書けるようになりたいものです・・)