在 VBA 中为 API 分页创建循环

Creating a Loop for API Pagination in VBA

提问人:Spark 提问时间:11/11/2023 最后编辑:Spark 更新时间:11/11/2023 访问量:87

问:

我正在开发一个 VBA 脚本,用于与 Excel 中的 Clockify API 进行交互。在键中,我注意到静态值被用于页面参数,将 API 响应限制为最新的 1000 条记录。detailedFilter

为了解决此限制,我想创建一个循环,该循环以迭代方式调用 API,根据给定日期范围内的记录总数 () 调整页面参数。例如,如果是 3250,我需要使用页面值 1、2 和 3 进行 API 调用。entriesCountentriesCount

API 响应包含一个名为该字段的字段,我可以使用该字段来计算记录总数。entriesCount

下面是当前代码的片段:

Public Sub Get2223()
    
    Set httpCaller = New MSXML2.XMLHTTP60
    
    body = "{""dateRangeStart"": ""2022-06-01T00:00:00.000"", " & vbLf & _
           " ""dateRangeEnd"": ""2023-05-30T23:59:59.000"", " & vbLf & _
           " ""detailedFilter"": {""page"": 1,""pageSize"": 1000}} "
    
    httpCaller.Open "POST", "https://reports.api.clockify.me/v1/workspaces/KEY/reports/detailed"
    httpCaller.setRequestHeader "X-Api-Key", "API_KEY"
    httpCaller.setRequestHeader "Content-Type", "application/json"
    httpCaller.send body
    
    Dim json        As Object, t As Object
    Dim Data, i     As Long, N As Long
    Data = httpCaller.responseText
    
    Set json = JsonConverter.ParseJson(Data)
    N = json("timeentries").Count
    If N < 1 Then
        MsgBox "No timeentries in JSON", vbCritical
        Exit Sub
    End If
    
    Dim dataArray() As Variant
    ReDim dataArray(1 To N, 1 To 6)
    
    i = 1
    For Each t In json("timeentries")
        dataArray(i, 1) = t("projectName")
        If Not IsNull(t("taskName")) Then
            dataArray(i, 2) = t("taskName")
        End If
        dataArray(i, 3) = t("description")
        dataArray(i, 4) = t("clientName")
        dataArray(i, 5) = t("timeInterval")("start")
        dataArray(i, 6) = t("timeInterval")("duration")
        i = i + 1
    Next
    Dim ws          As Worksheet
    Set ws = Sheets("Year2022")
    
    Dim col: col = Array(1, 5, 9, 10, 11, 7)
    For i = 0 To UBound(col)
        ws.Cells(2, col(i)).Resize(N) = WorksheetFunction.Index(dataArray, 0, i + 1)
    Next
    
End Sub

有人可以帮我创建一个循环来处理这种情况下的分页吗?我感谢任何指导或建议。

我尝试了多种方法,但没有成功

 Dim httpCaller As MSXML2.XMLHTTP60, body As String
Set httpCaller = New MSXML2.XMLHTTP60
    
    ' Set your date range and initial page size
    Dim startDate As String
    Dim endDate As String
    Dim pageSize As Long

    startDate = "2022-06-01T00:00:00.000"
    endDate = "2023-05-30T23:59:59.000"
    pageSize = 1000

    body = "{""dateRangeStart"": """ & startDate & """, " & vbLf & _
           """dateRangeEnd"": """ & endDate & """, " & vbLf & _
           """detailedFilter"": {""page"": 1, ""pageSize"": " & pageSize & "}} "

    ' Parse JSON response
    Dim json As Object
    Dim Data
    Data = ""

    httpCaller.Open "POST", "https://reports.api.clockify.me/v1/workspaces/KEY/reports/detailed"
    httpCaller.setRequestHeader "X-Api-Key", "API_KEY"
    httpCaller.setRequestHeader "Content-Type", "application/json"

    ' Send the request
    httpCaller.send body

    ' Wait for the response to complete
    Do While httpCaller.readyState <> 4
        DoEvents
    Loop


    If httpCaller.Status = 200 Then

        Data = httpCaller.responseText
 
        Set json = JsonConverter.ParseJson(Data)

        Dim totalPages As Long
        totalPages = Application.WorksheetFunction.Ceiling(json("totals")("entriesCount") / pageSize, 1)

        ' Loop through additional pages
        Dim currentPage As Long
        For currentPage = 2 To totalPages
            ' Adjust the API call with the current page value
            body = Replace(body, """page"": 1", """page"": " & currentPage)

            httpCaller.send body
            Do While httpCaller.readyState <> 4
                DoEvents
            Loop

            If httpCaller.Status = 200 Then
        
                Data = httpCaller.responseText
              
                Set json = JsonConverter.ParseJson(Data)

            Else
                MsgBox "Error: " & httpCaller.Status & " - " & httpCaller.statusText, vbCritical
                Exit Sub
            End If
        Next currentPage
    Else
        MsgBox "Error: " & httpCaller.Status & " - " & httpCaller.statusText, vbCritical
    End If
Excel VBA 时钟

评论

0赞 Tim Williams 11/11/2023
json("totals")("entriesCount")应该告诉您总共有多少个条目,因此您需要向 etc 发出额外的请求,直到您获取所有条目。或者,如果 pageSize 尚未达到其最大值,则增加 pageSize(编辑:看起来它的最大值为 1000,因此您需要分页)。page:2
0赞 Spark 11/11/2023
嘿!谢谢你@Tim威廉姆斯伸出援手,我真的不知道,但它对我不起作用,从那时起我就在尝试,但仍然无法摆脱。如果您能提供帮助,我将不胜感激,并抱歉回复您的短信迟到了。
0赞 Spark 11/11/2023
更新了我的新尝试,但仍然没有成功@Tim威廉姆斯
1赞 Tim Williams 11/11/2023
“no success”、“not working”和“unsuccessful”对代码运行时发生的情况的描述不是很有用。请尝试提供有关事情如何不起作用的更多详细信息。
0赞 Spark 11/11/2023
所以是的,你是对的。所以在启动代码时,在这一行指出run time error - Invalid procedure call or argumenttotalPages = Application.WorksheetFunction.Ceiling(json("totals")("entriesCount") / pageSize, 1)Ceiling Function

答:

1赞 artodoro 11/11/2023 #1

我没有足够的声誉来发表评论,所以我会尝试立即正确回答:)

在第一个请求中,您发送页面为 1 且页面大小为 1000 的选项。

body = "{""dateRangeStart"": ""2022-06-01T00:00:00.000"", " & vbLf & _
           " ""dateRangeEnd"": ""2023-05-30T23:59:59.000"", " & vbLf & _
           " ""detailedFilter"": {""page"": 1,""pageSize"": 1000}} "

如果我理解正确,您需要在循环中更改页码并每次发送新的 POST 请求并接收 responseText(并将其解析为 JSON),如下所示:

' here is your code to get the number of pages
    Dim PageNumber As Long
    For PageNumber = 1 To PageNumbers
        body = "{""dateRangeStart"": ""2022-06-01T00:00:00.000"", " & vbLf & _
               " ""dateRangeEnd"": ""2023-05-30T23:59:59.000"", " & vbLf & _
               " ""detailedFilter"": {""page"": {PageNumber},""pageSize"": 1000}} "
        body = Replace(body, "{PageNumber}", PageNumber)
    
        httpCaller.Open "POST", "https://reports.api.clockify.me/v1/workspaces/KEY/reports/detailed"
        httpCaller.setRequestHeader "X-Api-Key", "API_KEY"
        httpCaller.setRequestHeader "Content-Type", "application/json"
        httpCaller.send body
    
        ' do smth...
    Next

评论

0赞 Spark 11/11/2023
它仍然无法正常工作@artodoro
2赞 Tim Williams 11/11/2023 #2

也许是这样的东西。我不能测试,所以不能花太多时间在上面。

Option Explicit

Const WKSPACE_KEY As String = "keygoeshere"
Const API_KEY As String = "xxxxxxxxxxxxxxxxxxx"

Public Sub Get2223()
    Const PER_PAGE As Long = 1000
    
    Dim result As Object, dStart As String, dEnd As String, pgNum As Long, totResults As Long
    Dim entries As Object, numPages As Long
    
    dStart = "2022-06-01T00:00:00.000"
    dEnd = "2023-05-30T23:59:59.000"
    pgNum = 1
    
    Do
        Set result = ReportsDetailed(dStart, dEnd, pgNum, PER_PAGE)
        If result Is Nothing Then Exit Sub 'got no response
        
        If pgNum = 1 Then
            totResults = CLng(result("totals")("entriesCount"))
            numPages = Application.Ceiling(totResults / PER_PAGE, 1)
        End If
        
        Set entries = result("timeentries")
        'process entries
    
        pgNum = pgNum + 1
        If pgNum > numPages Then Exit Do
    Loop

End Sub

Function ReportsDetailed(dStart As String, dEnd As String, pageNum As Long, perPage As Long) As Object
    Dim httpCaller As Object, body As String
    
    Set httpCaller = New MSXML2.XMLHTTP60
    
    body = "{""dateRangeStart"": """ & dStart & """, " & vbLf & _
           " ""dateRangeEnd"": """ & dEnd & """, " & vbLf & _
           " ""detailedFilter"": {""page"": " & pageNum & ",""pageSize"": " & perPage & "}} "
    
    httpCaller.Open "POST", "https://reports.api.clockify.me/v1/workspaces/" & _
                             WKSPACE_KEY & "/reports/detailed"
    httpCaller.setRequestHeader "X-Api-Key", API_KEY
    httpCaller.setRequestHeader "Content-Type", "application/json"
    httpCaller.send body
    If httpCaller.Status = 200 Then
        Set ReportsDetailed = JsonConverter.ParseJson(httpCaller.responseText)
    Else
        MsgBox "Error in ReportsDetailed: " & httpCaller.Status & " - " & httpCaller.StatusText, vbCritical
    End If
End Function

评论

0赞 Spark 11/14/2023
感谢您发布答案,但我在同一行上遇到了我无法修复的相同错误,我不是可以像您一样修复该代码的专家。Run time error 5 - Invalid procedure or call argument
0赞 Tim Williams 11/14/2023
失败时的价值是什么?totResults
0赞 Spark 11/14/2023
我尝试并检查了使用它的调试结果0Debug.Print totalResults
0赞 Spark 11/14/2023
totResultsvalue 是0
0赞 Tim Williams 11/14/2023
好像没有记录吗?您需要添加一些代码来处理这种情况......