使用Google Apps Script调用Clockify API分页取数时的无限循环问题
Clockify API分页拉取数据无限循环问题排查与解决
问题背景
开发Google Apps Script从Clockify API拉取2023全年数据到Google Sheet,API单请求最多返回50条数据,实现分页逻辑后陷入无限循环,始终获取前50条数据,即便递增了page参数。
核心问题原因
你的代码中将分页参数page和pageSize放在了请求参数的顶层,但根据Clockify Detailed Reports API的规范,分页参数属于detailedFilter对象的子属性。参数位置错误导致API无法识别分页指令,始终返回第一页数据,进而触发无限循环。
修复方案
将page和pageSize移至detailedFilter对象内部,修改后的关键参数部分代码如下:
var params = { "dateRangeStart": startDate, "dateRangeEnd": endDate, "dateRangeType": "ABSOLUTE", "detailedFilter": { "sortOrder": "DESCENDING", "sortColumn": "DATE", "page": page, // 移到detailedFilter内部 "pageSize": pageSize // 移到detailedFilter内部 }, "users": { "status": "ACTIVE" } };
完整修复后的脚本:
function pullDataFromClockify() { var apiKey = "XXXX"; var workspaceId = "XXXX"; var apiUrl = "https://reports.api.clockify.me/v1/workspaces/" + workspaceId + "/reports/detailed"; var startDate = "2023-01-01T00:00:00Z"; var endDate = "2023-12-31T23:59:59Z"; var headers = { "X-Api-Key": apiKey, "Content-Type": "application/json" }; var spreadsheet = SpreadsheetApp.getActiveSpreadsheet(); var sheet = spreadsheet.getSheetByName("Clocky_Exp"); if (!sheet) { sheet = spreadsheet.insertSheet("Clocky_Exp"); } var page = 1; var pageSize = 50; var allEntries = []; // 收集所有条目,批量写入提升性能 while (true) { var params = { "dateRangeStart": startDate, "dateRangeEnd": endDate, "dateRangeType": "ABSOLUTE", "detailedFilter": { "sortOrder": "DESCENDING", "sortColumn": "DATE", "page": page, "pageSize": pageSize }, "users": { "status": "ACTIVE" } }; Logger.log("Fetching page: " + page); Logger.log(JSON.stringify(params)); var options = { "method" : "POST", "headers" : headers, "muteHttpExceptions": true, "payload": JSON.stringify(params) }; var response = UrlFetchApp.fetch(apiUrl + "?_t=" + new Date().getTime(), options); var jsonResponse = JSON.parse(response.getContentText()); // 检查HTTP状态码,处理错误 if (response.getResponseCode() !== 200) { Logger.log("API请求错误,状态码: " + response.getResponseCode()); Logger.log("错误响应: " + response.getContentText()); break; } if (!jsonResponse.timeentries || jsonResponse.timeentries.length === 0) { Logger.log("无更多数据,退出循环"); break; } var entries = jsonResponse.timeentries; // 处理当前页数据,存入allEntries for (var i = 0; i < entries.length; i++) { var start = new Date(entries[i].timeInterval.start); var end = new Date(entries[i].timeInterval.end); var duration = (end - start) / (1000 * 60 * 60); var formattedStartDate = Utilities.formatDate(start, "GMT", "yyyy.MM.dd"); allEntries.push([ entries[i].description, entries[i].taskName, entries[i].userEmail, formattedStartDate, duration ]); } // 用API返回的totalPages判断是否最后一页(更可靠) if (jsonResponse.totalPages && page >= jsonResponse.totalPages) { Logger.log("已获取最后一页,退出循环"); break; } // 备用判断:如果返回条目数小于页大小,视为最后一页 if (entries.length < pageSize) { Logger.log("返回条目数不足页大小,视为最后一页"); break; } page++; Logger.log("准备获取下一页: " + page); Utilities.sleep(5000); } // 批量写入数据到Sheet,大幅提升性能 if (allEntries.length > 0) { var nextRow = sheet.getLastRow() + 1; sheet.getRange(nextRow, 1, allEntries.length, allEntries[0].length).setValues(allEntries); } }
调试与优化建议
- 验证参数结构:打印请求的
payload确认page和pageSize在detailedFilter内,可直接查看Logger.log(JSON.stringify(params))的输出 - 利用API分页元数据:Clockify API响应会返回
totalPages和totalEntries字段,用page >= totalPages判断循环终止,比仅靠条目数更可靠 - 优化Sheet写入:避免循环调用
appendRow,改用批量setValues,减少脚本执行时间和Google服务调用次数 - 增强错误处理:检查API响应状态码,对429(速率限制)可实现指数退避重试,对其他错误及时终止并记录日志
- 日志细化:记录响应中的
totalPages、page等信息,方便跟踪分页进度
内容的提问来源于stack exchange,提问作者Levi
相关产品推荐
相关产品推荐

