javascript - Använda Drive API/DriveApp för att konvertera från PDF-filer till Google-dokument
Detta problem har lösts framgångsrikt. Jag redigerar mitt inlägg för att dokumentera min upplevelse för eftervärlden och framtida referens.
Uppgiften
Jag har 117 PDF-filer (genomsnittlig storlek ~238 KB) uppladdade på Google Drive. Jag vill konvertera dem alla till Google Docs och behålla dem i en annan Drive-mapp.
Problemet
Jag försökte konvertera filerna med Drive.Files.insert. Under de flesta omständigheter kunde dock endast 5 filer konverteras på detta sätt innan funktionen avslutas i förtid med detta fel
Gräns överskriden: DriveApp. (rad #, fil "Code")
där linjen som refereras ovan är när insert-funktionen kallas. Efter att ha anropat denna funktion för första gången, misslyckades efterföljande anrop vanligtvis omedelbart utan att något ytterligare Google-dokument skapades.
Tillvägagångssätt
Jag använde 3 huvudsakliga sätt för att uppnå mitt mål. Ett var att använda Drive.Files.insert, som nämnts ovan. De andra två involverade att använda Drive.Files.copy och att skicka en batch av HTTP-förfrågningar. De senaste två metoderna föreslogs av Tanaike, och jag rekommenderar att läsa hans svar nedan för mer information. insert och copy-funktionerna kommer från Google Drive REST v2 API, medan batchning av flera HTTP-förfrågningar kommer från Drive REST v3.
Med Drive.Files.insert upplevde jag problem med att hantera exekveringsbegränsningar (förklarat i problemdelen ovan). En lösning var att köra funktionerna flera gånger. Och för det behövde jag ett sätt att hålla reda på vilka filer som hade konverterats. Jag hade två alternativ för detta: att använda ett kalkylblad och en fortsättningstoken. Därför hade jag 4 olika metoder att testa: de två som nämns i detta stycke, batchning av HTTP-förfrågningar och att anropa Drive.Files.copy.
Eftersom teamenheter beter sig annorlunda än vanliga enheter, kände jag att det var nödvändigt att prova varje metod två gånger, en där mappen som innehåller PDF-filerna är en vanlig mapp som inte är en teamenhet och en där den mappen ligger under en teamenhet. Totalt betyder detta att jag hade 8 olika metoder att testa.
Dessa är de exakta funktioner jag använde. Var och en av dessa användes två gånger, med de enda variationerna vara ID:t för käll- och destinationsmapparna (av skäl som anges ovan):
Metod A: Använda Drive.Files.insert och ett kalkylark
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");
}
}
}
Metod B: Använda Drive.Files.insert och en fortsättningstoken
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());
}
}
Metod C: Använda Drive.Files.copy
Beröm för denna funktion går till Tanaike -- se hans svar nedan för mer information.
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)
}
}
Metod D: Skicka batchar av HTTP-förfrågningar
Kredit för denna funktion går till Tanaike -- se hans svar nedan för mer information.
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.
}
}
Vad som fungerade och vad som inte gjorde det
Ingen av funktionerna som använder Drive.Files.insert fungerade. Varje funktion som använder
insertför konvertering misslyckades med detta felGräns överskriden: DriveApp. (rad #, fil "Code")
(radnummer ersatt med generisk symbol). Inga ytterligare detaljer eller beskrivningar av felet kunde hittas. En anmärkningsvärd variant var en där jag använde ett kalkylblad och PDF-filerna låg i en team drive-mapp; medan alla andra metoder misslyckades direkt utan att konvertera en enda fil, konverterade denna 5 innan den misslyckades. Men när man överväger varför denna variant fungerade bättre än de andra, tror jag att det var mer en tillfällighet än någon orsak relaterad till användningen av särskilda resurser (kalkylblad, team drive, etc.)
Att använda Drive.Files.copy och batch HTTP-förfrågningar fungerade endast när källmappen var en personlig (icke-Team Drive) mapp.
Att försöka använda
copy-funktionen medan man läser från en Team Drive-mapp misslyckas med detta fel:Filen hittades inte: 1RAGxe9a_-euRpWm3ePrbaGaX5brpmGXu (rad #, fil "Code")
(radnummer ersatt med generisk symbol). Raden som refereras till är
var res = Drive.Files.copy({parents: [{id: destinationFolderId}]}, files.next().getId(), {convert: true, ocr: true});Att använda batch-HTTP-förfrågningar när man läser från en Team Drive-mapp gör ingenting - inga dokumentfiler skapas och inga fel genereras. Funktionen avslutas tyst utan att ha åstadkommit något.
Slutsats
Om du vill konvertera ett stort antal PDF-filer till Google Docs eller textfiler, använd då Drive.Files.copy eller skicka batchar av HTTP-förfrågningar och se till att PDF-filerna lagras i en personlig enhet snarare än i en Team Drive.
Speciellt tack till @tehhowch för att ha visat ett sådant stort intresse för min fråga och för att ha återkommit flera gånger för att ge feedback, och till @Tanaike för att ha tillhandahållit kod tillsammans med förklaringar som framgångsrikt löste mitt problem (med en reservation, läs ovan för detaljer).
Svar #1
Du vill konvertera från PDF-filer i mappen till Google Dokument. PDF-filerna finns i en mapp på teamdriven. Du vill importera dem som har konverterats till en mapp på din Google Drive. Om jag har förstått det rätt, vad sägs om denna metod?
För konvertering från PDF till Google-dokument kan det konvertera med inte bara Drive.Files.insert(), utan också Drive.Files.copy(). Fördelen med att använda Drive.Files.copy() är
- Även om
Drive.Files.insert()har storleksbegränsningen på 5 MB, kanDrive.Files.copy()använda mer än storleken på 5 MB. - I min miljö var processhastigheten snabbare än
Drive.Files.insert().
För denna metod skulle jag vilja föreslå följande 2 mönster.
Mönster 1 : Använda Drive API v2
I detta fall används Drive API v2 från Advanced Google Services för att konvertera filer.
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.
}
}
Mönster 2 : Använda Drive API v3
I det här fallet används Drive API v3 för att konvertera filer. Och här använde jag batchförfrågningar för denna situation. Eftersom batchförfrågningar kan använda 100 API-anrop med ett API-anrop. På detta sätt kan problemet med API-kvoten tas bort.
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.
}
}
Notera:
- Om antalet API-anrop vid 1 batchförfrågan är stort (det aktuella värdet är 100), vänligen ändra
var cycle=100. - Om Drive API v3 inte kan användas för team drive, vänligen tala om det för mig. Jag kan konvertera det till Drive API v2.
- Om teamdrev är orsaken till problemet i din situation, kan du prova detta efter att det har kopierat PDF-filer till ditt Google Drive?
Referens :
- Sammanställning av förfrågningar
Om dessa inte är användbara för dig, är jag ledsen.
Svar #2
Du kan först och främst hämta och lagra ID för alla filer i ett Google Sheet. Sedan kan du fortsätta med att bearbeta varje fil normalt genom att använda dess ID. Efter att du har bearbetat dem, markera den filen som bearbetad. Och innan du bearbetar en fil, kontrollera om den filen redan är bearbetad.
Om det finns flera filer kan du också lagra radnumret tills där du har bearbetat, nästa gång fortsätt efter det.
Skapa sedan slutligen en trigger för att köra din funktion ungefär var 10:e minut.
Genom detta kan du övervinna tidsgränsen för en enskild körning. API-begäranskvot och allt annat kommer inte att kringgås med denna metod.
