Что такое триггеры и зачем они нужны?
Триггеры в Google Apps Script — это механизм, позволяющий автоматически запускать определенные функции скрипта в ответ на заданные события или по расписанию. Они являются ключевым инструментом для автоматизации рутинных задач, таких как отправка отчетов, обработка данных, синхронизация информации между сервисами Google и внешними системами, без необходимости ручного запуска скрипта.
Использование триггеров позволяет создавать полностью автономные решения, работающие в фоновом режиме, что значительно повышает эффективность и снижает вероятность ошибок, связанных с человеческим фактором.
Типы триггеров в Google Apps Script (управляемые и устанавливаемые)
Существует два основных типа триггеров:
- Простые триггеры (Simple Triggers): Срабатывают на стандартные события в Документах, Таблицах, Формах или Презентациях (например,
onOpen()
,onEdit()
,onInstall()
). Они имеют ряд ограничений, не требуют специальной авторизации, но не могут выполнять операции, требующие разрешений пользователя (например, отправка email). - Устанавливаемые триггеры (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
- Откройте ваш проект Google Apps Script.
- На левой панели нажмите на иконку будильника («Триггеры»).
- Нажмите кнопку «+ Добавить триггер» в правом нижнем углу.
- В появившемся окне настройте параметры триггера:
- Выберите функцию для запуска: Укажите имя функции, которую нужно выполнять по расписанию.
- Выберите развертывание, которое должно выполняться: Обычно оставляют «Head».
- Выберите источник события: Выберите «Триггер по времени».
- Выберите тип таймера: Укажите желаемую периодичность (минутный, часовой, дневной и т.д.).
- Выберите интервал: Уточните частоту запуска (например, «Каждые 15 минут» или «С 2:00 до 3:00»).
- Настройки уведомлений об ошибках: Выберите, как часто вы хотите получать уведомления по электронной почте в случае сбоя выполнения скрипта (например, немедленно, ежедневно, еженедельно).
- Нажмите «Сохранить».
- 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 для сохранения состояния).
Решение проблем и часто задаваемые вопросы
Скрипт не запускается по расписанию. Что делать?
- Проверьте статус триггера: Зайдите в раздел «Триггеры» и убедитесь, что триггер активен и не отключен из-за ошибок.
- Проверьте историю выполнений: Перейдите в раздел «Выполнения». Найдите запуски вашей функции. Если статус «Не удалось», посмотрите логи для выявления ошибки в коде.
- Проверьте авторизацию: Возможно, скрипту требуются новые разрешения. Откройте редактор, запустите любую функцию вручную или отредактируйте триггер, чтобы инициировать запрос авторизации.
- Проверьте квоты: Убедитесь, что вы не превысили дневные квоты на время выполнения триггеров.
- Ошибки в коде: Тщательно проверьте код функции на наличие ошибок, которые могут прерывать выполнение.
- Часовой пояс: Убедитесь, что часовой пояс скрипта и триггера установлены корректно.
Как проверить историю запусков триггера?
История запусков всех функций, включая те, что были вызваны триггерами, доступна на левой панели редактора Apps Script в разделе «Выполнения». Здесь можно увидеть время начала, длительность, статус (Завершено, Не удалось) и логи (Logger.log
или console.log
).
Устранение ошибок авторизации
Ошибки авторизации часто возникают, когда:
- Скрипт был обновлен и теперь использует новые сервисы Google, требующие дополнительных разрешений.
- Политики безопасности вашего домена Google Workspace изменились.
- Токен авторизации истек или был отозван.
Для исправления:
- Откройте проект Apps Script.
- Запустите любую функцию вручную из редактора.
- Либо перейдите в «Триггеры», выберите проблемный триггер и нажмите «Редактировать» (карандаш), затем «Сохранить».
- Система запросит необходимые разрешения. Внимательно просмотрите их и предоставьте доступ.
- В некоторых случаях может потребоваться полностью отозвать доступ скрипта к вашему аккаунту 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 триггеров на скрипт/пользователя.