Как настроить запуск скрипта Google Apps Script по расписанию?

Что такое триггеры и зачем они нужны?

Триггеры в Google Apps Script — это механизм, позволяющий автоматически запускать определенные функции скрипта в ответ на заданные события или по расписанию. Они являются ключевым инструментом для автоматизации рутинных задач, таких как отправка отчетов, обработка данных, синхронизация информации между сервисами Google и внешними системами, без необходимости ручного запуска скрипта.

Использование триггеров позволяет создавать полностью автономные решения, работающие в фоновом режиме, что значительно повышает эффективность и снижает вероятность ошибок, связанных с человеческим фактором.

Типы триггеров в Google Apps Script (управляемые и устанавливаемые)

Существует два основных типа триггеров:

  1. Простые триггеры (Simple Triggers): Срабатывают на стандартные события в Документах, Таблицах, Формах или Презентациях (например, onOpen(), onEdit(), onInstall()). Они имеют ряд ограничений, не требуют специальной авторизации, но не могут выполнять операции, требующие разрешений пользователя (например, отправка email).
  2. Устанавливаемые триггеры (Installable Triggers): Могут быть созданы программно или через интерфейс редактора скриптов. Они предоставляют больше гибкости и возможностей по сравнению с простыми триггерами. Устанавливаемые триггеры делятся на:
    • Управляемые временем (Time-driven): Запускают скрипт по заданному расписанию (ежеминутно, ежечасно, ежедневно и т.д.). Именно этот тип мы подробно рассмотрим.
    • Управляемые событиями (Event-driven): Запускаются при определенных событиях в файлах Google Workspace (например, отправка формы, редактирование таблицы, изменение календаря).

Ограничения при использовании триггеров

При работе с триггерами важно учитывать существующие квоты и ограничения Google Apps Script:

  • Общее время выполнения триггеров: Существует дневная квота на суммарное время работы всех триггеров для одного пользователя (например, 90 минут/день для обычных аккаунтов Gmail, 6 часов/день для аккаунтов Google Workspace).
  • Время выполнения одного скрипта: Максимальное время выполнения одного запуска скрипта ограничено (обычно 6 минут для потребительских аккаунтов и 30 минут для Google Workspace).
  • Частота запуска: Минимальный интервал для триггеров по времени — 1 минута.
  • Количество триггеров: Ограничено количество триггеров на пользователя на скрипт (обычно 20).
  • Требования к авторизации: Устанавливаемые триггеры выполняются от имени пользователя, создавшего триггер, и требуют соответствующих разрешений.

Настройка запуска скрипта по расписанию с помощью управляемых триггеров

Управляемые триггеры по времени (часто называемые просто «триггерами по времени») настраиваются через интерфейс редактора скриптов и являются наиболее простым способом настроить регулярный запуск функции.

Доступные варианты расписания (минутные, часовые, дневные, недельные, месячные)

Интерфейс редактора Google Apps Script предлагает гибкие настройки расписания:

  • Минутные таймеры: Каждые 1, 5, 10, 15, 30 минут.
  • Часовые таймеры: Каждый час, каждые 2, 4, 6, 8, 12 часов.
  • Дневные таймеры: Ежедневно в определенный интервал времени (например, с 8:00 до 9:00).
  • Недельные таймеры: Еженедельно в определенный день и время.
  • Месячные таймеры: Ежемесячно в определенный день (или дни) и время.
  • На основе даты: Запуск в конкретную дату и время (используется редко для повторяющихся задач).

Как создать управляемый триггер через редактор Google Apps Script

  1. Откройте ваш проект Google Apps Script.
  2. На левой панели нажмите на иконку будильника («Триггеры»).
  3. Нажмите кнопку «+ Добавить триггер» в правом нижнем углу.
  4. В появившемся окне настройте параметры триггера:
    • Выберите функцию для запуска: Укажите имя функции, которую нужно выполнять по расписанию.
    • Выберите развертывание, которое должно выполняться: Обычно оставляют «Head».
    • Выберите источник события: Выберите «Триггер по времени».
    • Выберите тип таймера: Укажите желаемую периодичность (минутный, часовой, дневной и т.д.).
    • Выберите интервал: Уточните частоту запуска (например, «Каждые 15 минут» или «С 2:00 до 3:00»).
    • Настройки уведомлений об ошибках: Выберите, как часто вы хотите получать уведомления по электронной почте в случае сбоя выполнения скрипта (например, немедленно, ежедневно, еженедельно).
  5. Нажмите «Сохранить».
  6. Google запросит авторизацию для вашего скрипта, если она еще не была предоставлена или если добавлены новые требующие разрешений сервисы. Просмотрите запрашиваемые разрешения и подтвердите их.

Примеры кода для запуска скрипта по расписанию

Предположим, у нас есть функция, которая должна выполняться по расписанию для обработки данных в Google Sheets.

/**
 * @description Обрабатывает данные в указанном листе Google Sheets.
 * Например, очищает старые записи или агрегирует статистику.
 * Эта функция будет вызываться триггером по времени.
 */
function processSheetData(): void {
  const ss: GoogleAppsScript.Spreadsheet.Spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  const sheet: GoogleAppsScript.Spreadsheet.Sheet | null = ss.getSheetByName('RawData');

  if (!sheet) {
    Logger.log('Лист RawData не найден.');
    return;
  }

  const dataRange: GoogleAppsScript.Spreadsheet.Range = sheet.getDataRange();
  const values: any[][] = dataRange.getValues();

  // Пример: Удаление строк старше 30 дней (предполагается дата в первом столбце)
  const thirtyDaysAgo: number = new Date().getTime() - 30 * 24 * 60 * 60 * 1000;
  const rowsToDelete: number[] = [];

  values.forEach((row, index) => {
    // Пропускаем заголовок
    if (index === 0) return;

    const dateValue = row[0]; // Предполагаем, что дата в первом столбце (A)
    if (dateValue instanceof Date && dateValue.getTime() < thirtyDaysAgo) {
      // Запоминаем номер строки для удаления (индекс + 1)
      rowsToDelete.push(index + 1);
    }
  });

  // Удаляем строки в обратном порядке, чтобы избежать смещения индексов
  for (let i = rowsToDelete.length - 1; i >= 0; i--) {
    sheet.deleteRow(rowsToDelete[i]);
  }

  Logger.log(`Обработка завершена. Удалено строк: ${rowsToDelete.length}`);
}

Вы можете выбрать функцию processSheetData при создании триггера по времени через интерфейс редактора.

Просмотр и удаление управляемых триггеров

Все созданные через интерфейс триггеры отображаются на странице «Триггеры» вашего проекта Apps Script. Здесь вы можете:

  • Просмотреть список активных триггеров.
  • Увидеть, какая функция запускается, тип триггера и его расписание.
  • Узнать статус последнего запуска и время следующего.
  • Отредактировать настройки триггера (иконка карандаша).
  • Удалить триггер (иконка с тремя точками -> Удалить триггер).

Настройка запуска скрипта по расписанию с помощью устанавливаемых триггеров

Устанавливаемые триггеры создаются программно с использованием сервиса ScriptApp. Это дает больше контроля и позволяет управлять триггерами динамически.

Когда следует использовать устанавливаемые триггеры вместо управляемых?

Программное создание триггеров предпочтительнее в следующих случаях:

  • Динамическое управление: Когда триггеры нужно создавать, изменять или удалять в зависимости от действий пользователя или логики скрипта (например, пользователь включает/отключает автоматическую обработку).
  • Сложная логика расписания: Хотя стандартные опции покрывают большинство нужд, программное создание позволяет реализовать более специфичные сценарии.
  • Создание триггеров для других пользователей: В некоторых сценариях (например, аддоны) может потребоваться создать триггер, который будет выполняться от имени установившего его пользователя.
  • Автоматизация настройки: При развертывании решений может быть удобнее настроить триггеры автоматически при первом запуске скрипта.

Как создать устанавливаемый триггер с использованием Script Service

Для создания триггера по времени используется метод ScriptApp.newTrigger() с последующим вызовом timeBased() и методов настройки расписания (everyMinutes(), everyHours(), everyDays(), onWeekDay(), at(), etc.), и завершается методом create().

Примеры кода для создания и управления устанавливаемыми триггерами

/**
 * @description Создает ежедневный триггер для функции processSheetData, 
 *              если он еще не существует.
 */
function createDailyTrigger(): void {
  const functionName: string = 'processSheetData';

  // Проверяем, не существует ли уже триггер для этой функции
  const triggers: GoogleAppsScript.Script.Trigger[] = ScriptApp.getProjectTriggers();
  const triggerExists: boolean = triggers.some(trigger => 
    trigger.getHandlerFunction() === functionName &&
    trigger.getEventType() === ScriptApp.EventType.CLOCK
  );

  if (!triggerExists) {
    try {
      ScriptApp.newTrigger(functionName)
        .timeBased()
        .everyDays(1) // Запускать каждый день
        .atHour(3)    // Примерно в 3 часа ночи
        .inTimezone(Session.getScriptTimeZone()) // Используем часовой пояс скрипта
        .create();
      Logger.log(`Триггер для функции '${functionName}' успешно создан.`);
    } catch (error) {
      Logger.log(`Ошибка при создании триггера: ${error}`);
      // Дополнительная обработка ошибки, например, уведомление администратора
    }
  } else {
    Logger.log(`Триггер для функции '${functionName}' уже существует.`);
  }
}

/**
 * @description Удаляет все триггеры по времени для указанной функции.
 * @param {string} functionName Имя функции, триггеры которой нужно удалить.
 */
function deleteTimeBasedTriggersForFunction(functionName: string): void {
  const triggers: GoogleAppsScript.Script.Trigger[] = ScriptApp.getProjectTriggers();
  let deletedCount: number = 0;

  triggers.forEach(trigger => {
    if (trigger.getHandlerFunction() === functionName && trigger.getEventType() === ScriptApp.EventType.CLOCK) {
      try {
        ScriptApp.deleteTrigger(trigger);
        deletedCount++;
      } catch (error) {
        Logger.log(`Не удалось удалить триггер ${trigger.getUniqueId()}: ${error}`);
      }
    }
  });

  if (deletedCount > 0) {
    Logger.log(`Удалено ${deletedCount} триггеров для функции '${functionName}'.`);
  } else {
    Logger.log(`Триггеры по времени для функции '${functionName}' не найдены.`);
  }
}

// Пример вызова удаления
// deleteTimeBasedTriggersForFunction('processSheetData');

Обработка ошибок и логирование при использовании триггеров

Поскольку триггеры работают автоматически, важно предусмотреть надежную обработку ошибок и логирование.

  • try...catch блоки: Оборачивайте основной код функций, запускаемых триггерами, в блоки try...catch для перехвата исключений.
  • Logger или console.log: Используйте для записи информации о ходе выполнения, переменных и ошибках. Логи можно посмотреть в разделе «Выполнения» редактора скриптов.
  • Stackdriver Logging: Для более продвинутого логирования можно использовать console.log(), console.info(), console.warn(), console.error(). Эти логи доступны в Google Cloud Platform Console.
  • Уведомления об ошибках: Настройте уведомления при создании триггера (через интерфейс или программно с помощью TriggerBuilder.withFailureNotificationFrequency()), чтобы получать email при сбоях.
  • Отказоустойчивость: Проектируйте функции так, чтобы они могли корректно продолжить работу или повторить операцию при следующем запуске, если предыдущий завершился ошибкой (например, используя Properties Service для сохранения состояния).

Решение проблем и часто задаваемые вопросы

Скрипт не запускается по расписанию. Что делать?

  1. Проверьте статус триггера: Зайдите в раздел «Триггеры» и убедитесь, что триггер активен и не отключен из-за ошибок.
  2. Проверьте историю выполнений: Перейдите в раздел «Выполнения». Найдите запуски вашей функции. Если статус «Не удалось», посмотрите логи для выявления ошибки в коде.
  3. Проверьте авторизацию: Возможно, скрипту требуются новые разрешения. Откройте редактор, запустите любую функцию вручную или отредактируйте триггер, чтобы инициировать запрос авторизации.
  4. Проверьте квоты: Убедитесь, что вы не превысили дневные квоты на время выполнения триггеров.
  5. Ошибки в коде: Тщательно проверьте код функции на наличие ошибок, которые могут прерывать выполнение.
  6. Часовой пояс: Убедитесь, что часовой пояс скрипта и триггера установлены корректно.

Как проверить историю запусков триггера?

История запусков всех функций, включая те, что были вызваны триггерами, доступна на левой панели редактора Apps Script в разделе «Выполнения». Здесь можно увидеть время начала, длительность, статус (Завершено, Не удалось) и логи (Logger.log или console.log).

Устранение ошибок авторизации

Ошибки авторизации часто возникают, когда:

  • Скрипт был обновлен и теперь использует новые сервисы Google, требующие дополнительных разрешений.
  • Политики безопасности вашего домена Google Workspace изменились.
  • Токен авторизации истек или был отозван.

Для исправления:

  1. Откройте проект Apps Script.
  2. Запустите любую функцию вручную из редактора.
  3. Либо перейдите в «Триггеры», выберите проблемный триггер и нажмите «Редактировать» (карандаш), затем «Сохранить».
  4. Система запросит необходимые разрешения. Внимательно просмотрите их и предоставьте доступ.
  5. В некоторых случаях может потребоваться полностью отозвать доступ скрипта к вашему аккаунту Google (на странице управления доступом приложений) и затем авторизовать его заново.

Влияние часовых поясов на расписание

Расписание триггеров зависит от часового пояса, установленного для проекта Apps Script. Этот пояс можно проверить и изменить в настройках проекта (Файл -> Настройки проекта).

  • При создании триггера через интерфейс, он будет использовать часовой пояс проекта.
  • При создании триггера программно (ScriptApp.newTrigger()), можно явно указать часовой пояс с помощью метода .inTimezone(timezone). Рекомендуется использовать Session.getScriptTimeZone() для согласованности или указывать конкретный пояс (например, ‘Europe/Moscow’).
  • Убедитесь, что время, указанное в настройках триггера (например, atHour(8)), соответствует желаемому времени запуска в часовом поясе проекта.

Практические примеры и советы

Автоматическая отправка отчетов по электронной почте по расписанию

Частый сценарий — сбор данных из Google Sheets или других источников и отправка сводного отчета по email ежедневно/еженедельно.

/**
 * @description Собирает данные из таблицы 'SalesData' и отправляет 
 *              сводный отчет на указанный email.
 *              Предназначена для запуска триггером по времени.
 */
function sendSalesReport(): void {
  const recipient: string = 'manager@example.com'; // Email получателя
  const subject: string = 'Ежедневный отчет по продажам';
  const ss: GoogleAppsScript.Spreadsheet.Spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  const sheet: GoogleAppsScript.Spreadsheet.Sheet | null = ss.getSheetByName('SalesData');

  if (!sheet) {
    Logger.log('Лист SalesData не найден.');
    MailApp.sendEmail(recipient, `ОШИБКА: ${subject}`, 'Не удалось создать отчет. Лист SalesData не найден.');
    return;
  }

  try {
    const data: any[][] = sheet.getDataRange().getValues();
    // Удаляем заголовок для обработки
    const salesData = data.slice(1);

    // Простая агрегация: подсчет суммы продаж (предполагается сумма во 2-м столбце)
    let totalSales: number = 0;
    salesData.forEach(row => {
      if (typeof row[1] === 'number') {
        totalSales += row[1];
      }
    });

    const reportBody: string = `Отчет по продажам за ${new Date().toLocaleDateString('ru-RU')}:

Всего строк данных: ${salesData.length}
Общая сумма продаж: ${totalSales.toFixed(2)}

С уважением,
Автоматизированная система отчетов`;

    MailApp.sendEmail(recipient, subject, reportBody);
    Logger.log(`Отчет успешно отправлен на ${recipient}`);

  } catch (error) {
    Logger.log(`Ошибка при генерации или отправке отчета: ${error}`);
    MailApp.sendEmail(recipient, `ОШИБКА: ${subject}`, `Произошла ошибка при создании отчета:
${error}

Stack:
${(error as Error).stack}`);
  }
}

Резервное копирование данных Google Sheets по расписанию

Для предотвращения потери данных можно настроить ежедневное или еженедельное копирование важных листов или всей таблицы в новую таблицу или файл на Google Drive.

/**
 * @description Создает резервную копию активной таблицы Google Sheets
 *              в указанную папку на Google Drive.
 *              Предназначена для запуска триггером по времени.
 */
function backupSpreadsheet(): void {
  const backupFolderName: string = 'Sheet Backups'; // Имя папки для бэкапов на Google Drive
  const ss: GoogleAppsScript.Spreadsheet.Spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  const spreadsheetName: string = ss.getName();
  const timestamp: string = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), 'yyyy-MM-dd_HH-mm-ss');
  const backupFileName: string = `${spreadsheetName}_Backup_${timestamp}`;

  try {
    // Находим или создаем папку для бэкапов
    let backupFolder: GoogleAppsScript.Drive.Folder;
    const folders = DriveApp.getFoldersByName(backupFolderName);

    if (folders.hasNext()) {
      backupFolder = folders.next();
    } else {
      backupFolder = DriveApp.createFolder(backupFolderName);
      Logger.log(`Папка для бэкапов '${backupFolderName}' создана.`);
    }

    // Копируем файл таблицы в папку бэкапов
    DriveApp.getFileById(ss.getId()).makeCopy(backupFileName, backupFolder);
    Logger.log(`Резервная копия '${backupFileName}' успешно создана в папке '${backupFolderName}'.`);

  } catch (error) {
    Logger.log(`Ошибка при создании резервной копии: ${error}`);
    // Здесь можно добавить отправку уведомления об ошибке администратору
  }
}

Оптимизация кода для эффективного использования триггеров

  • Минимизируйте вызовы к внешним сервисам: Группируйте операции чтения/записи (например, getValues(), setValues() в Sheets) для уменьшения количества вызовов.
  • Используйте кэш: CacheService может хранить данные, которые не требуют частого обновления, снижая нагрузку на внешние API или сложные вычисления.
  • Используйте Properties Service: Сохраняйте состояние между запусками триггера или конфигурационные параметры (PropertiesService.getScriptProperties() или getUserProperties()).
  • Пишите идемпотентные функции: Функция должна давать одинаковый результат при многократном вызове с одними и теми же входными данными. Это важно, если триггер случайно запустится дважды или если предыдущий запуск не завершился.
  • Контролируйте время выполнения: Следите за временем выполнения ваших функций. Если они приближаются к лимиту (6 или 30 минут), разбейте задачу на более мелкие части или оптимизируйте алгоритмы.
  • Избегайте интенсивных циклов без пауз: В очень длинных циклах можно использовать Utilities.sleep(), но это увеличит общее время выполнения.
  • Очистка старых триггеров: Если вы программно создаете триггеры, не забывайте удалять ненужные, чтобы не превысить квоту в 20 триггеров на скрипт/пользователя.

Добавить комментарий