使用Drive API/DriveApp将PDF转换为Google文档

Man*_*uel 3 javascript google-apps-script google-drive-api

此问题已成功解决.我正在编辑我的帖子,以记录我的后代和未来参考的经验.

任务

我有117个PDF文件(平均大小~238 KB)上传到Google云端硬盘.我想将它们全部转换为Google文档,并将它们保存在不同的Drive文件夹中.

问题

我尝试使用Drive.Files.insert转换文件.但是,在大多数情况下,只有5个文件可以在函数过早失效之前以这种方式转换

限制超出:DriveApp.(行号,文件"代码")

上面引用的行insert是调用函数的位置.在第一次调用此函数后,后续调用通常会立即失败,而不会创建其他Google doc.

途径

我用3种主要方法来实现我的目标.一个是使用Drive.Files.insert,如上所述.另外两个涉及使用Drive.Files.copy并发送一批HTTP请求.最后两种方法由Tanaike提出,我建议阅读下面的答案以获取更多信息.这些insertcopy功能来自Google Drive REST v2 API,而批处理多个HTTP请求来自Drive REST v3.

使用Drive.Files.insert,我遇到了处理执行限制的问题(在上面的问题部分中进行了解释).一种解决方案是多次运行这些功能.为此,我需要一种方法来跟踪转换的文件.我有两种选择:使用电子表格和延续令牌.因此,我有4种不同的方法来测试:本段中提到的两种方法,批处理HTTP请求,以及调用Drive.Files.copy.

由于团队驱动器的行为与常规驱动器不同,我觉得有必要两次尝试这些方法,其中一个包含PDF的文件夹是常规的非Team Drive文件夹,另一个文件夹在Team Drive下.总的来说,这意味着我有8种不同的测试方法.

这些是我使用的确切功能.其中每个都使用了两次,唯一的变化是源文件夹和目标文件夹的ID(由于上述原因):

方法A:使用Drive.Files.insert和电子表格

function toDocs() {
  var sheet = SpreadsheetApp.openById(/* spreadsheet id*/).getSheets()[0];
  var range = sheet.getRange("A2:E118");
  var table = range.getValues();
  var len = table.length;
  var resources = {
    title: null,
    mimeType: MimeType.GOOGLE_DOCS,
    parents: [{id: /* destination folder id */}]
  };
  var count = 0;
  var files = DriveApp.getFolderById(/* source folder id */).getFiles();
  while (files.hasNext()) {
    var blob = files.next().getBlob();
    var blobName = blob.getName();
    for (var i=0; i<len; i++) {
      if (table[i][0] === blobName.slice(5, 18)) {
        if (table[i][4])
          break;
        resources.title = blobName;
        Drive.Files.insert(resources, blob);  // Limit Exceeded: DriveApp. (line 51, file "Code")
        table[i][4] = "yes";
      }
    }

    if (++count === 10) {
      range.setValues(table);
      Logger.log("time's up");
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

方法B:使用Drive.Files.insert延续令牌

function toDocs() {
  var folder = DriveApp.getFolderById(/* source folder id */);
  var sprop = PropertiesService.getScriptProperties();
  var contToken = sprop.getProperty("contToken");
  var files = contToken ? DriveApp.continueFileIterator(contToken) : folder.getFiles();
  var options = {
    ocr: true
  };
  var resource = {
    title: null,
    mimeType: null,
    parents: [{id: /* destination folder id */}]
  };

  while (files.hasNext()) {
    var blob = files.next().getBlob();
    resource.title = blob.getName();
    resource.mimeType = blob.getContentType();
    Drive.Files.insert(resource, blob, options);  // Limit Exceeded: DriveApp. (line 113, file "Code")
    sprop.setProperty("contToken", files.getContinuationToken());
  }
}
Run Code Online (Sandbox Code Playgroud)

方法C:使用Drive.Files.copy

这个功能的功劳归于Tanaike - 有关详细信息,请参阅下面的答案.

function toDocs() {
  var sourceFolderId = /* source folder id */;
  var destinationFolderId = /* destination folder id */;
  var files = DriveApp.getFolderById(sourceFolderId).getFiles();
  while (files.hasNext()) {
    var res = Drive.Files.copy({parents: [{id: destinationFolderId}]}, files.next().getId(), {convert: true, ocr: true});
    Logger.log(res) 
  }
}
Run Code Online (Sandbox Code Playgroud)

方法D:发送批量的HTTP请求

这个功能的功劳归于Tanaike - 有关详细信息,请参阅下面的答案.

function toDocs() {
  var sourceFolderId = /* source folder id */;
  var destinationFolderId = /* destination folder id */;

  var files = DriveApp.getFolderById(sourceFolderId).getFiles();
  var rBody = [];
  while (files.hasNext()) {
    rBody.push({
      method: "POST",
      endpoint: "https://www.googleapis.com/drive/v3/files/" + files.next().getId() + "/copy",
      requestBody: {
        mimeType: "application/vnd.google-apps.document",
        parents: [destinationFolderId]
      }
    });
  }
  var cycle = 20; // Number of API calls at 1 batch request.
  for (var i = 0; i < Math.ceil(rBody.length / cycle); i++) {
    var offset = i * cycle;
    var body = rBody.slice(offset, offset + cycle);
    var boundary = "xxxxxxxxxx";
    var contentId = 0;
    var data = "--" + boundary + "\r\n";
    body.forEach(function(e){
      data += "Content-Type: application/http\r\n";
      data += "Content-ID: " + ++contentId + "\r\n\r\n";
      data += e.method + " " + e.endpoint + "\r\n";
      data += e.requestBody ? "Content-Type: application/json; charset=utf-8\r\n\r\n" : "\r\n";
      data += e.requestBody ? JSON.stringify(e.requestBody) + "\r\n" : "";
      data += "--" + boundary + "\r\n";
    });
    var options = {
      method: "post",
      contentType: "multipart/mixed; boundary=" + boundary,
      payload: Utilities.newBlob(data).getBytes(),
      headers: {'Authorization': 'Bearer ' + ScriptApp.getOAuthToken()},
      muteHttpExceptions: true,
    };
    var res = UrlFetchApp.fetch("https://www.googleapis.com/batch", options).getContentText();
//    Logger.log(res); // If you use this, please remove the comment.
  }
}
Run Code Online (Sandbox Code Playgroud)

什么工作,什么没有

  • 使用Drive.Files.insert的所有函数都不起作用.insert用于转换的每个函数都因此错误而失败

    限制超出:DriveApp.(行号,文件"代码")

    (行号用通用符号替换).没有找到错误的进一步细节或描述.一个值得注意的变化是我使用电子表格而PDF是在团队驱动文件夹中; 虽然所有其他方法在没有转换单个文件的情况下立即失败,但这个方法在失败之前转换为5 但是,在考虑为什么这种变化比其他变量更好时,我认为这比使用特定资源(电子表格,团队驱动等)的任何原因更具吸引力.

  • 仅当源文件夹是个人(非Team Drive)文件夹时,才使用Drive.Files.copy批处理HTTP请求.

  • 尝试copy从Team Drive文件夹中读取时使用该功能失败,并显示以下错误:

    找不到档案:1RAGxe9a_-euRpWm3ePrbaGaX5brpmGXu(行号,文件"代码")

    (行号用通用符号替换).被引用的行是

    var res = Drive.Files.copy({parents: [{id: destinationFolderId}]}, files.next().getId(), {convert: true, ocr: true});
    
    Run Code Online (Sandbox Code Playgroud)
  • 从Team Drive文件夹读取时使用批处理HTTP请求不会执行任何操作 - 不会创建任何doc文件,也不会抛出任何错误.功能在没有完成任何事情的情况下无声地终止

结论

如果您希望将大量PDF转换为Google文档或文本文件,请使用Drive.Files.copy发送批量HTTP请求,并确保将PDF存储在个人驱动器而不是团队驱动器中.


特别感谢@tehhowch对我的问题表现出如此狂热的兴趣并反复回来提供反馈,并感谢@Tanaike提供代码以及成功解决我的问题的解释(需要注意,请阅读上面的详细信息).

Tan*_*ike 5

您想要将文件夹中的PDF文件转换为Google文档.PDF文件位于团队驱动器的文件夹中.您想导入将它们转换为Google云端硬盘的文件夹.如果我的理解是正确的,这个方法怎么样?

从PDF到谷歌文档转换,它不仅使用转换Drive.Files.insert(),而且还Drive.Files.copy().使用的好处Drive.Files.copy()

  • 虽然Drive.Files.insert()具有5 MB的大小限制,但Drive.Files.copy()可以使用超过5 MB的大小.
  • 在我的环境中,过程速度快于Drive.Files.insert().

对于这种方法,我想提出以下两种模式.

模式1:使用Drive API v2

在这种情况下,Advanced Google Services的Drive API v2用于转换文件.

function myFunction() {
  var sourceFolderId = "/* source folder id */";
  var destinationFolderId = "/* dest folder id */";
  var files = DriveApp.getFolderById(sourceFolderId).getFiles();
  while (files.hasNext()) {
    var res = Drive.Files.copy({parents: [{id: destinationFolderId}]}, files.next().getId(), {convert: true, ocr: true});
//    Logger.log(res) // If you use this, please remove the comment.
  }
}
Run Code Online (Sandbox Code Playgroud)

模式2:使用Drive API v3

在这种情况下,Drive API v3用于转换文件.在这里,我使用了批处理请求.因为批处理请求可以通过一次API调用使用100个API调用.这样,就可以删除API配额问题.

function myFunction() {
  var sourceFolderId = "/* source folder id */";
  var destinationFolderId = "/* dest folder id */";

  var files = DriveApp.getFolderById(sourceFolderId).getFiles();
  var rBody = [];
  while (files.hasNext()) {
    rBody.push({
      method: "POST",
      endpoint: "https://www.googleapis.com/drive/v3/files/" + files.next().getId() + "/copy",
      requestBody: {
        mimeType: "application/vnd.google-apps.document",
        parents: [destinationFolderId]
      }
    });
  }
  var cycle = 100; // Number of API calls at 1 batch request.
  for (var i = 0; i < Math.ceil(rBody.length / cycle); i++) {
    var offset = i * cycle;
    var body = rBody.slice(offset, offset + cycle);
    var boundary = "xxxxxxxxxx";
    var contentId = 0;
    var data = "--" + boundary + "\r\n";
    body.forEach(function(e){
      data += "Content-Type: application/http\r\n";
      data += "Content-ID: " + ++contentId + "\r\n\r\n";
      data += e.method + " " + e.endpoint + "\r\n";
      data += e.requestBody ? "Content-Type: application/json; charset=utf-8\r\n\r\n" : "\r\n";
      data += e.requestBody ? JSON.stringify(e.requestBody) + "\r\n" : "";
      data += "--" + boundary + "\r\n";
    });
    var options = {
      method: "post",
      contentType: "multipart/mixed; boundary=" + boundary,
      payload: Utilities.newBlob(data).getBytes(),
      headers: {'Authorization': 'Bearer ' + ScriptApp.getOAuthToken()},
      muteHttpExceptions: true,
    };
    var res = UrlFetchApp.fetch("https://www.googleapis.com/batch", options).getContentText();
//    Logger.log(res); // If you use this, please remove the comment.
  }
}
Run Code Online (Sandbox Code Playgroud)

注意 :

  • 如果1批量请求的API调用数量很大(当前值为100),请修改var cycle = 100.
  • 如果Drive API v3无法用于团队驱动,请告诉我.我可以将其转换为Drive API v2.
  • 如果团队驱动是您的情况问题的原因,您可以在将PDF文件复制到Google云端硬盘后尝试此操作吗?

参考:

如果这些对你没用,我很抱歉.