UrlFetchApp 请求在菜单函数中失败,但在自定义函数中失败(连接到外部 REST API)

Posted

技术标签:

【中文标题】UrlFetchApp 请求在菜单函数中失败,但在自定义函数中失败(连接到外部 REST API)【英文标题】:UrlFetchApp request fails in Menu Functions but not in Custom Functions (connecting to external REST API) 【发布时间】:2020-11-11 09:56:12 【问题描述】:

我正在使用以下函数连接到外部 API (Binance),使用 Google Apps 脚本检索 JSON 数组(市场价格)。这个简单的查询 url 在浏览器中运行良好(不需要 API 密钥):

function getMyArray() 
  var url ="https://api.binance.com/api/v3/ticker/price"; // works perfectly in browser
  var params =  "method"  : "get",  "muteHttpExceptions":true ;  
  var response = UrlFetchApp.fetch(url, params);
  var array = JSON.parse(response.getContentText());
  
  return array;

但是,当我尝试在 Google Apps 脚本中运行该函数时,情况就不同了:

    脚本编辑器:在脚本编辑器中运行简单实用,但我收到403 error“请求被阻止” 菜单功能:从添加到电子表格 UI 的菜单项调用该功能 => 相同 403 error 自定义函数:编辑任何单元格并输入=getMyArray() => 请求有效,我可以使用 Logger 跟踪数组

为什么我的简单请求在从菜单或脚本编辑器调用时会被阻止,是否可以更改?谢谢

【问题讨论】:

根据您的情况,我提出了一种解决方法作为答案。如果这不是您期望的方向,我很抱歉。 【参考方案1】:

UrlFetchApp被自定义函数和脚本编辑器使用时,我认为区别在于是否使用IPv6,而IPv4的地址每次运行都会改变。在这种情况下,脚本编辑器和自定义菜单的结果是相同的。我认为这可能是您的问题的原因。但我不确定我的猜测是否正确。因此,在这个答案中,我想提出以下解决方法。

    使用脚本将公式=getMyArray() 放入单元格中。 通过这种方式,将值检索到单元格中。 使用脚本从单元格中检索值。 清除 put 公式。

通过这个流程,我认为你的目标可以实现。

示例脚本如下。

示例脚本:

在此脚本中,作为测试,=getMyArray() 被放入活动工作表上的单元格“A1”,并从单元格中检索值。当您使用它时,请在脚本编辑器和自定义菜单中运行函数main()。这样,可以将值检索到array

function getMyArray() 
  var url = "https://api.binance.com/api/v3/ticker/price";
  var params =  "method": "get", "muteHttpExceptions": true;
  var response = UrlFetchApp.fetch(url, params);
  return response.getContentText();


// Please run this function by the script editor and the custom menu.
function main() 
  var sheet = SpreadsheetApp.getActiveSheet();
  var range = sheet.getRange("A1");
  range.setFormula("=getMyArray()");
  SpreadsheetApp.flush();
  var value = range.getValue();
  range.clearContent();
  var array = JSON.parse(value);
  console.log(array)

参考资料:

Custom Functions in Google Sheets setFormula() flush()

补充:

https://httpbin.org/get的响应值如下。

用于测试的示例脚本:

function sample() 
  var url = "https://httpbin.org/get";
  var res = UrlFetchApp.fetch(url);
  console.log(res.getContentText())
  return res.getContentText();

结果:

模式 1. 使用脚本编辑器运行脚本。

  "args": , 
  "headers": 
    "Accept-Encoding": "gzip,deflate,br", 
    "Host": "httpbin.org", 
    "User-Agent": "Mozilla/5.0 (compatible; Google-Apps-Script; beanserver; +https://script.google.com; id: ###)", 
    "X-Amzn-Trace-Id": "Root=###"
  , 
  "origin": "### IPV6 ###, ### IPV4 ###", // or "### IPV4 ###, ### IPV4 ###"
  "url": "https://httpbin.org/get"

当您使用 IPV6 时,origin"### IPV6 ###, ### IPV4 ###"。但是当你使用 IPV4 时,origin"### IPV4 ###, ### IPV4 ###"。 在这种情况下,无法从https://api.binance.com/api/v3/ticker/price 检索到正确的值。 模式 2. 使用自定义函数运行脚本。

在这种情况下,=sample() 被放入单元格并检索值。


  "args": , 
  "headers": 
    "Accept-Encoding": "gzip,deflate,br", 
    "Host": "httpbin.org", 
    "User-Agent": "Mozilla/5.0 (compatible; Google-Apps-Script; beanserver; +https://script.google.com; id: ###)", 
    "X-Amzn-Trace-Id": "Root=###"
  , 
  "origin": "### IPV4 ###", 
  "url": "https://httpbin.org/get"

在这种情况下,可以从https://api.binance.com/api/v3/ticker/price 检索到正确的值。 模式 3. 使用 OnEdit 事件触发器运行脚本。

UrlFetchApp与自定义函数一起使用时,无需授权。但是当UrlFetchApp与OnEdit事件触发器一起使用时,通过授权需要安装触发器。我认为这个授权可能会出现这个问题。所以我比较了这个。

UrlFetchApp 与可安装的 OnEdit 事件触发器一起使用时,将检索以下结果。


  "args": , 
  "headers": 
    "Accept-Encoding": "gzip,deflate,br", 
    "Host": "httpbin.org", 
    "User-Agent": "Mozilla/5.0 (compatible; Google-Apps-Script; beanserver; +https://script.google.com; id: ###)", 
    "X-Amzn-Trace-Id": "Root=###"
  , 
  "origin": "### IPV4 ###", 
  "url": "https://httpbin.org/get"

此结果与上述模式 2 相同。 在这种情况下,可以从https://api.binance.com/api/v3/ticker/price 检索到正确的值。

结果:

包括User-Agent 在内的标头对于所有模式都是相同的。 从模式2和模式3来看,与谷歌端授权无关。 检索带有 IPV4 的 WHOIS 时,返回相同的结果。 当origin"### IPV4 ###, ### IPV4 ###" 时,第二个IPV4 是Google 的IP 地址。

从上面的结果来看,所有模式的不同之处在于origin的值是1还是2。

【讨论】:

这肯定是一种解决方法,但我仍然很想知道这个错误的确切原因。 @SAAD 感谢您的回复。我带来的不便表示歉意。当UrlFetchApp被自定义函数和脚本编辑器使用时,我认为区别在于是否使用IPv6,而IPv4的地址每次运行都会改变。在这种情况下,脚本编辑器和自定义菜单的结果是相同的。我认为这可能是您的问题的原因。但我不确定我的猜测是否正确。这是因为我的技术不好。对此我深表歉意。 不错。但是ip4地址是谷歌的服务器地址,ip6地址是你的。不同之处在于额外的地址,这不是“origin”标头的正确语法。区别不是ip6 vs ip4。例如,如果 ip6 地址受到限制并且只允许 ip4,您将得到“###IP4 ### IP4###”。第一个ip4是你的。第二个是谷歌的。 @TheMaster 感谢您的评论。哦!对不起。我注意到我误解了 IPv6。我能理解你的评论。在这种情况下,似乎当有 2 个 origin 值时,就会出现问题。因为当我删除我的 IPV6 时,我确认 origin 的值是 2 个 IPV4 地址。我忘记了我同时使用 IPV4 和 IPV6。 Tbh,我不是这些 IP 差异方面的专家,但鉴于您提供的详细解释和测试,我会接受答案

以上是关于UrlFetchApp 请求在菜单函数中失败,但在自定义函数中失败(连接到外部 REST API)的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Google Apps 脚本中向 UrlFetchApp 添加 API 密钥

UrlFetchApp Google Scripts VS Node Fetch

Google 应用程序脚本:urlfetchapp - 服务调用次数过多

GET 请求在邮递员中有效,但在 Axios 中失败

XHR 请求在 Scrapy 中失败,但在 python-requests 中有效

Axios GET请求在我的本地版本上运行,但在Heroku上失败-未捕获(承诺)错误:请求失败,状态码为500