Potrzebujesz pomocy w optymalizacji skryptu google apps oznaczającego wiadomości e-mail

Gmail ma problem z tym, że etykiety konwersacji nie są stosowane do nowych wiadomości, które pojawiają się w wątku konwersacji. szczegóły wydania tutaj

Znaleźliśmy skrypt Google Apps, który naprawia etykiety na poszczególnych wiadomościach w skrzynce odbiorczej Gmaila, aby rozwiązać ten problem. Skrypt wygląda następująco:

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);
    }
  }
}

Jednak ten skrypt działa na skrzynkach e-mail z ponad 20 000 wiadomości ze względu na limit czasu wykonania 5 min w Google Apps Scenariusz.

Czy ktoś może zasugerować sposób na optymalizację tego skryptu tak, aby nie był on timeout?

Author: Saqib Ali, 2013-03-05

3 answers

OK, pracuję nad tym od kilku dni, ponieważ byłem naprawdę sfrustrowany dziwnym sposobem, w jaki Gmail etykietuje / nie etykietuje wiadomości w rozmowach.

Jestem oszołomiony, że etykiety nie są automatycznie stosowane do nowych wiadomości w rozmowie. Nie ma to żadnego odzwierciedlenia w interfejsie użytkownika Gmaila. Nie ma sposobu, aby spojrzeć na wątek i stwierdzić, że etykiety dotyczą tylko niektórych wiadomości w wątku, a etykiet nie można dodawać do pojedynczej wiadomości w interfejsie użytkownika. As I was pracując nad poniższym skryptem zauważyłem, że nie można nawet programowo dodawać etykiet do pojedynczej wiadomości. Więc naprawdę nie ma powodu do obecnego zachowania.

Mam kilka uwag na temat scenariusza.
    Połączyłem kod Saqiba z kodem Serge ' a.
  1. skrypt składa się z dwóch części: początkowego uruchomienia, które ponownie etykietuje wszystkie wątki, które mają dołączoną Etykietę użytkownika, i uruchomienia konserwacji, które etykietuje Ostatnie wiadomości e-mail (obecnie wygląda wstecz 4 dni). Tylko jedna część jest wykonywana podczas jednego uruchomienia. Po zakończeniu początkowego biegu uruchomi się tylko część konserwacyjna. Możesz ustawić WYZWALACZ, aby działał raz dziennie, lub mniej lub bardziej często, w zależności od potrzeb.
  2. początkowe uruchamianie zatrzymuje się po 4 minutach, aby nie zostać przerwane przez 5 minutowy limit czasu skryptu. Ustawia wyzwalacz do ponownego uruchomienia po 4 minutach (oba te czasy można zmienić za pomocą stałych w skrypcie). WYZWALACZ zostanie usunięty przy następnym uciekaj.
    • nie ma kontroli czasu pracy w sekcji konserwacji. Jeśli masz dużo wiadomości e-mail w ciągu ostatnich 4 dni, sekcja konserwacja może trafić w limit czasu skryptu. Prawdopodobnie mógłbym zmienić skrypt, aby był bardziej wydajny tutaj, ale do tej pory to działało dla mnie, więc nie jestem naprawdę zmotywowany do poprawy na nim.
  3. jest instrukcja try / catch w początkowym uruchomieniu, aby spróbować złapać Gmaila "write quota error" i zakończyć z wdziękiem (tzn. zapisując bieżący postęp tak, aby może być odebrany ponownie później), ale nie wiem, czy to działa, ponieważ nie mogłem uzyskać błąd się zdarzyć.
  4. otrzymasz wiadomość e-mail po osiągnięciu limitu czasu i zakończeniu początkowego biegu.
  5. z jakiegoś powodu, dziennik nie zawsze jest w pełni wyczyszczony między uruchomieniami, nawet podczas korzystania z rejestratora.polecenie clear (). Tak więc dzienniki stanu, które wysyła do Użytkownika, mają więcej niż tylko najnowsze informacje o uruchomieniu. Nie wiem, dlaczego tak się dzieje.

Użyłem tego do przetworzenia 20,000 e-maile w około pół godziny (łącznie z czasem oczekiwania). Uruchomiłem go dwa razy, więc przetworzył 40,000 e-maili w jeden dzień. Domyślam się, że limit odczytu/zapisu Gmaila wynoszący 10 000 nie jest tym, co jest tutaj stosowane (może zastosowanie etykiety do 100 wątków na raz liczy się jako pojedyncze zdarzenie zapisu zamiast 100?). Przechodzi przez około 5000 wątków w ciągu 4 minut, zgodnie z e-mailem o statusie, który wysyła.

Przepraszam za długie kolejki. Winię monitory panoramiczne. Let me know what you myśl!
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;

}

Edit: 3/24/2017 Chyba powinienem włączyć powiadomienia czy coś, bo nigdy nie widziałem pytania z user29020. W przypadku, gdyby ktoś kiedykolwiek miał to samo pytanie, oto, co robię: uruchamiam go jako funkcję konserwacji, ustawiając dzienny WYZWALACZ, aby działał każdej nocy między 1 A 2 w nocy.

Dodatkowa uwaga: wydaje się, że w pewnym momencie w ciągu ostatniego roku połączenia z Gmailem znacznie spowolniły. Teraz zajmuje to około 0,2 sekundy na wątek, więc chciałbym spodziewaj się, że początkowy przebieg 20K e-maili zajmie co najmniej 20 przebiegów lub tak, zanim dotrze do końca. Oznacza to również, że jeśli zazwyczaj otrzymujesz więcej niż 100-200 wiadomości e-mail dziennie, sekcja konserwacji może również zacząć trwać zbyt długo i zacząć zawodzić. Teraz to dużo e-maili, ale założę się, że są ludzie, którzy otrzymują, że wiele, i wydaje się o wiele bardziej prawdopodobne, że trafisz, że niż 1000 lub tak codziennie e-maile, które byłyby potrzebne do niepowodzenia z powrotem, kiedy po raz pierwszy napisał scenariusz.

W każdym razie, jednym z łagodzenia byłoby zmniejszenie LOOKBACKDAYS do mniej niż 4, ale nie polecam umieścić go mniej niż 2.

 13
Author: GordonM,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-24 22:59:30

From the documentation:

Metoda getInboxThreads()

pobieranie wszystkich wątków skrzynki odbiorczej niezależnie od etykiet To wywołanie nie powiedzie się, gdy rozmiar wszystkich wątków jest zbyt duży, aby system mógł sobie z tym poradzić. Jeśli rozmiar wątku jest nieznany, a potencjalnie bardzo duży, użyj wywołania 'paged' i określ zakresy wątków do pobrania w każdym wywołaniu.*

Więc powinieneś obsłużyć określoną liczbę wątków, oznaczyć wiadomości i ustawić czas Uruchom każdą "stronę"co około 10 minut, aż wszystkie wiadomości zostaną oznaczone.


EDIT: dałem temu try , proszę rozważyć jako szkic na początek:

Skrypt przetworzy 100 wątków na raz i wyśle Ci e-mail z informacją o jego postępach i wyświetli dziennik.

Kiedy to się skończy, ostrzega cię również e-mailem. Używa skryptów do przechowywania swojego stanu. (nie zapomnij zaktualizować adresu poczty na końcu skrypt). Próbowałem z wyzwalaczem czasowym ustawionym na 5 minut i wydaje się, że na razie działa płynnie...

function inboxLabeller() {

  if(ScriptProperties.getKeys().length==0){ // this is to create keys on the first run
    ScriptProperties.setProperties({'threadStart':0, 'itemsprocessed':0, 'notF':true})
    }
    var items = Number(ScriptProperties.getProperty('itemsprocessed'));// total counter
    var tStart = Number(ScriptProperties.getProperty('threadStart'));// the value to start with
    var notFinished = ScriptProperties.getProperty('notF');// the "main switch" ;-)
    Logger.clear()

  while (notFinished){ // the main loop
    var threads = GmailApp.getInboxThreads(tStart,100);
    Logger.log('Number of threads='+Number(tStart+threads.length));
      if(threads.length==0){
      notFinished=false ;
      break
      }
      for(t=0;t<threads.length;++t){
       var mCount = threads[t].getMessageCount();
       var mSubject = threads[t].getFirstMessageSubject();
       var labels = threads[t].getLabels();
       var labelsNames = '';
         for(var l in labels){labelsNames+=labels[l].getName()}
       Logger.log('subject '+mSubject+' has '+mCount+' msgs with labels '+labelsNames)
         for(var l in labels){
             labels[l].addToThread(threads[t])
      }
      }
        tStart = tStart+100;
        items = items+100
        ScriptProperties.setProperties({'threadStart':tStart, 'itemsprocessed':items})
        break
      }
   if(notFinished){
      GmailApp.sendEmail('mymail', 'inboxLabeller progress report', 'Still working, '+items+' processed \n - see logger below \n \n'+Logger.getLog());
      }else{
      GmailApp.sendEmail('mymail', 'inboxLabeller End report', 'Job completed : '+items+' processed');
      ScriptProperties.setProperties({'threadStart':0, 'itemsprocessed':0, 'notF':true})
      }
}
 5
Author: Serge insas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-06 11:40:11

Spowoduje to znalezienie poszczególnych wiadomości, które nie mają etykiety i zastosowanie etykiety powiązanego wątku. Zajmuje to znacznie mniej czasu, ponieważ nie jest to ponowne etykietowanie każdej wiadomości.

function label_unlabeled_messages() {
  var unlabeled = GmailApp.search("has:nouserlabels -label:inbox -label:sent -label:chats -label:draft -label:spam -label:trash");

  for (var i = 0; i < unlabeled.length; i++) {
    Logger.log("thread: " + i + " " + unlabeled[i].getFirstMessageSubject());
    labels = unlabeled[i].getLabels();
    for (var j = 0; j < labels.length; j++) {
      Logger.log("labels: " + i + " " + labels[j].getName());
      labels[j].addToThread(unlabeled[i]);
    }
  }
}
 1
Author: falcontx,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-18 06:14:48