Google Apps Script(GAS)上でGoogle Ads APIを利用することを目標にしています。
Google Ads APIは特定の言語(Python等)に対してのライブラリしか用意が無いため、
基本的にはGAS上では利用が困難です。
そのため、PythonのGoogle Adsライブラリとflaskを用いてGoogle Ads APIへリクエストし、
レスポンスをリターンするAPIサーバーをHeroku上に作成しました。
app.py
Python
1from flask import Flask 2from flask import request 3from flask import jsonify 4 5from src.modules.Google_Ads import get_keywords_data 6 7import logging 8import sys 9 10 11app = Flask(__name__) 12app.config['JSON_AS_ASCII'] = False 13 14 15# ログを標準出力に出力する 16app.logger.addHandler(logging.StreamHandler(sys.stdout)) 17# (レベル設定は適宜行ってください) 18app.logger.setLevel(logging.ERROR) 19 20 21@app.route('/') 22def index(): 23 return 'Hello World!' 24 25 26@app.route('/volume', methods=['POST']) 27def get_volume(): 28 if request.method == 'POST': 29 request_json = request.json 30 required = ( 31 'keys', 'page_url') 32 if not any(k in request_json for k in required): 33 app.logger.info(jsonify({'message': 'missing values'})) 34 return jsonify({'message': 'missing values'}), 400 35 36 customer_id = DEFAULT_CUSTOMER_ID 37 language_id = DEFAULT_LANGUAGE_ID 38 location_id = DEFAULT_LOCATION_ID 39 40 keyword = [] 41 if 'keys' in request_json: 42 keyword.append(request_json['keys']) 43 44 if 'page_url' in request_json: 45 page_url = request_json['page_url'] 46 else: 47 page_url = '' 48 49 is_volumed = get_keywords_data( 50 customer_id, location_id, language_id, keyword, page_url 51 ) 52 53 if not is_volumed: 54 app.logger.info(jsonify({'message': 'fail'})) 55 return jsonify({'message': 'fail'}), 400 56 app.logger.info(jsonify({'message': 'success', 'result': is_volumed})) 57 return jsonify({'message': 'success', 'result': is_volumed}), 201 58 59 60if __name__ == '__main__': 61 app.run(host='0.0.0.0', port=8080, debug=True)
ここまではおそらく問題がなく、curlでPOSTをしても問題なく値を取得できます。
curl
1curl -X POST -H "Content-Type: application/json" -d '{"keys": "東京都 レストラン"}' https://my-app-name.herokuapp.com/volume
ですが、Google Apps ScriptからUrlfetchにてリクエストを出した場合、稀にリクエストが通る時がある程度の成功率となってしまいます。(必ずエラーになるわけではなく、数十回に1回はなぜかリクエストが正常に通り、レスポンスが帰ってきます)
GoogleAppsScript
1function mainVolume() { 2 const base_url = 'https://my-app-name.herokuapp.com/'; 3 4 const keys = "東京都 レストラン"; 5 6 const volumes = getKeywordVolumes(base_url, address, keys); 7 return volumes; 8} 9 10 11function getKeywordVolumes(base_url, keys=false, page_url=false) { 12 const url = base_url + 'volume'; 13 14 const data = {}; 15 16 if(page_url) { 17 data["page_url"] = page_url; 18 } 19 if(keys) { 20 data["keys"] = keys; 21 } 22 23 const param = { 24 'method' : 'POST', 25 'headers' : { 26 'Content-Type' : 'application/json' 27 }, 28 'payload': JSON.stringify(data), 29 'muteHttpExceptions' : true 30 }; 31 console.log(url, param); 32 33 const json = UrlFetchApp.fetch(url, param).getContentText(); 34 console.log(json); 35 return JSON.parse(json); 36}
エラーも様々に出ていて、Herokuサーバー自体が原因としても考えられるのですが、その場合はcurlが問題なく通る理由がわからなくなります。
確認したエラー
HerokuLog
1 app[web.1]: Traceback (most recent call last): 2 app[web.1]: File "/app/.heroku/python/lib/python3.8/site-packages/google/api_core/grpc_helpers.py", line 57, in error_remapped_callable 3 app[web.1]: return callable_(*args, **kwargs) 4 app[web.1]: File "/app/.heroku/python/lib/python3.8/site-packages/grpc/_interceptor.py", line 216, in __call__ 5 app[web.1]: response, ignored_call = self._with_call(request, 6 app[web.1]: File "/app/.heroku/python/lib/python3.8/site-packages/grpc/_interceptor.py", line 257, in _with_call 7 app[web.1]: return call.result(), call 8 app[web.1]: File "/app/.heroku/python/lib/python3.8/site-packages/grpc/_channel.py", line 333, in result 9 app[web.1]: raise self 10 app[web.1]: File "/app/.heroku/python/lib/python3.8/site-packages/grpc/_interceptor.py", line 241, in continuation 11 app[web.1]: response, call = self._thunk(new_method).with_call( 12 app[web.1]: File "/app/.heroku/python/lib/python3.8/site-packages/grpc/_interceptor.py", line 266, in with_call 13 app[web.1]: return self._with_call(request, 14 app[web.1]: File "/app/.heroku/python/lib/python3.8/site-packages/grpc/_interceptor.py", line 257, in _with_call 15 app[web.1]: return call.result(), call 16 app[web.1]: File "/app/.heroku/python/lib/python3.8/site-packages/grpc/_channel.py", line 333, in result 17 app[web.1]: raise self 18 app[web.1]: File "/app/.heroku/python/lib/python3.8/site-packages/grpc/_interceptor.py", line 241, in continuation 19 app[web.1]: response, call = self._thunk(new_method).with_call( 20 app[web.1]: File "/app/.heroku/python/lib/python3.8/site-packages/grpc/_interceptor.py", line 266, in with_call 21 app[web.1]: return self._with_call(request, 22 app[web.1]: File "/app/.heroku/python/lib/python3.8/site-packages/grpc/_interceptor.py", line 254, in _with_call 23 app[web.1]: call = self._interceptor.intercept_unary_unary(continuation, 24 app[web.1]: File "/app/.heroku/python/lib/python3.8/site-packages/google/ads/google_ads/interceptors/exception_interceptor.py", line 169, in intercept_unary_unary 25 app[web.1]: self._handle_grpc_failure(response) 26 app[web.1]: File "/app/.heroku/python/lib/python3.8/site-packages/google/ads/google_ads/interceptors/exception_interceptor.py", line 141, in _handle_grpc_failure 27 app[web.1]: raise self._get_error_from_response(response) 28 app[web.1]: File "/app/.heroku/python/lib/python3.8/site-packages/grpc/_interceptor.py", line 241, in continuation 29 app[web.1]: response, call = self._thunk(new_method).with_call( 30 app[web.1]: File "/app/.heroku/python/lib/python3.8/site-packages/grpc/_channel.py", line 837, in with_call 31 app[web.1]: return _end_unary_response_blocking(state, call, True, None) 32 app[web.1]: File "/app/.heroku/python/lib/python3.8/site-packages/grpc/_channel.py", line 729, in _end_unary_response_blocking 33 app[web.1]: raise _InactiveRpcError(state) 34 app[web.1]: grpc._channel._InactiveRpcError: <_InactiveRpcError of RPC that terminated with: 35 app[web.1]: 36 app[web.1]: 37 app[web.1]: 38 app[web.1]: > 39 app[web.1]: 40 app[web.1]: The above exception was the direct cause of the following exception: 41 app[web.1]: 42 app[web.1]: Traceback (most recent call last): 43 app[web.1]: File "/app/.heroku/python/lib/python3.8/site-packages/flask/app.py", line 2447, in wsgi_app 44 app[web.1]: response = self.full_dispatch_request() 45 app[web.1]: File "/app/.heroku/python/lib/python3.8/site-packages/flask/app.py", line 1952, in full_dispatch_request 46 app[web.1]: rv = self.handle_user_exception(e) 47 app[web.1]: File "/app/.heroku/python/lib/python3.8/site-packages/flask/app.py", line 1821, in handle_user_exception 48 app[web.1]: reraise(exc_type, exc_value, tb) 49 app[web.1]: File "/app/.heroku/python/lib/python3.8/site-packages/flask/_compat.py", line 39, in reraise 50 app[web.1]: raise value 51 app[web.1]: File "/app/.heroku/python/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request 52 app[web.1]: rv = self.dispatch_request() 53 app[web.1]: File "/app/.heroku/python/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request 54 app[web.1]: return self.view_functions[rule.endpoint](**req.view_args) 55 app[web.1]: File "/app/app.py", line 78, in get_volume 56 app[web.1]: is_volumed = get_keywords_data( 57 app[web.1]: File "/app/src/modules/Google_Ads.py", line 198, in get_keywords_data 58 app[web.1]: return main(google_ads_client, customer_id, location_ids, language_id, keyword_texts, page_url) 59 app[web.1]: File "/app/src/modules/Google_Ads.py", line 103, in main 60 app[web.1]: for idea in keyword_ideas: 61 app[web.1]: File "/app/.heroku/python/lib/python3.8/site-packages/google/api_core/page_iterator.py", line 212, in _items_iter 62 app[web.1]: for page in self._page_iter(increment=False): 63 app[web.1]: File "/app/.heroku/python/lib/python3.8/site-packages/google/api_core/page_iterator.py", line 249, in _page_iter 64 app[web.1]: page = self._next_page() 65 app[web.1]: File "/app/.heroku/python/lib/python3.8/site-packages/google/api_core/page_iterator.py", line 534, in _next_page 66 app[web.1]: response = self._method(self._request) 67 app[web.1]: File "/app/.heroku/python/lib/python3.8/site-packages/google/api_core/gapic_v1/method.py", line 145, in __call__ 68 app[web.1]: return wrapped_func(*args, **kwargs) 69 app[web.1]: File "/app/.heroku/python/lib/python3.8/site-packages/google/api_core/retry.py", line 281, in retry_wrapped_func 70 app[web.1]: return retry_target( 71 app[web.1]: File "/app/.heroku/python/lib/python3.8/site-packages/google/api_core/retry.py", line 184, in retry_target 72 app[web.1]: return target() 73 app[web.1]: File "/app/.heroku/python/lib/python3.8/site-packages/google/api_core/timeout.py", line 214, in func_with_timeout 74 app[web.1]: return func(*args, **kwargs) 75 app[web.1]: File "/app/.heroku/python/lib/python3.8/site-packages/google/api_core/grpc_helpers.py", line 59, in error_remapped_callable 76 app[web.1]: six.raise_from(exceptions.from_grpc_error(exc), exc) 77 app[web.1]: File "<string>", line 3, in raise_from 78 app[web.1]: google.api_core.exceptions.ResourceExhausted: 429 Resource has been exhausted (e.g. check quota). 79 app[web.1]: 10.69.31.173 - - [23/Oct/2020:04:51:27 +0000] "POST /volume HTTP/1.1" 500 290 "-" "Mozilla/5.0 (compatible; Google-Apps-Script; beanserver; +https://script.google.com; id: UAEmdDd_PyV8UkOp5JGIO-aAM4YeiKkU-Ug)"
429エラーが確認されていますが、リソースがどの部分を指しているのかがわかりません。
Google Apps ScriptやGoogle Adsは一人で使用し、リクエスト回数を数十回程度です。
複雑なことになっていますが、どなたかお力添えいただけると助かります。
あなたの回答
tips
プレビュー