【Python】スクレイピングで経済指標カレンダー情報を取得する

FX投資
この記事は約26分で読めます。

以前の投稿で経済指標カレンダーサイトを比較し、どのサイトが見やすかったり、機能性が良かったり、重要な情報を提供してくれているかを調べました。
参考:経済指標カレンダーサイト比較

今回は、その中で一番評価が高かったYahooファイナンスのサイトからpythonのスクレイピングを使って、経済指標カレンダーの情報を取得してみたいと思います。

Pythonというプログラミング言語を使うのですが、Pythonに詳しくないという方は、まずはこちらご参照ください。
参考:個人投資家のためのPythonプログラミング

スポンサーリンク

robots.txtの確認

サイトによっては、スクレイピングが禁止されているものがあります。
スクレイピングをしてよいかどうかを確認するためには、各サイトのrobots.txtを見ることで確認できます。

robots.txtをご存じない方はこちらのサイトで詳細がわかるので確認してください。

Yahooのサイトは、robots.txtを設置していないので、ありがたくスクレイピングさせていただこうと思います。

必要なモジュールのインストール

pythonにはスクレイピングを簡単に行えるBeautifulSoupという便利なモジュールがあります。今回はこのBeautifulSoupというモジュールを利用したいと思います。
ただし、BeautifulSoup以外にも必要なモジュールがいくつかありますので、以下のモジュールがインストールされていない方は、pip installでインストールしてください。versionは最新のもので良いので特に指定しなくて良いです。
pipがよくわからない方はこちらをご参照ください。
Pythonの始め方④ pipとは?Pythonを便利に利用するためのpipの理解

  • bs4(BeautifulSoupのこと)
  • pandas
  • requests

PythonのBeautifulSoupを使って経済指標カレンダーをスクレイピング

モジュールのインポート

モジュールをインポートします。datetimeというモジュールをインポートしていますが、これはpythonの標準モジュールなので、pip installしなくてもpythonをインストールした時点ですでに存在しているはずです。

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

Yahooファイナンス経済カレンダーページのhtmlの取得

yahooファイナンスの経済指標カレンダーのページのトップはこちらのリンクになるのですが、後々、過去のデータも取得したいので日付情報が入った以下のurlを使用したいと思います。

date = '20190831'
url = 'https://info.finance.yahoo.co.jp/fx/marketcalendar/?d={}'.format(date)

このページは2019年8月31日から先の7日間の経済指標カレンダーを表示するページになります。
?d=の後ろの日付を変えると、その日付から先の7日間のカレンダーを表示します。
まずは、2019年8月31日から先の7日間の経済指標カレンダーを取得しましょう。

soup = BeautifulSoup(requests.get(url).content,'html.parser')

上記のコードで、ページ全体のhtml情報がsoupという変数に格納できます。

カレンダー情報の取得

カレンダー情報は、先程取得したsoupの中の様々なhtmlで区切られた中のcontents-body-bottomというidのdivタグの中に、テーブル構造で持っています。
※ここでは、htmlの詳細には触れません。

細かいことは置いておいて、以下のコードでテーブルを取得することができます。
※年データがテーブルには含まれていないので、5, 12, 15行目で年データを含める処理をしています。

div_bodybottom = soup.find_all('div', id = 'contents-body-bottom')
tr = div_bodybottom[0].find_all('tr')

data = []
year = int(date[:4])
for i in range(1,len(tr)):
    th = tr[i].find_all('th')
    td = tr[i].find_all('td')
    if len(th) > 0:
        date_, weekday = th[0].text.split(' ')
        if date_ == '1/1':
            year += 1
    if len(td) > 0:
        tempdata = []
        tempdata.append(year)
        tempdata.append(date_)
        tempdata.extend([d.text for d in td])
        data.append(tempdata)
header = ['年','日付', '時刻', '経済指標', 'blank', '前回(修正)', '予想', '結果']
df = pd.DataFrame(data, columns = header)
df = df.drop('blank', axis = 1)

実行すると以下のようなテーブルが得られると思います。

重要度の取得

blankの列があるかと思いますが、この列は元々重要度が入っている列です。ただ、text情報ではないので、少しコードに手を加える必要があります。

重要度の星は、先程のコード中のtdというリストの中の2番目のタグにおいて、spanタグの中にclass分けされて存在します。
したがって、以下のような関数を作成して重要度を取得します。
この関数の使い方は後ほど説明します。

def get_priority(td_):
    if len(td_.find_all('span', class_ = 'icoRating1')) > 0:
        priority = 1
    elif len(td_.find_all('span', class_ = 'icoRating2')) > 0:
        priority = 2
    elif len(td_.find_all('span', class_ = 'icoRating3')) > 0:
        priority = 3
    else:
        priority = 0
    return priority

国情報の取得

先程のデータにはどの国の経済指標なのかという情報も欠落しています。これもtextデータではないので、コードに手を加える必要があります。

国の情報は、先程のコード中のtdというリストの中の1番目のタグにおいて、spanタグの中にclass分けされて存在します。
したがって、以下のような関数を作成して国情報を取得します。
この関数の使い方も後ほど説明します。

def get_country(td_):
    if len(td_.find_all('span', class_ = 'icon18 icoUsa18')) > 0:
        country = 'アメリカ'
    elif len(td_.find_all('span', class_ = 'icon18 icoJpn18')) > 0:
        country = '日本'
    elif len(td_.find_all('span', class_ = 'icon18 icoCnh18')) > 0:
        country = '中国'
    elif len(td_.find_all('span', class_ = 'icon18 icoSui18')) > 0:
        country = 'スイス'
    elif len(td_.find_all('span', class_ = 'icon18 icoFra18')) > 0:
        country = 'フランス'
    elif len(td_.find_all('span', class_ = 'icon18 icoEu18')) > 0:
        country = 'EU'
    elif len(td_.find_all('span', class_ = 'icon18 icoGbr18')) > 0:
        country = 'イギリス'
    elif len(td_.find_all('span', class_ = 'icon18 icoGer18')) > 0:
        country = 'ドイツ'
    elif len(td_.find_all('span', class_ = 'icon18 icoRsa18')) > 0:
        country = '南アフリカ'
    elif len(td_.find_all('span', class_ = 'icon18 icoAus18')) > 0:
        country = 'オーストラリア'
    elif len(td_.find_all('span', class_ = 'icon18 icoCan18')) > 0:
        country = 'カナダ'
    elif len(td_.find_all('span', class_ = 'icon18 icoNzl18')) > 0:
        country = 'ニュージーランド'
    elif len(td_.find_all('span', class_ = 'icon18 icoHkg18')) > 0:
        country = '香港'
    elif len(td_.find_all('span', class_ = 'icon18 icoSin18')) > 0:
        country = 'シンガポール'
    else:
        country = 0    
    return country

重要度と国情報を追加したカレンダー情報

重要度と国情報を取得する関数を、先程のカレンダー情報を取得するコードに組み込みます。
以下のコードの13, 14行目に追加しております。
headerにも重要度と国というのを追加しました。あと、blank列は不要なので削除しています。

div_bodybottom = soup.find_all('div', id = 'contents-body-bottom')
tr = div_bodybottom[0].find_all('tr')
data = []
for i in range(1,len(tr)):
    th = tr[i].find_all('th')
    td = tr[i].find_all('td')
    if len(th) > 0:
        date, weekday = th[0].text.split(' ')
    if len(td) > 0:
        tempdata = []
        tempdata.append(date)
        tempdata.extend([d.text for d in td])
        tempdata.append(get_priority(td[2])) #重要度を取得する関数
        tempdata.append(get_country(td[1])) #国情報を取得する関数
        data.append(tempdata)
header = ['日付', '時刻', '経済指標', 'blank', '前回(修正)', '予想', '結果', '重要度','国']
df = pd.DataFrame(data, columns = header)
df = df.drop('blank', axis = 1)

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

過去の経済指標カレンダー情報を取得する

これまでのコードで、1週間分の経済指標カレンダーを取得することができました。
最後に先程のコードを関数化し、過去の経済指標カレンダーも取得できるようにしたいと思います。

以下のコードになります。

def get_weeklycalendar(date):
    url = 'https://info.finance.yahoo.co.jp/fx/marketcalendar/?d={}'.format(date)
    soup = BeautifulSoup(requests.get(url).content,'html.parser')
    div_bodybottom = soup.find_all('div', id = 'contents-body-bottom')
    tr = div_bodybottom[0].find_all('tr')

    data = []
    year = int(date[:4])
    for i in range(1,len(tr)):
        th = tr[i].find_all('th')
        td = tr[i].find_all('td')
        if len(th) > 0:
            date, weekday = th[0].text.split(' ')
            if date_ == '1/1':
                year += 1
        if len(td) > 0:
            tempdata = []
            tempdata.append(year)
            tempdata.append(date_)
            tempdata.extend([d.text for d in td])
            tempdata.append(get_priority(td[2]))
            tempdata.append(get_country(td[1]))
            data.append(tempdata)
    header = ['年','日付', '時刻', '経済指標', 'blank', '前回(修正)', '予想', '結果', '重要度','国']
    df = pd.DataFrame(data, columns = header)
    df = df.drop('blank', axis = 1)
    return df

date = '20190831'
dfs = []
for i in range(5):
    dfs.append(get_weeklycalendar(date))
    date_ = datetime.strptime(date, '%Y%m%d')
    date_ = date_ - timedelta(days = 7)
    date = datetime.strftime(date_, '%Y%m%d')

最初にも述べたようにyahooファイナンスの経済指標カレンダーのurlは?d=の後ろの日付を変えれば、その日付から先の7日間の経済指標カレンダーを表示します。
したがって、上記のコードは2019年8月31日を起点に7日ずつずらしていき、5週間分のカレンダー情報を取得しております。

カレンダー情報を取得するコードはget_weeklycalendarという関数にしています。
得られた各週のカレンダーをdfsというリストに格納しています。

データフレームの結合

上記のプログラムで得られたdfsというリストには、各週のカレンダーが入っておりますデータフレームとして入っておりますが、このままでは使いにくいので1つのデータフレームにしてしまいたいと思います。

df = pd.concat(dfs).reset_index(drop = True)

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

1つのデータテーブルにはなっているのですが、日付の順序があべこべになっています。
日時情報を使って並べ替えたいと思います。

データテーブルの並べ替え

少し複雑になってしまいますが、以下のコード日付を並べ替えます。
やっていることは日付列、時刻列からデータを取ってくるのですが、文字列を整数に置き換え、datetime関数に入れて、日付データとして扱えるようにしています。
その際、データの中に27時といった表記があるので、24時以降は日付を1日追加して、時刻を0〜23時に変えています。

dt = []
year = date[:4]
for i in range(df.shape[0]):
    month, day = df.iloc[i,df.columns.get_loc('日付')].split('/')
    time = df.iloc[i,df.columns.get_loc('時刻')]
    if time == '未定':
        hour, minute = 0,0
    else:
        hour, minute = df.iloc[i,df.columns.get_loc('時刻')].split(':') 
    year, month, day, hour, minute = int(year), int(month), int(day), int(hour), int(minute)

    if hour > 23:
        tempdate = datetime(year, month, day)
        tempdate = tempdate + timedelta(days = 1)
        hour = hour - 24
        dt.append(datetime(tempdate.year, tempdate.month, tempdate.day, hour, minute))
    else:
        dt.append(datetime(year, month, day, hour, minute))

これをデータフレームのdatetime列に追加して、並べ替えます。

df['datetime'] = dt
df = df.sort_values('datetime').reset_index(drop = True)

実行すると以下の結果が得られる思います。無事完了です。

まとめコード

最後にまとめのコードを示しておきます。日付の並べ替えの箇所も関数化しておりますので、ご注意ください。
参考になった場合は、ポチッとしておいていただけると嬉しいです。

 にほんブログ村 IT技術ブログへ
# -*- coding: utf-8 -*-
import requests
from bs4 import BeautifulSoup
from datetime import datetime, timedelta
import pandas as pd

def get_priority(td_):
    if len(td_.find_all('span', class_ = 'icoRating1')) > 0:
        priority = 1
    elif len(td_.find_all('span', class_ = 'icoRating2')) > 0:
        priority = 2
    elif len(td_.find_all('span', class_ = 'icoRating3')) > 0:
        priority = 3
    else:
        priority = 0
    return priority

def get_country(td_):
    if len(td_.find_all('span', class_ = 'icon18 icoUsa18')) > 0:
        country = 'アメリカ'
    elif len(td_.find_all('span', class_ = 'icon18 icoJpn18')) > 0:
        country = '日本'
    elif len(td_.find_all('span', class_ = 'icon18 icoCnh18')) > 0:
        country = '中国'
    elif len(td_.find_all('span', class_ = 'icon18 icoSui18')) > 0:
        country = 'スイス'
    elif len(td_.find_all('span', class_ = 'icon18 icoFra18')) > 0:
        country = 'フランス'
    elif len(td_.find_all('span', class_ = 'icon18 icoEu18')) > 0:
        country = 'EU'
    elif len(td_.find_all('span', class_ = 'icon18 icoGbr18')) > 0:
        country = 'イギリス'
    elif len(td_.find_all('span', class_ = 'icon18 icoGer18')) > 0:
        country = 'ドイツ'
    elif len(td_.find_all('span', class_ = 'icon18 icoRsa18')) > 0:
        country = '南アフリカ'
    elif len(td_.find_all('span', class_ = 'icon18 icoAus18')) > 0:
        country = 'オーストラリア'
    elif len(td_.find_all('span', class_ = 'icon18 icoCan18')) > 0:
        country = 'カナダ'
    elif len(td_.find_all('span', class_ = 'icon18 icoNzl18')) > 0:
        country = 'ニュージーランド'
    elif len(td_.find_all('span', class_ = 'icon18 icoHkg18')) > 0:
        country = '香港'
    elif len(td_.find_all('span', class_ = 'icon18 icoSin18')) > 0:
        country = 'シンガポール'
    else:
        country = 0    
    return country

def get_weeklycalendar(date):
    url = 'https://info.finance.yahoo.co.jp/fx/marketcalendar/?d={}'.format(date)
    soup = BeautifulSoup(requests.get(url).content,'html.parser')
    div_bodybottom = soup.find_all('div', id = 'contents-body-bottom')
    tr = div_bodybottom[0].find_all('tr')
    data = []
    year = int(date[:4])
    for i in range(1,len(tr)):
        th = tr[i].find_all('th')
        td = tr[i].find_all('td')
        if len(th) > 0:
            date_, weekday = th[0].text.split(' ')
            if date_ == '1/1':
                year += 1
        if len(td) > 0:
            tempdata = []
            tempdata.append(year)
            tempdata.append(date_)
            tempdata.extend([d.text for d in td])
            tempdata.append(get_priority(td[2]))
            tempdata.append(get_country(td[1]))
            data.append(tempdata)
    header = ['年','日付', '時刻', '経済指標', 'blank', '前回(修正)', '予想', '結果', '重要度','国']
    df = pd.DataFrame(data, columns = header)
    df = df.drop('blank', axis = 1)
    return df

def get_datetime(df_):
    dt = []
    for i in range(df_.shape[0]):
        year = df_.iloc[i,df_.columns.get_loc('年')]
        month, day = df_.iloc[i,df_.columns.get_loc('日付')].split('/')
        time = df_.iloc[i,df_.columns.get_loc('時刻')]
        if time == '未定':
            hour, minute = 0,0
        else:
            hour, minute = df_.iloc[i,df_.columns.get_loc('時刻')].split(':') 
        year, month, day, hour, minute = int(year), int(month), int(day), int(hour), int(minute)

        if hour > 23:
            tempdate = datetime(year, month, day)
            tempdate = tempdate + timedelta(days = 1)
            hour = hour - 24
            dt.append(datetime(tempdate.year, tempdate.month, tempdate.day, hour, minute))
        else:
            dt.append(datetime(year, month, day, hour, minute))
    return dt

date = '20190831'
dfs = []
for i in range(5):
    print(date)
    dfs.append(get_weeklycalendar(date))
    date_ = datetime.strptime(date, '%Y%m%d')
    date_ = date_ - timedelta(days = 7)
    date = datetime.strftime(date_, '%Y%m%d')
    
df = pd.concat(dfs).reset_index(drop = True)
df['datetime'] = get_datetime(df)
df = df.sort_values('datetime').reset_index(drop = True)

おすすめ書籍

私がスクレイピングで参考にした書籍はこちらになります。
もっと勉強したいという方は参考にしていただければと思います。
ただし、書籍はLinuxを前提としているので、その点だけご注意ください。

コメント

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