European Central Bank (ECB) から Python でユーロの為替レートを取得してみた

ECBの対ユーロの為替レートをPythonで取得してみました。その2つの方法をご紹介します。
2020.08.07

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

Guten Tag、ベルリンの伊藤です。

ヨーロッパで請求書を作成する場合、European Central Bank (ECB) で対ユーロの為替レートを確認します。

普通のWebページでは、例えば EUR vs GBP の為替レートで、期間を指定したり平均や最大を指定したりして確認することができます。

また、ECBの管理する Currency Converter のページなら、指定した金額を指定した日付のレートから計算することもでき、請求書に使うレートを取得するにはこちらが便利です。

一方、スクリプトの処理で使うなど、APIなどで取得して計算できればなぁという場面があるかと思います。これを2つの方法でやってみました。

Currency Converter を使う

当初私が bash スクリプトを書いた時に使っていた方法がこちらです。

Currency Converter のWebページでレートを取得すると以下のように結果が出力され、URLを確認すると日付や通貨などのリクエストが含まれていることが確認できます。この例では、8/3(月)350 USD から GBP へのレート変換を行なっています。

ECB Currency Converter

URL:

https://sdw.ecb.europa.eu/curConverter.do?sourceAmount=350&sourceCurrency=USD&targetCurrency=GBP&inputDate=03-08-2020&submitConvert.x=51&submitConvert.y=2

ここで気になるのが、末尾の submitConvert.x=51&submitConvert.y=2 ですが、この値は検索結果ごとに異なる数字が割り当てられているようで、同じ条件で再検索しても数値が変わっていました。返ってくるレートの結果には影響がないようで、例えばどちらも固定で 0 と指定しても問題ありませんでした。(逆に属性自体を消してしまうと、リクエストが送信されません)

bashの場合

このURLを活用して curl コマンドを実行することで、レスポンスで上図に含まれる HTML コンテンツが返却されるので、それを元に該当部分を抽出することができます。

% ecb_res=$(curl -Ssf "https://sdw.ecb.europa.eu/curConverter.do" \
 --data "sourceAmount=350.0&sourceCurrency=USD&targetCurrency=GBP&inputDate=03-08-2020&submitConvert.x=64&submitConvert.y=7")

% echo $ecb_res | grep -e "1&nbsp;EUR" -e "<td rowspan=\"2\">"
#             <td rowspan="2">350.00&nbsp;USD
#             <td rowspan="2">268.67&nbsp;GBP
#                     1&nbsp;EUR&nbsp;=&nbsp;1.1726&nbsp;USD
#                         1&nbsp;EUR&nbsp;=&nbsp;0.90013&nbsp;GBP

% echo $ecb_res | grep "<td rowspan=\"2\">" | grep "GBP"
#             <td rowspan="2">268.67&nbsp;GBP

以下、bash スクリプトの一例です。ここでは、grep で該当行(1 EUR あたりの変換元・変換先の通貨のレート)を取得し、sed&nbsp; 1 をスペースに置き換えています。その後、取得したそれぞれのレートから小数10桁のGBP/USDレートを計算しています。

src_cur="USD"  # 変換元の通貨
tar_cur="GBP"  # 変換先の通貨
ecb_date="03-08-2020"  # レートの日付(DD-MM-YYYY)

eur_rate=$(curl \
-Ssf "https://sdw.ecb.europa.eu/curConverter.do" \
--data "sourceAmount=1.0&sourceCurrency=${src_cur}&targetCurrency=${tar_cur}&inputDate=${ecb_date}&submitConvert.x=64&submitConvert.y=7" \
 | grep "1&nbsp;EUR*" | sed -e "s/&nbsp;/ /g")


src_rate=$(echo ${eur_rate} | grep ${src_cur} | awk '{print $4}')
tar_rate=$(echo ${eur_rate} | grep ${tar_cur} | awk '{print $4}')
rate_calc=$(echo "scale=10; ${tar_rate}/${src_rate}" | bc)

echo $rate_calc  # 出力: .7676360225

◆ 参考:

Python の場合

要は先ほどと同じようなことをPythonでやれば良いわけです。curl と同じことを実現するには requests モジュールをインストールして、インポートしてやるのが良さそうです。

import re, requests
ecb_res = requests.get("https://sdw.ecb.europa.eu/curConverter.do?sourceAmount=350.0&sourceCurrency=USD&targetCurrency=GBP&inputDate=03-08-2020&submitConvert.x=64&submitConvert.y=7")

re.findall('\t1&nbsp;EUR.*\n', ecb_res.text)
# ['\t1&nbsp;EUR&nbsp;=&nbsp;1.1726&nbsp;USD\n', '\t1&nbsp;EUR&nbsp;=&nbsp;0.90013&nbsp;GBP\n']

re.findall('<td rowspan="2">.*\n', ecb_res.text)
# ['<td rowspan="2">350.00&nbsp;USD\n', '<td rowspan="2">268.67&nbsp;GBP\n']

あんまり素敵じゃありませんが、同様に値が取得できることはお分かりいただけたかと思います。いわゆるWebスクレイピングというもので、他にも Beautiful Soup を使うなど、いろいろやり方があるようです。

◆ 参考:

ECB SDMX RESTful web service を使う

手元で使ってた bash スクリプトを Python へ移行させようと思った時に、API的なのないのかな?と思って調べてみたら、こちらのサービスを見つけました。ドメインからしてもちゃんと公式のようです。

ECB SDMX RESTful web service - https://sdw-wsrest.ecb.europa.eu/help/

詳細は上記の「Data」ページで確認できますが、1 EUR あたりの USD レートを取得するには以下のように指定します。

https://sdw-wsrest.ecb.europa.eu/service/data/EXR/D.USD.EUR.SP00.A?startPeriod=2020-08-03&endPeriod=2020-08-03

日付部分は開始日と終了日がありますが、Currency Converterのように特定の日付のレートを取得するには、同じ日を指定します。(年月日の並び順が逆になります)

リクエストの D.USD.EUR.SP00.A の部分は次のような構成になっています。

  • D : 頻度(日次の場合、D)
  • USD : 変換先の通貨
  • EUR : 変換元の通貨
  • SP00 : 変換レートのタイプ(外貨変換の場合、SP00)
  • A : シリーズ(?)(平均の場合、A)

各リクエストでは対ユーロのレートしか取得できず、変換先/元のいずれかにEURを指定する必要があります。そのため、GBP/USDを計算するには、まずはUSD/EURとGBP/EURをそれぞれ取得する必要があります。

上記URLをcurlした場合のレスポンスは以下の通り(一部省略)、XML形式で出力されます。"generic:ObsValue"の値が USD/EUR レートで、先ほどのCurrency Converterで取得した際の値とも一致していることが確認できます。

<?xml version="1.0" encoding="UTF-8"?>
<message:GenericData xmlns:common="http://www.sdmx.org/resources/sdmxml/schemas/v2_1/common" xmlns:generic="http://www.sdmx.org/resources/sdmxml/schemas/v2_1/data/generic" xmlns:message="http://www.sdmx.org/resources/sdmxml/schemas/v2_1/message" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sdmx.org/resources/sdmxml/schemas/v2_1/message https://sdw-wsrest.ecb.europa.eu:443/vocabulary/sdmx/2_1/SDMXMessage.xsd http://www.sdmx.org/resources/sdmxml/schemas/v2_1/common https://sdw-wsrest.ecb.europa.eu:443/vocabulary/sdmx/2_1/SDMXCommon.xsd http://www.sdmx.org/resources/sdmxml/schemas/v2_1/data/generic https://sdw-wsrest.ecb.europa.eu:443/vocabulary/sdmx/2_1/SDMXDataGeneric.xsd">
    <message:Header>
        ...
    </message:Header>
    <message:DataSet action="Replace" structureRef="ECB_EXR1" validFromDate="2020-08-07T11:59:30.109+02:00">
        <generic:Series>
            <generic:SeriesKey>
                <generic:Value id="FREQ" value="D"/>
                <generic:Value id="CURRENCY" value="USD"/>
                <generic:Value id="CURRENCY_DENOM" value="EUR"/>
                <generic:Value id="EXR_TYPE" value="SP00"/>
                <generic:Value id="EXR_SUFFIX" value="A"/>
            </generic:SeriesKey>
            <generic:Attributes>
                <generic:Value id="COLLECTION" value="A"/>
                <generic:Value id="TITLE_COMPL" value="ECB reference exchange rate, US dollar/Euro, 2:15 pm (C.E.T.)"/>
                <generic:Value id="TITLE" value="US dollar/Euro"/>
                ...
            </generic:Attributes>
            <generic:Obs>
                <generic:ObsDimension value="2020-08-03"/>
                <generic:ObsValue value="1.1726"/>
                <generic:Attributes>
                    <generic:Value id="OBS_STATUS" value="A"/>
                    <generic:Value id="OBS_CONF" value="F"/>
                </generic:Attributes>
            </generic:Obs>
        </generic:Series>
    </message:DataSet>
</message:GenericData>

注意しなければならないのは、ECB では休日の場合レートが更新されないため、その場合は結果が空で返ってきてしまいます。Currency Converter の場合は自動的に前営業日のレートを取得してくれる親切設計がありますが、こちらを使う場合はその処理をスクリプト内で対策しなければいけません。

Python で取得してみた

Python スクリプトで取得する場合の例です。

requests.get(url) でURLを実行し、レスポンスに値がある場合(休日でない場合)はXMLの "generic:ObsValue" の値を抽出してレートを返し、値がない場合(休日の場合)はURLの日付を1日前に更新して再実行しています。

こちらでも、取得したそれぞれのレートから小数10桁のGBP/USDレートを計算しており、Currency Converter を元にしたbashスクリプトと同じ結果になりました。

# -*- coding:utf-8 -*-
import re, requests, datetime
from xml.etree import ElementTree as ET
from decimal import Decimal

src_cur="USD"  # 変換元の通貨
tar_cur="GBP"  # 変換先の通貨
ecb_date="2020-08-03"  # レートの日付(YYYY-MM-DD)

def req_url (url_pre, cur, date):
    rate = ""
    while not rate:
        url_date = 'startPeriod={}&endPeriod={}'.format(date, date)
        url = url_pre + url_date
        res = requests.get(url)
        if not res.ok:  # レスポンスがOKじゃなかったらエラー
            raise Exception("The currency rate ({}) could not be fetched because the response was incorrect.".format(cur))

        if res.text:  # レスポンスに値があれば、XMLから値を抽出して返す
            root = ET.fromstring(res.text)
            ns = { 'generic' : "http://www.sdmx.org/resources/sdmxml/schemas/v2_1/data/generic",
                   'message' : "http://www.sdmx.org/resources/sdmxml/schemas/v2_1/message"}
            obsvalue = root.find('./message:DataSet/generic:Series/generic:Obs/generic:ObsValue', ns)
            if obsvalue is None:
                raise Exception("The currency rate ({}) could not be fetched in the xml data.".format(cur))
            rate = obsvalue.get('value')
            return Decimal(rate)
        else:   # レスポンスに値がなければ、1日前を指定して再実行
            dt = datetime.datetime.strptime(date, '%Y-%m-%d')
            new_dt = dt + datetime.timedelta(days=-1)
            date = new_dt.strftime('%Y-%m-%d')


url_main = 'https://sdw-wsrest.ecb.europa.eu/service/data/EXR/'
url_src = url_main + 'D.{}.EUR.SP00.A?'.format(src_cur)
url_tar = url_main + 'D.{}.EUR.SP00.A?'.format(tar_cur)

# 1USDあたりのGBPレートを計算して出力
src_rate = req_url(url_src, src_cur, ecb_date)
tar_rate = req_url(url_tar, tar_cur, ecb_date)
rate_calc = round(tar_rate/src_rate, 10)

print(rate_calc) # 出力: 0.7676360225

◆ 参考:

以上、ECBの対ユーロ為替レートをPythonで取得する方法をご紹介しました。どなたかのお役に立つと幸いです。

ヨーロッパに拠点がある法人向け:クラスメソッド・ヨーロッパではメインバンクにAPIなど機能の豊富な Qonto を使用しています。こちらの紹介ブログ(英語のみ)もぜひご参考ください。

Retrieving Qonto’s transactions via the API and converting the CSV format to match our accounting tool


  1. ブログの表示の都合で、&を大文字で表記していますが、実際&から;まですべて小文字です 
  2. 本件とは関係ないけど、注意事項に記載のあった事件が非常に興味深いと思いました...