European Central Bank (ECB) から Python でユーロの為替レートを取得してみた
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 へのレート変換を行なっています。
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 EUR" -e "<td rowspan=\"2\">" # <td rowspan="2">350.00 USD # <td rowspan="2">268.67 GBP # 1 EUR = 1.1726 USD # 1 EUR = 0.90013 GBP % echo $ecb_res | grep "<td rowspan=\"2\">" | grep "GBP" # <td rowspan="2">268.67 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
◆ 参考:
- curl コマンド | コマンドの使い方(Linux) | hydroculのメモ
- grepでAND検索とOR検索 - Qiita
- [sed] ファイル内の文字を置換する - A First Course in Linux
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 EUR.*\n', ecb_res.text) # ['\t1 EUR = 1.1726 USD\n', '\t1 EUR = 0.90013 GBP\n'] re.findall('<td rowspan="2">.*\n', ecb_res.text) # ['<td rowspan="2">350.00 USD\n', '<td rowspan="2">268.67 GBP\n']
あんまり素敵じゃありませんが、同様に値が取得できることはお分かりいただけたかと思います。いわゆるWebスクレイピングというもので、他にも Beautiful Soup を使うなど、いろいろやり方があるようです。
◆ 参考:
- Pythonでcurlコマンドと同等の処理を実行する方法 - 知的好奇心
- Pythonで文字列を抽出(位置・文字数、正規表現) | note.nkmk.me
- 【Python】猫でも出来るWebスクレイピングの基礎【BeautifulSoup4】 - Qiita 2
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
◆ 参考:
- python requests: how to check for "200 OK" - Stack Overflow
- ElementTreeやlxmlで名前空間を含むXMLの要素を取得する - orangain flavor
- xml.etree.ElementTree — The ElementTree XML API — Python 3.8.5 documentation
以上、ECBの対ユーロ為替レートをPythonで取得する方法をご紹介しました。どなたかのお役に立つと幸いです。
ヨーロッパに拠点がある法人向け:クラスメソッド・ヨーロッパではメインバンクにAPIなど機能の豊富な Qonto を使用しています。こちらの紹介ブログ(英語のみ)もぜひご参考ください。