Как в Google Apps Script скачать файл по ссылке: полное руководство

Что такое Google Apps Script и его возможности

Google Apps Script (GAS) — это облачная платформа для скриптов на основе JavaScript, которая позволяет расширять функциональность приложений Google Workspace (Docs, Sheets, Drive, Gmail и др.) и автоматизировать рабочие процессы. С его помощью можно создавать кастомные меню, диалоговые окна, автоматизировать задачи, интегрироваться с внешними API и многое другое, работая непосредственно на серверах Google.

Обзор задачи: скачивание файла по ссылке

Часто возникает необходимость автоматизировать процесс загрузки файлов из внешних источников по прямым URL-адресам. Например, это может быть ежедневная загрузка отчетов из CRM, прайс-листов от поставщиков или медиафайлов из облачных хранилищ. Google Apps Script предоставляет встроенные сервисы для выполнения HTTP-запросов, что делает эту задачу выполнимой без привлечения сторонних серверов.

Необходимые условия: доступ к Google Apps Script и знание JavaScript

Для работы с примерами из этой статьи вам потребуется аккаунт Google и базовый доступ к среде разработки Google Apps Script (например, через редактор скриптов в Google Sheets или Google Docs). Предполагается, что вы обладаете уверенными знаниями JavaScript, включая асинхронные операции (хотя GAS работает синхронно по умолчанию в большинстве случаев), работу с объектами и обработку ошибок.

Методы скачивания файла по ссылке в Google Apps Script

Использование UrlFetchApp для получения файла

Основным инструментом для взаимодействия с внешними URL в GAS является сервис UrlFetchApp. Метод fetch(url, params) позволяет отправлять HTTP-запросы (GET, POST и др.) к указанному URL.

Для скачивания файла обычно используется GET-запрос. Сервис автоматически обрабатывает перенаправления и возвращает ответ от сервера.

/**
 * Выполняет GET-запрос к указанному URL.
 * @param {string} url - URL для скачивания файла.
 * @returns {GoogleAppsScript.URL_Fetch.HTTPResponse} Объект ответа.
 * @throws {Error} Если запрос не удался.
 */
function fetchFileContent(url) {
  try {
    const options = {
      'method': 'get',
      'muteHttpExceptions': true // Важно для ручной обработки ошибок HTTP
    };
    const response = UrlFetchApp.fetch(url, options);
    return response;
  } catch (e) {
    Logger.log(`Ошибка сети при запросе к ${url}: ${e}`);
    throw new Error(`Сетевая ошибка при доступе к URL: ${e.message}`);
  }
}

Разбор ответа (Response) UrlFetchApp

Метод UrlFetchApp.fetch() возвращает объект HTTPResponse. Для получения содержимого файла используется метод getBlob(), который возвращает данные в виде Blob (Binary Large Object). Blob представляет собой объект, содержащий необработанные данные файла.

Важно проверять код ответа HTTP перед обработкой содержимого. Успешный запрос обычно возвращает код 200 OK.

/**
 * Извлекает Blob из HTTP-ответа, проверяя код состояния.
 * @param {GoogleAppsScript.URL_Fetch.HTTPResponse} response - Объект HTTP-ответа.
 * @param {string} url - Исходный URL (для логирования).
 * @returns {GoogleAppsScript.Base.Blob} Blob с содержимым файла.
 * @throws {Error} Если код ответа не 200.
 */
function extractBlobFromResponse(response, url) {
  const responseCode = response.getResponseCode();
  if (responseCode === 200) {
    const fileBlob = response.getBlob();
    Logger.log(`Файл успешно получен из ${url}. Тип контента: ${fileBlob.getContentType()}, Размер: ${fileBlob.getBytes().length} байт.`);
    return fileBlob;
  } else {
    Logger.log(`Ошибка при скачивании файла с ${url}. Код ответа: ${responseCode}. Ответ: ${response.getContentText()}`);
    throw new Error(`Сервер вернул ошибку ${responseCode} для URL ${url}`);
  }
}

Обработка ошибок и исключений при скачивании

При скачивании файлов могут возникать различные ошибки:

Сетевые ошибки: Недоступность URL, проблемы с DNS, таймауты. Перехватываются с помощью try...catch вокруг UrlFetchApp.fetch().

Ошибки HTTP: Коды ответа, отличные от 2xx (например, 404 Not Found, 403 Forbidden, 500 Internal Server Error). Требуют проверки response.getResponseCode(). Использование опции muteHttpExceptions: true позволяет перехватывать эти ошибки вручную, иначе GAS выбросит исключение.

Ограничения GAS: Превышение квот на количество запросов UrlFetchApp или время выполнения скрипта.

Надежный скрипт должен предусматривать обработку всех этих сценариев, логировать ошибки и, возможно, реализовывать механизм повторных попыток.

Сохранение скачанного файла в Google Drive

Получение доступа к Google Drive с помощью Google Apps Script

Для работы с Google Drive используется сервис DriveApp. Он предоставляет методы для навигации по папкам, создания, чтения и модификации файлов.

/**
 * Получает объект папки в Google Drive по ее ID.
 * Если ID не указан, возвращает корневую папку.
 * @param {string} [folderId] - ID папки назначения (опционально).
 * @returns {GoogleAppsScript.Drive.Folder} Объект папки.
 * @throws {Error} Если папка с указанным ID не найдена.
 */
function getTargetDriveFolder(folderId) {
  if (folderId) {
    try {
      const folder = DriveApp.getFolderById(folderId);
      Logger.log(`Используется папка: ${folder.getName()} (ID: ${folderId})`);
      return folder;
    } catch (e) {
      Logger.log(`Ошибка: Не удалось найти папку с ID ${folderId}. ${e}`);
      throw new Error(`Папка с ID ${folderId} не найдена или недоступна.`);
    }
  } else {
    const rootFolder = DriveApp.getRootFolder();
    Logger.log('Используется корневая папка Google Drive.');
    return rootFolder;
  }
}

Создание файла в Google Drive из полученного содержимого

Получив Blob с содержимым файла, можно создать новый файл в нужной папке Google Drive с помощью метода createFile(blob) объекта Folder.

/**
 * Создает файл в указанной папке Google Drive из объекта Blob.
 * @param {GoogleAppsScript.Drive.Folder} targetFolder - Папка для сохранения файла.
 * @param {GoogleAppsScript.Base.Blob} fileBlob - Blob с содержимым файла.
 * @param {string} fileName - Желаемое имя файла.
 * @returns {GoogleAppsScript.Drive.File} Созданный файл.
 */
function saveBlobToDrive(targetFolder, fileBlob, fileName) {
  // Устанавливаем имя для Blob перед созданием файла
  fileBlob.setName(fileName);
  const file = targetFolder.createFile(fileBlob);
  Logger.log(`Файл '${fileName}' успешно сохранен в папке '${targetFolder.getName()}'. ID файла: ${file.getId()}`);
  return file;
}

Указание имени файла и типа контента

Имя файла можно задать, установив его для Blob перед вызовом createFile() с помощью blob.setName(fileName). Если имя не задано, Google Drive попытается определить его самостоятельно, что не всегда надежно.

Тип контента (MIME type) обычно определяется автоматически на основе данных Blob. Blob получает MIME type из заголовка Content-Type HTTP-ответа. Если заголовок отсутствует или некорректен, можно попытаться установить MIME type явно с помощью blob.setContentType(mimeType), хотя DriveApp.createFile обычно справляется с определением типа самостоятельно.

Пример кода для скачивания и сохранения файла

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

/**
 * Скачивает файл по URL и сохраняет его в указанную папку Google Drive.
 *
 * @param {string} fileUrl URL файла для скачивания.
 * @param {string} targetFileName Имя для сохраняемого файла.
 * @param {string} [targetFolderId] ID папки в Google Drive для сохранения (опционально, по умолчанию - корневая папка).
 * @returns {string | null} ID созданного файла в Google Drive или null в случае ошибки.
 */
function downloadFileToGoogleDrive(fileUrl, targetFileName, targetFolderId) {
  if (!fileUrl || !targetFileName) {
    Logger.log('Ошибка: URL файла и имя файла должны быть указаны.');
    return null;
  }

  try {
    // 1. Выполняем HTTP-запрос
    const options = {
      'method': 'get',
      'muteHttpExceptions': true, // Включаем ручную обработку HTTP-ошибок
      'headers': {
         // Пример заголовка, может понадобиться для некоторых сайтов
        'User-Agent': 'Mozilla/5.0 (compatible; Google-Apps-Script)' 
      },
      'followRedirects': true // По умолчанию true, но можно указать явно
    };
    const response = UrlFetchApp.fetch(fileUrl, options);
    Logger.log(`Запрос к ${fileUrl} выполнен.`);

    // 2. Проверяем код ответа и извлекаем Blob
    const responseCode = response.getResponseCode();
    if (responseCode !== 200) {
      Logger.log(`Ошибка HTTP ${responseCode} при скачивании ${fileUrl}. Ответ: ${response.getContentText().substring(0, 500)}`);
      throw new Error(`Сервер вернул ошибку ${responseCode}`);
    }
    
    const fileBlob = response.getBlob();
    Logger.log(`Blob получен. Тип: ${fileBlob.getContentType()}, Размер: ${fileBlob.getBytes().length} байт.`);

    // 3. Получаем целевую папку
    const targetFolder = getTargetDriveFolder(targetFolderId);

    // 4. Устанавливаем имя и сохраняем файл
    fileBlob.setName(targetFileName);
    const savedFile = targetFolder.createFile(fileBlob);
    const fileId = savedFile.getId();
    
    Logger.log(`Файл '${targetFileName}' успешно сохранен в папке '${targetFolder.getName()}'. ID файла: ${fileId}`);
    return fileId;

  } catch (error) {
    Logger.log(`Произошла ошибка при скачивании или сохранении файла: ${error.message}`);
    // Дополнительная информация об ошибке, если доступна
    if (error.stack) {
      Logger.log(`Стек вызовов: ${error.stack}`);
    }
    return null;
  }
}

/**
 * Вспомогательная функция для получения папки в Google Drive.
 * @param {string} [folderId] ID папки (опционально).
 * @returns {GoogleAppsScript.Drive.Folder} Объект папки.
 */
function getTargetDriveFolder(folderId) {
  if (folderId) {
    try {
      return DriveApp.getFolderById(folderId);
    } catch (e) {
      Logger.log(`Папка с ID ${folderId} не найдена, используется корневая. Ошибка: ${e}`);
      // Fallback to root folder if ID is invalid
    }
  }
  return DriveApp.getRootFolder();
}

// --- Пример использования --- 
/**
 * Тестовая функция для демонстрации скачивания.
 */
function testDownload() {
  const imageUrl = 'https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png'; // Пример URL
  const fileName = 'google_logo.png';
  // Опционально: укажите ID папки, куда сохранить файл
  // const folderId = 'YOUR_FOLDER_ID_HERE'; 
  // const fileId = downloadFileToGoogleDrive(imageUrl, fileName, folderId);
  
  const fileId = downloadFileToGoogleDrive(imageUrl, fileName); // Сохранение в корень

  if (fileId) {
    Logger.log(`Тест завершен успешно. ID файла: ${fileId}`);
  } else {
    Logger.log('Тест завершен с ошибкой.');
  }
}
Реклама

Объяснение каждой части кода

downloadFileToGoogleDrive(fileUrl, targetFileName, targetFolderId): Основная функция, принимающая URL, имя файла и опциональный ID папки.

Проверка входных данных: Убеждаемся, что URL и имя файла переданы.

UrlFetchApp.fetch(): Выполняет GET-запрос. muteHttpExceptions: true позволяет нам самим проверить responseCode. headers могут быть необходимы для некоторых сайтов. followRedirects: true обрабатывает перенаправления.

Проверка responseCode: Если код не 200, логируем ошибку и выбрасываем исключение.

response.getBlob(): Получаем данные файла в виде Blob.

getTargetDriveFolder(targetFolderId): Вспомогательная функция для получения объекта Folder.

fileBlob.setName(targetFileName): Устанавливаем имя для Blob.

targetFolder.createFile(fileBlob): Создаем файл в Google Drive.

try...catch: Обертывает весь процесс для перехвата любых ошибок (сетевых, HTTP, Drive API и т.д.).

getTargetDriveFolder(folderId): Ищет папку по ID или возвращает корневую папку.

testDownload(): Пример вызова основной функции для тестирования.

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

Скопируйте код в редактор скриптов Google Apps Script (например, связанный с Google Sheet).

Замените URL в testDownload на реальный URL файла, который вы хотите скачать.

При необходимости укажите targetFolderId в testDownload.

Выберите функцию testDownload в выпадающем меню редактора и нажмите кнопку ‘Выполнить’.

При первом запуске потребуется предоставить разрешения для скрипта на доступ к внешним сервисам (UrlFetchApp) и Google Drive (DriveApp).

Результат выполнения (ID файла или сообщение об ошибке) будет виден в логах (Вид -> Журналы).

Продвинутые техники и оптимизация

Скачивание больших файлов: Chunked Download

Google Apps Script имеет ограничение на время выполнения скрипта (обычно 6 минут для обычных аккаунтов Gmail и 30 минут для аккаунтов Google Workspace). Скачивание очень больших файлов может превысить этот лимит. UrlFetchApp не поддерживает нативное скачивание по частям (chunked download) для одного запроса. Если сервер поддерживает заголовки Range, можно попытаться скачивать файл частями несколькими запросами UrlFetchApp.fetch(), собирая Blob по частям, но это усложняет код и не всегда возможно.

Использование сервиса Lock для предотвращения конфликтов

Если скрипт может запускаться одновременно несколькими триггерами или пользователями (например, при обработке веб-хуков), это может привести к конфликтам при записи в Drive или превышению квот. Сервис LockService позволяет установить блокировку на выполнение критических участков кода.

/**
 * Пример использования LockService для предотвращения параллельного выполнения.
 */
function downloadWithLock() {
  const lock = LockService.getScriptLock();
  const lockAcquired = lock.tryLock(10000); // Попытаться получить блокировку на 10 сек

  if (!lockAcquired) {
    Logger.log('Не удалось получить блокировку. Другой процесс уже запущен.');
    return;
  }

  try {
    // Здесь вызываем основную функцию скачивания
    // downloadFileToGoogleDrive(...);
    Logger.log('Блокировка получена, выполняется скачивание...');
  } finally {
    lock.releaseLock(); // Обязательно освобождаем блокировку
    Logger.log('Блокировка освобождена.');
  }
}

Обработка аутентификации: скачивание файлов, требующих авторизации

Если URL требует аутентификации, стандартный UrlFetchApp.fetch() не сможет получить доступ. Возможные решения:

Basic Auth: Передать заголовок Authorization: 'Authorization': 'Basic ' + Utilities.base64Encode(username + ':' + password).

Bearer Token (API Key): Передать токен в заголовке: 'Authorization': 'Bearer ВАШ_ТОКЕН'.

OAuth2: Для сервисов, поддерживающих OAuth2, использовать библиотеку OAuth2 for Apps Script. Это более сложный, но и наиболее безопасный и стандартный способ для многих API.

Выбор метода зависит от требований конкретного сервиса, предоставляющего файл.

Параллельное скачивание нескольких файлов (если необходимо)

Сервис UrlFetchApp не поддерживает асинхронное выполнение в привычном понимании JavaScript (async/await). Запросы выполняются последовательно. Для одновременного запуска нескольких запросов можно использовать UrlFetchApp.fetchAll(requests), который принимает массив объектов запросов и возвращает массив объектов ответов. Однако обработка ответов все равно будет происходить последовательно после завершения всех запросов.

/**
 * Пример скачивания нескольких файлов с помощью fetchAll.
 */
function downloadMultipleFiles() {
  const requests = [
    { url: 'URL_1', method: 'get', muteHttpExceptions: true },
    { url: 'URL_2', method: 'get', muteHttpExceptions: true }
  ];

  const responses = UrlFetchApp.fetchAll(requests);

  responses.forEach((response, index) => {
    try {
      const fileBlob = extractBlobFromResponse(response, requests[index].url);
      // Дальнейшая обработка и сохранение blob...
      // saveBlobToDrive(...);
    } catch (e) {
      Logger.log(`Ошибка обработки ответа для ${requests[index].url}: ${e.message}`);
    }
  });
}

Этот метод может ускорить процесс, если время ожидания ответа от серверов значительно, но следует учитывать квоты и возможное увеличение пиковой нагрузки на ресурсы.


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