关于Google Apps Script中UrlFetchApp.fetchAll配额限制的技术咨询
我对Google Apps Script中UrlFetchApp.fetchAll的工作原理存在误解。正在开发一个插件用于批量复制模板并填充占位符,每个模板需替换约6个字段(后续可能增加)。少量模板处理正常,但处理20个左右时出现超时错误:
Exception: Service invoked too many times in a short time: urlfetch. Try Utilities.sleep(1000) between calls.
我的目标是扩展至处理200-300个模板,原以为fetchAll会将数组中的请求视为单次请求,这是否错误?我可以添加延时,但担心触发Google脚本运行时配额超时。想了解fetchAll的请求计数方式,以及它相较于fetch的优势。
function copyTemplates(copiedTemplateIds, destinationFolderId) { let copyRequests = [] copiedTemplateIds.forEach(id => { let title = getTitle(enteredInfo.name, DriveApp.getFileById(id).getName()) copyRequests.push({ "url": `https://www.googleapis.com/drive/v3/files/${id}/copy`, "method": "POST", "headers": { "Authorization": "Bearer " + token }, "payload": `{"parents":"${destinationFolderId}","name":"${title}"}`, "contentType": "application/json", }) }) let responses = UrlFetchApp.fetchAll(copyRequests); responses.forEach(response => { copiedTemplateIds.push(JSON.parse(response.getContentText()).id) }); return copiedTemplateIds; } function replaceText(copiedTemplateIds) { let fillRequests = [] copiedTemplateIds.forEach(id => { for (let [field, data] of Object.entries(enteredInfo)) { let fill = "<<"+ field +">>" fillRequests.push({ "url": `https://docs.googleapis.com/v1/documents/${id}:batchUpdate`, "method": "POST", "headers": { "Authorization": "Bearer " + token }, "payload": `{"requests":[{"replaceAllText":{"replaceText":"${data}","containsText":{"text":"${fill}","matchCase":false}}}]`, "contentType": "application/json", }) } }) let responses = UrlFetchApp.fetchAll(fillRequests); console.log(responses.toString()) }
fetchAll是否将数组中的所有请求视为单次请求?fetchAll的请求计数规则是什么?- 相较于
UrlFetchApp.fetch,fetchAll的优势在哪里? - 如何优化代码以处理200-300个模板而不触发配额限制?
1. fetchAll的请求计数规则
你的理解是错误的:fetchAll不会将数组中的请求视为单次请求。Google Apps Script的UrlFetch配额是按实际发起的HTTP请求数量统计的,fetchAll只是将多个请求打包批量发送,底层仍然是每个请求单独计数,和循环调用fetch的配额消耗完全一致。
2. fetchAll对比fetch的优势
虽然配额计数相同,但fetchAll有两个关键优势:
- 连接复用:底层会复用HTTP连接,减少多次建立连接的开销,整体请求速度比循环调用
fetch更快。 - 代码简洁:无需手动循环处理请求和响应,一次性完成批量请求的发送和结果接收,代码结构更清晰。
3. 优化方案(针对200-300个模板的场景)
(1)合并Docs的批量更新请求
当前代码中,每个文档的每个字段都单独发送一次batchUpdate请求,这会导致请求数爆炸(200个模板×6个字段=1200次请求)。可以将一个文档的所有替换请求合并到同一个batchUpdate中,每个文档仅发送1次请求:
function replaceText(copiedTemplateIds) { let fillRequests = [] copiedTemplateIds.forEach(id => { let requests = [] for (let [field, data] of Object.entries(enteredInfo)) { let fill = "<<"+ field +">>" requests.push({ "replaceAllText": { "replaceText": data, "containsText": { "text": fill, "matchCase": false } } }) } // 合并当前文档的所有替换请求到一个batchUpdate fillRequests.push({ "url": `https://docs.googleapis.com/v1/documents/${id}:batchUpdate`, "method": "POST", "headers": { "Authorization": "Bearer " + token }, "payload": JSON.stringify({"requests": requests}), "contentType": "application/json", }) }) let responses = UrlFetchApp.fetchAll(fillRequests); console.log(responses.toString()) }
这样请求数直接从模板数×字段数减少到模板数,大幅降低配额消耗。
(2)分段批量处理+延时
即使合并请求后,200-300次请求仍可能触发短时间内的速率限制。可以将请求分成多个小批次,每发送一批后添加延时:
function processInBatches(requests, batchSize = 50, delayMs = 2000) { let responses = [] for (let i = 0; i < requests.length; i += batchSize) { let batch = requests.slice(i, i + batchSize) responses.push(...UrlFetchApp.fetchAll(batch)) // 最后一批无需延时 if (i + batchSize < requests.length) { Utilities.sleep(delayMs) } } return responses } // 使用示例: // let responses = processInBatches(fillRequests, 50, 2000)
(3)使用Google高级服务替代手动UrlFetch
手动调用Drive/Docs API容易出错且配额紧张,推荐使用Google提供的高级服务(DriveApp、DocumentApp),这些服务的配额与UrlFetch分开,且调用更简洁:
- 复制文件:用
DriveApp.getFileById(id).copyTo(DriveApp.getFolderById(destinationFolderId), title)替代UrlFetch请求。 - 替换文本:用
DocumentApp.openById(id)打开文档,然后批量执行replaceText方法,无需发送HTTP请求。
(4)检查并利用配额限制
Google Apps Script的UrlFetch配额为每天10000次,短时间内的速率限制约为每分钟100次。合并请求+分段处理后,200-300次请求完全在配额范围内。
内容的提问来源于stack exchange,提问作者jip101

