Работа с Google Drive
Скрипт, который каждый месяц сам выгружает отчёт в PDF, кладёт его в папку «Отчёты/2026/07», открывает доступ руководителю и присылает ссылку.
DriveApp — служба Apps Script для работы с Google Диском: создание и поиск файлов и папок, копирование, перемещение, выдача прав доступа.
Зачем это нужно
Ежемесячный отчёт — классика офисной рутины. Открыть таблицу, «Файл → Скачать → PDF», выбрать нужный лист, поправить ориентацию, сохранить, залить на Диск, найти прошлогоднюю папку, создать новую, переименовать файл по стандарту, расшарить, скопировать ссылку, вставить в письмо. Десять минут в месяц, два часа в год, и каждый раз кто-нибудь ошибается в названии.
Всё это описывается тридцатью строками кода — и дальше работает само по триггеру первого числа. Заодно вы получаете то, чего не даёт ручная работа: единообразие. Файлы всегда названы одинаково, всегда лежат там, где ожидаешь, и всегда доступны тем, кому нужно.
Файлы и папки: главное, что нужно понять
На Google Диске нет путей в привычном смысле. Нет метода getFolderByPath('Отчёты/2026/07'). Есть файлы, у которых указан родитель, и подниматься по этой цепочке приходится вручную, уровень за уровнем.
Второе, ещё более важное: имена на Диске не уникальны. В одной папке могут спокойно лежать три файла «Отчёт.pdf» и две папки «2026». Поэтому все методы поиска возвращают не объект, а итератор — ленивую последовательность, которую нужно обходить через hasNext() и next().
function getOrCreateFolder(parent, name) {
const folders = parent.getFoldersByName(name);
return folders.hasNext() ? folders.next() : parent.createFolder(name);
}
Эта крошечная функция — рабочая лошадка любого скрипта архивации. Без неё каждый запуск честно вызовет createFolder('Отчёты') и создаст ещё одну папку с тем же именем: через полгода на Диске будет шесть «Отчётов», и в каждой по одному файлу. Google не ругнётся — с его точки зрения всё легально.
Поиск файлов
Для более сложных выборок есть searchFiles с языком запросов Drive:
function findOldReports() {
const query = 'title contains "Отчёт" and modifiedDate < "2026-01-01" and trashed = false';
const files = DriveApp.searchFiles(query);
const result = [];
while (files.hasNext()) {
const file = files.next();
result.push([file.getName(), file.getUrl(), file.getLastUpdated()]);
}
Logger.log('Найдено файлов: ' + result.length);
return result;
}
Обратите внимание на кавычки: внутри строки запроса значения обёрнуты в двойные кавычки, а сама строка — в одинарные. Условие trashed = false добавляйте всегда: по умолчанию поиск найдёт и то, что лежит в корзине.
Экспорт листа в PDF
Здесь новичков ждёт сюрприз: у SpreadsheetApp нет метода «сохрани лист как PDF». Есть два обходных пути.
Быстрый: ss.getAs('application/pdf') — но он выгрузит всю книгу целиком, со всеми служебными листами, в вертикальной ориентации и с сеткой. Годится для черновиков.
Точный: обратиться к тому же URL экспорта, который дёргает кнопка «Скачать → PDF» в интерфейсе. Он принимает параметры: конкретный лист, ориентацию, поля, сетку.
function exportSheetToPdf(sheet) {
const ss = sheet.getParent();
const params = [
'format=pdf',
'gid=' + sheet.getSheetId(), // какой именно лист
'portrait=false', // альбомная ориентация
'fitw=true', // вписать по ширине страницы
'gridlines=false', // без сетки
'printtitle=false', // без названия книги в шапке
'sheetnames=false',
'top_margin=0.5', 'bottom_margin=0.5',
'left_margin=0.5', 'right_margin=0.5'
].join('&');
const url = 'https://docs.google.com/spreadsheets/d/' + ss.getId() + '/export?' + params;
const response = UrlFetchApp.fetch(url, {
headers: { Authorization: 'Bearer ' + ScriptApp.getOAuthToken() },
muteHttpExceptions: true
});
if (response.getResponseCode() !== 200) {
throw new Error('Экспорт не удался, код ' + response.getResponseCode());
}
return response.getBlob().setName(sheet.getName() + '.pdf');
}
Ключевая строка — ScriptApp.getOAuthToken(). Она отдаёт токен доступа текущего пользователя, и мы подставляем его в заголовок Authorization. Без него Google ответит страницей входа: с его точки зрения к приватному документу постучался анонимный клиент. Именно так скрипт «притворяется» вашим браузером и получает ровно тот PDF, который вы увидели бы, нажав кнопку скачивания.
Сценарий целиком: архивация отчёта
function archiveMonthlyReport() {
const ss = SpreadsheetApp.getActive();
const sheet = ss.getSheetByName('Отчёт');
const tz = ss.getSpreadsheetTimeZone();
const now = new Date();
const year = Utilities.formatDate(now, tz, 'yyyy');
const month = Utilities.formatDate(now, tz, 'MM');
const stamp = Utilities.formatDate(now, tz, 'yyyy-MM-dd');
const root = getOrCreateFolder(DriveApp.getRootFolder(), 'Отчёты');
const folder = getOrCreateFolder(getOrCreateFolder(root, year), month);
const blob = exportSheetToPdf(sheet).setName('Отчёт ' + stamp + '.pdf');
const file = folder.createFile(blob);
file.addViewer('[email protected]');
MailApp.sendEmail({
to: '[email protected]',
subject: 'Отчёт за ' + stamp,
htmlBody: '<p>Отчёт готов: <a href="' + file.getUrl() + '">открыть</a></p>'
});
Logger.log('Сохранено: ' + file.getUrl());
}
Двадцать строк — и вся ручная процедура из вступления автоматизирована. Utilities.formatDate с часовым поясом таблицы обязателен: без него скрипт, запущенный триггером в 00:30, легко положит июльский отчёт в папку за июнь, потому что серверное время — UTC.
Раздача доступа
| Метод | Что делает |
file.addViewer(email) | даёт человеку право просмотра, Google шлёт ему уведомление |
file.addEditor(email) | право редактирования |
file.addCommenter(email) | право комментировать |
file.removeViewer(email) | отзывает доступ |
file.getViewers() | список тех, у кого есть доступ — удобно для аудита |
file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW) | доступ «по ссылке» всем подряд |
С последней строкой обращайтесь осторожно. Ссылка на финансовый отчёт, однажды попавшая в рабочий чат, живёт там вечно и пересылается дальше без вашего ведома. Точечный addViewer почти всегда правильнее. В корпоративных доменах политика безопасности может вообще запрещать публичные ссылки — тогда вызов упадёт с ошибкой прав.
Как это работает
DriveApp — это обёртка над сетевым API Диска. Каждый вызов getFilesByName, createFile, addViewer — это отдельный запрос к серверу Google, который занимает десятки, а то и сотни миллисекунд. Локально это незаметно, но в цикле по тысяче строк превращается в катастрофу: скрипт упрётся в лимит выполнения (6 минут) задолго до конца.
Отсюда правило: обращения к Диску выносим за пределы циклов. Нужную папку получаем один раз до цикла, а не на каждой итерации.
Второй важный момент — чей это Диск. Файлы создаются на Диске того аккаунта, от имени которого работает скрипт, и занимают его квоту хранилища. Если скрипт написал стажёр и повесил триггер, а потом уволился и его аккаунт отключили, архив отчётов пропадёт вместе с ним. Для важных процессов используйте общий диск или сервисный аккаунт организации.
И третье: file.setTrashed(true) отправляет файл в корзину, откуда его можно восстановить. Полного удаления в DriveApp нет намеренно — это отличная страховка. Не пишите скрипты, которые «чистят» Диск безвозвратно; перемещайте в архивную папку или в корзину.
Частые ошибки
- Считать, что
getFoldersByNameвернёт папку. Он возвращает итератор. Обращение к.getName()напрямую даст ошибку, а.length—undefined. - Создавать папку без проверки. Каждый запуск плодит дубликат с тем же именем. Всегда используйте паттерн «найди или создай».
- Забыть
trashed = falseв поиске. Скрипт «находит» файлы, которые пользователь давно удалил. - Экспортировать книгу вместо листа.
ss.getAs('application/pdf')отдаёт все листы, включая служебные, где лежат пароли и черновики расчётов. - Не ставить
muteHttpExceptions: trueпри экспорте. Вместо внятного «код 401» вы получите обрезанное исключение и будете гадать, что сломалось. - Не учитывать часовой пояс. Ночной триггер раскладывает файлы по неправильным месяцам.
- Раздавать
ANYONE_WITH_LINK«чтобы не возиться с правами». Это самая частая причина утечки внутренних данных через Диск.
Итоги
- На Диске нет путей и нет уникальных имён: спускайтесь по папкам вручную и всегда работайте через итераторы.
getOrCreateFolder— обязательный помощник; без него архив зарастает дублями папок.- Точный экспорт листа в PDF делается через URL
/exportс параметрами и токеномScriptApp.getOAuthToken(). - Даты в именах файлов и папок форматируйте через
Utilities.formatDateс часовым поясом таблицы. - Доступ выдавайте точечно (
addViewer), а не ссылкой для всех. - Каждый вызов
DriveApp— сетевой запрос: не держите их внутри длинных циклов.