質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.32%
Google Apps Script

Google Apps ScriptはGoogleの製品と第三者のサービスでタスクを自動化するためのJavaScriptのクラウドのスクリプト言語です。

Q&A

解決済

1回答

2688閲覧

GASによる投信情報APIでの情報の取得

x.o_H_Q1n8sZc-7

総合スコア1

Google Apps Script

Google Apps ScriptはGoogleの製品と第三者のサービスでタスクを自動化するためのJavaScriptのクラウドのスクリプト言語です。

0グッド

1クリップ

投稿2023/08/17 06:18

実現したいこと

GASによる投信情報APIでの情報の取得

前提

GoogleスプレッドシートにAPIで取得した情報を書き込みたい。
投信情報API https://www.am.mufg.jp/tool/webapi/

Google Apps Script は必要に迫られて今回初めて使っている。
情報を取得できるのは6~7回に1回程度で理由がわからない。6~7回に1回は成功するというのがまた厄介だ。
ここ一週間ほど毎日ChatGPTに聞いてみたり検索を駆使して色々調べているが解決しない。
code 403 というエラーはどうもアクセス拒否のようで、GASによる海外サーバーから(か、どうかはわからないが)
のアクセスやユーザーエージェントを理由にはじかれているのかなと素人ながら想像している。
Pythonで似たようなコードを書くと今のところ100%取得できる。
GASに原因があるのかなと想像する理由の一つがこれだ。

補足情報が必要であれば、教えてもらえれば調べます。
何か思い当たる方がいらっしゃれば、教えていただけるとありがたいです。

発生している問題・エラーメッセージ

Exception: Request failed for http://developer.am.mufg.jp returned code 403. Truncated server response: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<HTML><HEAD><META HTTP-EQUIV="Content-Type" ... (use muteHttpExceptions option to examine full response) getFundJson @ memo.gs:30 MyFunction @ memo.gs:2

該当のソースコード

Google Apps Script

function MyFunction() { jsonData = getFundJson('253266'); let baseDate = jsonData.base_date; baseDate = baseDate.slice(0, 4) + '/' + baseDate.slice(4, 6) + '/' + baseDate.slice(-2); baseDate = Utilities.parseDate(baseDate, 'JST', 'yyyy/MM/dd') let nav = jsonData.nav; let cmpPrevDay = Number(jsonData.cmp_prev_day); let netassets = jsonData.netassets; let result = [ [ Utilities.formatDate(baseDate, 'Asia/Tokyo', 'yyyy/MM/dd'), nav.toLocaleString(), cmpPrevDay, (Math.floor(netassets / 1000000) / 100).toLocaleString() ] ]; let numRows = result.length; // 配列の行数を取得 let numCols = result[0].length; // 配列の1行目の要素数を列数として取得 let sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('シート1'); let targetRange = sheet.getRange(9, 2, numRows, numCols); // 起点セル(B9) targetRange.setValues(result); } function getFundJson(fundCode) { let baseUrl = "developer.am.mufg.jp/fund_information_latest/fund_cd/"; let response = UrlFetchApp.fetch(baseUrl + fundCode); let data = response.getContentText(); return JSON.parse(data).datasets[0]; }

毎回ちゃんと成功する Python のコード

import pandas as pd import requests import unicodedata def get_fund_information_by_fund_code(fund_code): base_url = "https://developer.am.mufg.jp/fund_information_latest/fund_cd/" url = base_url + fund_code response = requests.get(url) if response.status_code == 200: return response.json()['datasets'][0] else: print("APIリクエストエラー:", response.status_code) return None def convert_to_halfwidth(text): return ''.join([unicodedata.normalize('NFKC', char) for char in text]) fund_code = "253266" result_json = get_fund_information_by_fund_code(fund_code) print(f'\nファンド名: {convert_to_halfwidth(result_json["fund_name"])}') print(f'ファンドコード: {result_json["fund_cd"]}') print(f'基準日: {pd.to_datetime(result_json["base_date"]):%Y/%m/%d}') print(f'基準価格: {result_json["nav"]:,}') print(f'前日比: {result_json["cmp_prev_day"]}')

試したこと

Pythonでの反応を試したが、同じような問題は発生しなかった。
URLをブラウザのアドレス欄に張り付けてもちゃんと毎回 JSON が表示される。
developer.am.mufg.jp/fund_information_latest/fund_cd/253266

補足情報(FW/ツールのバージョンなど)

ウェブ上のスプレッドシートを使っているので、最新版だと思われる。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

YAmaGNZ

2023/08/17 08:12

”https://”を省略しているのは何か理由がありますか? エラーに”Exception: Request failed for http://developer.am.mufg.jp returned code 403.”とあるので 省略することによりhttp接続になっているとかが理由だったりしませんかね?
x.o_H_Q1n8sZc-7

2023/08/17 08:18

コメントありがとうございます。 申し訳ありません、説明が不足していました。 元々は”https://”を含めて指定していましたが挙動は同じで、リンク先にある「ファンド情報API仕様」のPDFに書かれているままにシンプルにした方がいいのかなという素人考えで最終的にこうしました。 ブラウザのアドレス欄に、 developer.am.mufg.jp/fund_information_latest/fund_cd/253266 を張り付けていただけるとわかりますが、URLに問題はないのかなと考えています。
x.o_H_Q1n8sZc-7

2023/08/17 08:25

今もう一度ご指摘いただいたところともう一か所セミコロンを忘れている部分も修正して試してみましたが結果は変わりませんでした。ご指摘ありがとうございました。
YAmaGNZ

2023/08/17 09:34

試しに実行してみましたが Request blocked. We can't connect to the server for this app or website at this time. There might be too much traffic or a configuration error. Try again later, or contact the app or website owner. といった応答が返ってきました。 もしかしたらGASからのリクエスト(貴方だけではなく)が多いために拒否されていたりするのではないでしょうか? その為に、何回かに1回はリクエストが通って正常なリターンが返ってきているとか エラー応答に関しては let response = UrlFetchApp.fetch(baseUrl + fundCode,{"muteHttpExceptions" : true}); とオプションをつければ実行エラーとはならずにエラーのレスポンスが見れるのでそれで確認してみてください。
x.o_H_Q1n8sZc-7

2023/08/17 09:59

お手数をおかけして申し訳ありません。わざわざ試していただいてありがとうございます。 やはりコードとは別の部分でブロックされているようですね。元々以下のページ https://emaxis.jp/fund/253266.html が、静的なページでIMPORTXML関数で情報が取得できていたのですが、最近動的なページに一新されて情報が取得できなくなりました。半日遅れぐらいでもよければ情報が取得できるところはたくさんあるのですが、ちょっと勉強がてらこだわってやってみようと思ったんですが難しそうですね。 しかしまだ一新されてから一週間程度しか経っていないのですが、最初から同じ挙動ですし、実害があってブロックしているというより、あらかじめ締め出しているという意図を感じますね。 もう少し様子を見てから難しそうであれば閉じようと思います。ありがとうございます。
x.o_H_Q1n8sZc-7

2023/08/17 13:07

前回のコメントは事実を混同して的外れなことを書いていました。APIはすでに数年運用されています。ウェブページのリニューアルがあったのは一週間ほど前です。APIアクセスをブロックしていることとサイトのリニューアルは別々の事実で事前にブロックしたというのは私の勘違いです。訂正させていただきます。
guest

回答1

0

自己解決

最後にアドバイスしていただいたことを参考に書き換えたコードを貼っておきます。

function MyFunction() { let fundCode = '253266'; let response = getFundJson(fundCode); if (response.getResponseCode() != 200) { Logger.log("HTTPエラーコード: " + response.getResponseCode()); Logger.log("エラーレスポンス内容: " + response.getContentText()); return } let jsonData = JSON.parse(response.getContentText()).datasets[0]; let baseDate = jsonData.base_date; baseDate = baseDate.slice(0, 4) + '/' + baseDate.slice(4, 6) + '/' + baseDate.slice(-2); let nav = jsonData.nav; let cmpPrevDay = Number(jsonData.cmp_prev_day); let netassets = jsonData.netassets; let result = [ [ baseDate, nav.toLocaleString(), cmpPrevDay, (Math.floor(netassets / 1000000) / 100).toLocaleString() ] ]; let numRows = result.length; // 配列の行数を取得 let numCols = result[0].length; // 配列の1行目の要素数を列数として取得 let sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('シート1'); let targetRange = sheet.getRange(9, 2, numRows, numCols); // 起点セル(B9) targetRange.setValues(result); } function getFundJson(fundCode) { let baseUrl = "https://developer.am.mufg.jp/fund_information_latest/fund_cd/"; let response = UrlFetchApp.fetch(baseUrl + fundCode, { "muteHttpExceptions": true }); return response; }

エラーの詳細

HTTPエラーコード: 403 エラーレスポンス内容: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <HTML><HEAD><META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1"> <TITLE>ERROR: The request could not be satisfied</TITLE> </HEAD><BODY> <H1>403 ERROR</H1> <H2>The request could not be satisfied.</H2> <HR noshade size="1px"> Request blocked. We can't connect to the server for this app or website at this time. There might be too much traffic or a configuration error. Try again later, or contact the app or website owner. <BR clear="all"> If you provide content to customers through CloudFront, you can find steps to troubleshoot and help prevent this error by reviewing the CloudFront documentation. <BR clear="all"> <HR noshade size="1px"> <PRE> Generated by cloudfront (CloudFront) Request ID: **** </PRE> <ADDRESS> </ADDRESS> </BODY></HTML>

結論としてYAmaGNZさんに教えていただいた通りで、ブロックされているという認識で間違いなさそうです。
できないということが分かったので別の方法を模索しようと思います。
APIを使う前は phantomJSCloud というのに登録して https://emaxis.jp/fund/253266.html の情報を取得しようとしていたのですが、これもダメでした。初めてGASを使うので勝手もわからりませんでしたが、エラー内容からブロックされていたようです。両方ともGASからのアクセスをブロックしているのだと思います。
ありがとうございました。

投稿2023/08/18 03:02

x.o_H_Q1n8sZc-7

総合スコア1

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.32%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問