【问题标题】:Apps Script custom function working in script editor but not in Google Sheet custom functionApps 脚本自定义函数在脚本编辑器中工作,但在 Google 表格自定义函数中不起作用
【发布时间】:2021-11-26 01:35:08
【问题描述】:

我使用 URLFetchApp 在 Apps Script 中构建了一个简单的自定义函数来获取 TikTok 帐户的关注者数量。

function tiktok_fans() {
  var raw_data = new RegExp(/("followerCount":)([0-9]+)/g);
  var handle = '@charlidamelio';
  var web_content = UrlFetchApp.fetch('https://www.tiktok.com/'+ handle + '?lang=en').getContentText();
  var match_text = raw_data.exec(web_content);
  var result = (match_text[2]);
  Logger.log(result)
  return result
}

日志返回正确的追随者人数。

但是,当我将代码更改为;

function tiktok_fans(handle) {
  var raw_data = new RegExp(/("followerCount":)([0-9]+)/g);
  //var handle = '@charlidamelio';
  var web_content = UrlFetchApp.fetch('https://www.tiktok.com/'+ handle + '?lang=en').getContentText();
  var match_text = raw_data.exec(web_content);
  var result = (match_text[2]);
  Logger.log(result)
  return result
}

并在电子表格中使用它,例如 =tiktok_fans(A1),其中 A1 有 @charlidamelio 我在单元格中收到 #ERROR 响应

TypeError:无法读取 null 的属性“2”(第 6 行)。

为什么它在日志中有效,但在电子表格中无效?

--附加信息--

在测试下面的@Tanaike 答案后仍然得到同样的错误,“TypeError: Cannot read property '2' of null (line 6).”

已手动映射以查看错误,每次以下运行时,不同的日志都会返回“null”。我相信这与缓存中的 ContentText 大小/有关。我尝试在函数之间使用Utilities.sleep(),但没有成功,我仍然得到空值。

代码

  var raw_data = new RegExp(/("followerCount":)([0-9]+)/g);

  //tiktok urls
  var qld = UrlFetchApp.fetch('https://www.tiktok.com/@thisisqueensland?lang=en').getContentText();
  var nsw = UrlFetchApp.fetch('https://www.tiktok.com/@visitnsw?lang=en').getContentText();
  var syd = UrlFetchApp.fetch('https://www.tiktok.com/@sydney?lang=en').getContentText();
  var tas = UrlFetchApp.fetch('https://www.tiktok.com/@tasmania?lang=en').getContentText();
  var nt = UrlFetchApp.fetch('https://www.tiktok.com/@ntaustralia?lang=en').getContentText();
  var nz = UrlFetchApp.fetch('https://www.tiktok.com/@purenz?lang=en').getContentText();
  var aus = UrlFetchApp.fetch('https://www.tiktok.com/@australia?lang=en').getContentText();
  var vic = UrlFetchApp.fetch('https://www.tiktok.com/@visitmelbourne?lang=en').getContentText();

  //find folowers with regex
  var match_qld = raw_data.exec(qld);
  var match_nsw = raw_data.exec(nsw);
  var match_syd = raw_data.exec(syd);
  var match_tas = raw_data.exec(tas);
  var match_nt = raw_data.exec(nt);
  var match_nz = raw_data.exec(nz);
  var match_aus = raw_data.exec(aus);
  var match_vic = raw_data.exec(vic);

  Logger.log(match_qld);
  Logger.log(match_nsw);
  Logger.log(match_syd);
  Logger.log(match_tas);
  Logger.log(match_nt);
  Logger.log(match_nz);
  Logger.log(match_aus);
  Logger.log(match_vic);

【问题讨论】:

    标签: google-apps-script google-sheets http-headers custom-function tiktok


    【解决方案1】:

    问题:

    从你的情况,我记得UrlFetchApp用自定义函数的请求和UrlFetchApp用脚本编辑器的请求是不一样的。所以我认为你的问题的原因可能与这个线程有关。 https://*.com/a/63024816 在您的情况下,您的情况似乎与此线程相反。但是,这个问题被认为是由于网站的规范造成的。

    为了检查这种差异,我检查了检索到的 HTML 数据的文件大小。

    • 使用脚本编辑器执行的 UrlFetchApp 检索到的 HTML 数据的文件大小为 518k 字节。
    • 使用自定义函数执行的 UrlFetchApp 检索到的 HTML 数据的文件大小为 9k 字节。
      • 看来UrlFetchApp用自定义函数执行的请求和UrlFetchApp用Web Apps执行的请求是一样的。使用this检索9k字节的数据。

    从上面的结果可以发现,脚本编辑器和自定义函数检索到的HTML是不一样的。即,自定义函数检索到的 HTML 数据不包含 ("followerCount":)([0-9]+) 的正则表达式。这样,就会发生这样的错误。我认为这可能是您的问题的原因。

    解决方法:

    当我使用 Web 应用程序和触发器测试您的情况时,会出现同样的问题。至此,在现阶段,我认为自动执行脚本的方法可能无法使用。那么,作为一种解决方法,使用按钮和自定义菜单怎么样?当脚本由按钮和自定义菜单运行时,脚本工作。这个方法好像和脚本编辑器一样。

    示例脚本如下。

    示例脚本:

    在运行脚本之前,请设置range。例如,请将此功能分配给电子表格上的按钮。单击按钮时,脚本将运行。在这个示例中,它假设像@charlidamelio 这样的值被放在“A”列中。

    function sample() {
      var range = "A2:A10"; // Please set the range of "handle".
      var raw_data = new RegExp(/("followerCount":)([0-9]+)/g);
      var sheet = SpreadsheetApp.getActiveSheet();
      var r = sheet.getRange(range);
      var values = r.getValues();
      var res = values.map(([handle]) => {
        if (handle != "") {
          var web_content = UrlFetchApp.fetch('https://www.tiktok.com/'+ handle + '?lang=en').getContentText();
          var match_text = raw_data.exec(web_content);
          return [match_text[2]];
        }
        return [""];
      });
      r.offset(0, 1).setValues(res);
    }
    
    • 运行此脚本时,将从 URL 检索值并将其放入“B”列。

    注意:

    • 这是一个简单的脚本。所以请根据您的实际情况进行修改。

    参考:

    补充:

    关于以下附加问题,

    虽然这适用于 1 个 TikTok 句柄,但当尝试运行多个列表时,它每次都会失败,并出现错误 TypeError: Cannot read property '2' of null。在做了一些调查并手动映射出 8 个句柄之后,我可以看到每次运行时,它都会为一个或多个 web_content 变量返回“null”。有没有办法减慢脚本速度/一次运行每个 UrlFetchApp 以确保每个都返回内容?

    我已经尝试过了,但仍然出现错误。已尝试长达 10000 毫秒。我在原始问题中添加了更多细节,希望这对错误有意义。我总是在不同的日志中得到空值,因此我认为这是时间或缓存问题。

    在这种情况下,下面的示例脚本怎么样?

    示例脚本:

    在此示例脚本中,当无法从 URL 中检索到值时,将尝试再次检索该值作为重试。此示例脚本使用 2 次作为重试。所以当2次重试都取不到值时,返回空值。

    function sample() {
      var range = "A2:A10"; // Please set the range of "handle".
      var raw_data = new RegExp(/("followerCount":)([0-9]+)/g);
      var sheet = SpreadsheetApp.getActiveSheet();
      var r = sheet.getRange(range);
      var values = r.getValues();
      var res = values.map(([handle]) => {
        if (handle != "") {
          var web_content = UrlFetchApp.fetch('https://www.tiktok.com/'+ handle + '?lang=en').getContentText();
          var match_text = raw_data.exec(web_content);
          if (!match_text || match_text.length != 3) {
            var retry = 2; // Number of retry.
            for (var i = 0; i < retry; i++) {
              Utilities.sleep(3000);
              web_content = UrlFetchApp.fetch('https://www.tiktok.com/'+ handle + '?lang=en').getContentText();
              match_text = raw_data.exec(web_content);
              if (match_text || match_text.length == 3) break;
            }
          }
          return [match_text && match_text.length == 3 ? match_text[2] : ""];
        }
        return [""];
      });
      r.offset(0, 1).setValues(res);
    }
    
    • 请调整retryUtilities.sleep(3000)的值。

    【讨论】:

    • 解释得很清楚。干得好。
    • 感谢@Tanaike,虽然这适用于 1 个 TikTok 句柄,但当尝试运行多个列表时,它每次都会失败,并出现错误 TypeError: Cannot read property '2' of null。在做了一些调查并手动映射出 8 个句柄之后,我可以看到每次运行时,它都会为一个或多个 web_content 变量返回“null”。有没有办法减慢脚本的速度/一次运行每个 UrlFetchApp 以确保每个都返回内容?
    • @racker 感谢您的回复。关于你对Is there a way to slow the script down/run each UrlFetchApp one at a time to ensure each returns content?的附加问题,在这种情况下,例如,将var range = "A2:A10"修改为var range = "A2"怎么样?这样,脚本仅使用“A2”的值。如果我误解了您的新问题,我深表歉意。
    • @Tanaike 是的,这是可行的,但是对于超过 1 个单元格的范围,您将如何实现它?
    • @racker 感谢您的回复。当您想将上述脚本用于多个脚本时,您可以将其与var range = "A2:A10" 一起使用。到时候想加上等待时间,把Utilities.sleep(3000)放在var match_text = raw_data.exec(web_content);后面怎么样?至此,等待 3 秒。当您想更改 3 秒时,请修改 Utilities.sleep(3000)3000。单位是毫秒。
    【解决方案2】:

    这对我来说是一个自定义函数:

    function MYFUNK(n=2) {
      const url = 'my website url'
      const re = new RegExp(`<p id="un${n}.*\/p>`,'g')
      const r = UrlFetchApp.fetch(url).getContentText();
      const v = r.match(re);
      Logger.log(v);
      return v;
    }
    

    我使用了自己的网站,并且有几个段落的 ID 从 un1 到 un7,我将 A1 的值作为唯一参数。每次我更改它时它都会返回正确的字符串。

    【讨论】: