需要帮助优化标记电子邮件的Google Apps脚本 [英] Need help optimizing a google apps script that labels emails

查看:156
本文介绍了需要帮助优化标记电子邮件的Google Apps脚本的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Gmail有一个问题,即会话标签不会应用于到达会话主题的新邮件。 在这里发布详情

Gmail has a issue where conversation labels are not applied to new messages that arrive in the conversation thread. issue details here

我们找到一个Google Apps脚本,可以修复Gmail收件箱中单个邮件的标签,以解决此问题。脚本如下:

We found a Google Apps Script that fixes the labels on individual messages in the Gmail Inbox to address this issue. The script is as follows:

function relabeller() {
  var labels = GmailApp.getUserLabels();


  for (var i = 0; i < labels.length; i++) {
    Logger.log("label: " + i + " " + labels[i].getName());

    var threads = labels[i].getThreads(0,100);
    for (var j = 1; threads.length > 0; j++) {
      Logger.log( (j - 1) * 100 + threads.length);
      labels[i].addToThreads(threads);
      threads = labels[i].getThreads(j*100, 100);
    }
  }
}

但是这个脚本超时由于Google Apps脚本执行时间限制为5分钟,因此电子邮件包含超过20,000条消息。

However this script times out on email boxes with more than 20,000 messages due to the 5 mins execution time limit on Google Apps Script.

任何人都可以建议一种方法来优化此脚本, t超时?

Can anyone please suggest a way to optimize this script so that it doesn't timeout?

推荐答案

好的,我一直在努力这几天,因为我真的很沮丧的奇怪的方式Gmail标签/不会在会话中标记邮件。

OK, I've been working on this for a few days because I was really frustrated with the strange way that Gmail labels/doesn't label messages in conversations.

我实际上夸张的是,标签不会自动应用于会话中的新消息。 Gmail界面中完全没有反映出来。没有办法查看线程,并确定标签仅适用于线程中的某些消息,您不能在UI中的单个消息中添加标签。当我在下面的脚本中,我注意到你甚至不能通过编程方式向单个消息添加标签。所以真的没有理由目前的行为。

I'm flabbergasted actually that labels aren't automatically applied to new messages in a conversation. This is not reflected at all in the Gmail UI. There's no way to look at a thread and determine that the labels only apply to some messages in the thread, and you cannot add labels to a single message in the UI. As I was working through my script below, I noticed that you can't even programmatically add labels to a single message. So there really is no reason for the current behavior.

随着我的咆哮,我有一些关于脚本的笔记。

With my rant out of the way, I have a few notes about the script.


  1. 我将Saqib的代码与Serge的代码组合在一起。

  2. 该脚本有两个部分:一个初始运行,重新标记附加了用户标签的所有线程,以及维护运行,标记最近的电子邮件(目前看起来4天)。在一次运行中只执行一个部分。初始运行完成后,只有维护部分运行。您可以设置一个触发器,它每天运行一次,或多或少经常根据您的需要。

  3. 初始运行在4分钟后停止,以避免在5分钟的脚本时间限制内终止。它设置一个触发器在4分钟后再次运行(这两个时间可以使用脚本中的常量来更改)。触发器在下一次运行时被删除。


    • 在维护部分没有运行时检查。如果您在过去4天内收到大量电子邮件,则维护部分可能会触发脚本时间限制。我可能会改变脚本在这里更有效率,但到目前为止,它对我来说是有效的,所以我没有真正的动力来改进它。

我已经用了大约半小时内处理了2万封电子邮件(包括等待时间)。我实际上跑了两次,所以在一天内处理了4万封电子邮件。我猜这个Gmail读/写限制是不是在这里应用的(可能一次把100个线程应用到一个单独的写入事件而不是100个))。根据发送的状态邮件,它可以在4分钟的时间内通过约5,000个线程。

I have used this to process 20,000 emails in around half an hour (including wait times). I actually ran it twice, so it processed 40,000 emails in one day. I guess the Gmail read/write limit of 10,000 isn't what is being applied here (maybe applying a label to 100 threads at a time counts as a single write event instead of 100?). It gets through about 5,000 threads in a 4 minute run, according to the status email it sends.

对于长行而言,对不起。我责怪宽屏显示器。让我知道你的想法!

Sorry for the long lines. I blame the widescreen monitors. Let me know what you think!

function relabelGmail() {

  var startTime= (new Date()).getTime(); // Time at start of script
  var BATCH=100; // total number of threads to apply label to at once.
  var LOOKBACKDAYS=4; // Days to look back for maintenance section of script. Should be at least 2
  var MAX_RUN_TIME=4*60*1000; // Time in ms for max execution. 4 minutes is a good start.
  var WAIT_TIME=4*60*1000; // Time in ms to wait before starting the script again.
  Logger.clear();



//  ScriptProperties.deleteAllProperties(); return; // Uncomment this line and run once to start over completely

  if(ScriptProperties.getKeys().length==0){ // this is to create keys on the first run
    ScriptProperties.setProperties({'itemsProcessed':0, 'initFinished':false, 'lastrun':'20000101', 'itemsProcessedToday':0, 
                                    'currentLabel':'null-label-NOTREAL', 'currentLabelStart':0, 'autoTrig':0, 'autoTrigID':'0'});
  }

  var itemsP = Number(ScriptProperties.getProperty('itemsProcessed')); // total counter
  var initTemp = ScriptProperties.getProperty('initFinished'); // keeps track of when initial run is finished. 
  var initF = (initTemp.toLowerCase() == 'true'); // Make it boolean

  var lastR = ScriptProperties.getProperty('lastrun'); // String of date corresponding to itemsProcessedToday in format yyyymmdd
  var itemsPT = Number(ScriptProperties.getProperty('itemsProcessedToday')); // daily counter
  var currentL = ScriptProperties.getProperty('currentLabel'); // Label currently being processed
  var currentLS = Number(ScriptProperties.getProperty('currentLabelStart')); // Thread number to start on

  var autoT = Number(ScriptProperties.getProperty('autoTrig')); // Number to say whether the last run made an automatic trigger
  var autoTID = ScriptProperties.getProperty('autoTrigID'); // Unique ID of last written auto trigger

  // First thing: google terminates scripts after 5 minutes. 
  // If 4 minutes have passed, this script will terminate, write some data, 
  // and create a trigger to re-schedule itself to start again in a few minutes. 
  // If an auto trigger was created last run, it is deleted here.
  if (autoT) {
    var allTriggers = ScriptApp.getProjectTriggers();

    // Loop over all triggers. If trigger isn't found, then it must have ben deleted.
    for(var i=0; i < allTriggers.length; i++) {
      if (allTriggers[i].getUniqueId() == autoTID) {
        // Found the trigger and now delete it
        ScriptApp.deleteTrigger(allTriggers[i]);
        break;
      }
    }
    autoT = 0;
    autoTID = '0';
  }

  var today = dateToStr_();
  if (today == lastR) { // If new day, reset daily counter
    // Don't do anything
  } else {
    itemsPT = 0;
  }

  if (!initF) { // Don't do any of this if the initial run has been completed
    var labels = GmailApp.getUserLabels();

    // Find position of last label attempted
    var curLnum=0;
    for ( ; curLnum < labels.length; curLnum++) { 
      if (labels[curLnum].getName() == currentL) {break};
    }
    if (curLnum == labels.length) { // If label isn't found, start over at the beginning
      curLnum = 0;
      currentLS = 0;
      itemsP=0;
      currentL=labels[0].getName();
    }

    // Now start working through the labels until the quota is hit.
    // Use a try/catch to stop execution if your quota has been hit. 
    // Google can actually automatically email you, but we need to clean up a bit before terminating the script so it can properly pick up again tomorrow.
    try {
      for (var i = curLnum; i < labels.length; i++) {
        currentL = labels[i].getName(); // Next label
        Logger.log('label: ' + i + ' ' + currentL);

        var threads = labels[i].getThreads(currentLS,BATCH);

        for (var j = Math.floor(currentLS/BATCH); threads.length > 0; j++) {
          var currTime = (new Date()).getTime();
          if (currTime-startTime > MAX_RUN_TIME) {

            // Make the auto-trigger
            autoT = 1; // So the auto trigger gets deleted next time.

            var autoTrigger = ScriptApp.newTrigger('relabelGmail')
            .timeBased()
            .at(new Date(currTime+WAIT_TIME))
            .create();

            autoTID = autoTrigger.getUniqueId();

            // Now write all the values.
            ScriptProperties.setProperties({'itemsProcessed':itemsP, 'initFinished':initF, 'lastrun':today, 'itemsProcessedToday':itemsPT, 
                                            'currentLabel':currentL, 'currentLabelStart':currentLS, 'autoTrig':autoT, 'autoTrigID':autoTID});

            // Send an email
            var emailAddress = Session.getActiveUser().getEmail();
            GmailApp.sendEmail(emailAddress, 'Relabel job in progress', 'Your Gmail Relabeller has halted to avoid termination due to excess ' +
                               'run time. It will run again in ' + WAIT_TIME/1000/60 + ' minutes.\n\n' + itemsP + ' threads have been processed. ' + itemsPT + 
                               ' have been processed today.\n\nSee the log below for more information:\n\n' + Logger.getLog());
            return;
          } else {
            // keep on going
            var len = threads.length;
            Logger.log( j * BATCH + len);

            labels[i].addToThreads(threads);

            currentLS = currentLS + len;
            itemsP = itemsP + len;
            itemsPT = itemsPT + len;
            threads = labels[i].getThreads( (j+1) * BATCH, BATCH);
          }
        }

        currentLS = 0; // Reset LS counter
      }

      initF = true; // Initial run is done

    } catch (e) { // Clean up and send off a notice. 
      // Write current values back to ScriptProperties
      ScriptProperties.setProperties({'itemsProcessed':itemsP, 'initFinished':initF, 'lastrun':today, 'itemsProcessedToday':itemsPT, 
                                      'currentLabel':currentL, 'currentLabelStart':currentLS, 'autoTrig':autoT, 'autoTrigID':autoTID});

      var emailAddress = Session.getActiveUser().getEmail();
      var errorDate = new Date();
      GmailApp.sendEmail(emailAddress, 'Error "' + e.name + '" in Google Apps Script', 'Your Gmail Relabeller has failed in the following stack:\n\n' + 
                         e.stack + '\nThis may be due to reaching your daily Gmail read/write quota. \nThe error message is: ' + 
                         e.message + '\nThe error occurred at the following date and time: ' + errorDate + '\n\nThus far, ' + 
                         itemsP + ' threads have been processed. ' + itemsPT + ' have been processed today. \nSee the log below for more information:' + 
                         '\n\n' + Logger.getLog());
      return;
    }

    // Write current values back to ScriptProperties. Send completion email.
    ScriptProperties.setProperties({'itemsProcessed':itemsP, 'initFinished':initF, 'lastrun':today, 'itemsProcessedToday':itemsPT, 
                                    'currentLabel':currentL, 'currentLabelStart':currentLS, 'autoTrig':autoT, 'autoTrigNumber':autoTID});

    var emailAddress = Session.getActiveUser().getEmail();
    GmailApp.sendEmail(emailAddress, 'Relabel job completed', 'Your Gmail Relabeller has finished its initial run.\n' + 
                       'If you continue to run the script, it will skip the initial run and instead relabel ' + 
                       'all emails from the previous ' + LOOKBACKDAYS + ' days.\n\n' + itemsP + ' threads were processed. ' + itemsPT + 
                       ' were processed today. \nSee the log below for more information:' + '\n\n' + Logger.getLog());

    return; // Don't run the maintenance section after initial run finish

  } // End initial run section statement


  // Below is the 'maintenance' section that will be run when the initial run is finished. It finds all new threads
  // (as defined by LOOKBACKDAYS) and applies any existing labels to all messages in each thread. Note that this 
  // won't miss older threads that are labeled by the user because all messages in a thread get the label
  // when the label action is first performed. If another message is then sent or received in that thread, 
  // then this maintenance section will find it because it will be deemed a "new" thread at that point. 
  // You may need to search further back the first time you run this if it took more than 3 days to finish
  // the initial run. For general maintenance, though, 4 days should be plenty.

  // Note that I have not implemented a script-run-time check for this section. 

  var threads = GmailApp.search('newer_than:' + LOOKBACKDAYS + 'd', 0, BATCH); // 
  var len = threads.length;

  for (var i=0; len > 0; i++) {

    for (var t = 0; t < len; t++) {
      var labels = threads[t].getLabels();

      for (var l = 0; l < labels.length; l++) { // Add each label to the thread
        labels[l].addToThread(threads[t]);
      }
    }

    itemsP = itemsP + len;
    itemsPT = itemsPT + len;

    threads = GmailApp.search('newer_than:' + LOOKBACKDAYS + 'd', (i+1) * BATCH, BATCH); 
    len = threads.length;
  }
  // Write the property data
  ScriptProperties.setProperties({'itemsProcessed':itemsP, 'initFinished':initF, 'lastrun':today, 'itemsProcessedToday':itemsPT, 
                                  'currentLabel':currentL, 'currentLabelStart':currentLS, 'autoTrig':autoT, 'autoTrigID':autoTID});
}


// Takes a date object and turns it into a string of form yyyymmdd
function dateToStr_(dateObj) { //takes in a date object, but uses current date if not a date

  if (!(dateObj instanceof Date)) {
    dateObj = new Date();
  }

  var dd = dateObj.getDate();
  var mm = dateObj.getMonth()+1; //January is 0!
  var yyyy = dateObj.getFullYear();

  if(dd<10){dd='0'+dd}; 
  if(mm<10){mm='0'+mm};
  dateStr = ''+yyyy+mm+dd;

  return dateStr;

}

编辑:3/24/2017
I猜测我应该打开通知或某事,因为我从来没有看到user29020的问题。如果任何人有同样的问题,这里是我做的:我通过设置一个每天的触发器在1和2 AM之间运行它作为一个维护功能。

3/24/2017 I guess I should turn on notifications or something, because I never saw the question from user29020. In case anyone ever has the same question, here's what I do: I run it as a maintenance function by setting a daily trigger to run each night between 1 and 2 AM.

另外一个注意事项:看来,在过去一年左右的某个时候,对Gmail的标签呼叫显着放缓。它现在每个线程需要大约0.2秒,所以我希望初始运行20k个电子邮件至少需要20次运行,然后才能完成。这也意味着,如果您通常每天收到超过100-200封电子邮件,维护部分也可能开始耗时太久,并开始失败。现在这是很多的电子邮件,但我敢打赌有一些人会收到这么多的东西,似乎更有可能你会击中,而不是每天的1000个电子邮件,这将是我需要的故障,当我第一次写脚本。

An additional note: It seems that at some point in the last year or so, labeling calls to Gmail have slowed down significantly. It now takes around 0.2 seconds per thread, so I would expect an initial run of 20k emails to take at least 20 runs or so before it makes it all the way through. This also means that if you typically receive more than 100-200 emails a day, the maintenance section might also start to take too long and start to fail. Now that's a lot of emails, but I bet there are some people that receive that many, and it seems much more likely that you would hit that than the 1000 or so daily emails that would have been needed for failure back when I first wrote the script.

无论如何,一个缓解措施是将LOOKBACKDAYS减少到4以下,但不建议把它放在2以下。

Anyway, one mitigation would be to reduce the LOOKBACKDAYS to less than 4, but I wouldn't recommend putting it less than 2.

这篇关于需要帮助优化标记电子邮件的Google Apps脚本的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆