如何在 C# HttpClient 中循环调用分页 URL 以从 JSON 结果中下载所有页面

Posted

技术标签:

【中文标题】如何在 C# HttpClient 中循环调用分页 URL 以从 JSON 结果中下载所有页面【英文标题】:How to Loop calls to Pagination URL in C# HttpClient to download all Pages from JSON results 【发布时间】:2017-04-17 05:24:33 【问题描述】:

我的第一个问题,请善待... :)

我正在使用 C# HttpClient 来调用 Jobs API Endpoint。

这里是端点:Jobs API Endpoint (doesn't require key, you can click it)

这给了我这样的 JSON。


  "count": 1117,
  "firstDocument": 1,
  "lastDocument": 50,
  "nextUrl": "\/api\/rest\/jobsearch\/v1\/simple.json?areacode=&country=&state=&skill=ruby&city=&text=&ip=&diceid=&page=2",
  "resultItemList": [
    
      "detailUrl": "http:\/\/www.dice.com\/job\/result\/90887031\/918715?src=19",
      "jobTitle": "Sr Security Engineer",
      "company": "Accelon Inc",
      "location": "San Francisco, CA",
      "date": "2017-03-30"
    ,
    
      "detailUrl": "http:\/\/www.dice.com\/job\/result\/cybercod\/BB7-13647094?src=19",
      "jobTitle": "Platform Engineer - Ruby on Rails, AWS",
      "company": "CyberCoders",
      "location": "New York, NY",
      "date": "2017-04-16"
    
 ]

我已经粘贴了一个完整的 JSON sn-p,所以你可以在你的答案中使用它。完整的结果在这里真的很长。

这里是 C# 类。

using Newtonsoft.Json;
using System.Collections.Generic;

namespace MyNameSpace

    public class DiceApiJobWrapper
    
        public int count  get; set; 
        public int firstDocument  get; set; 
        public int lastDocument  get; set; 
        public string nextUrl  get; set; 

        [JsonProperty("resultItemList")]
        public List<DiceApiJob> DiceApiJobs  get; set; 
    

    public class DiceApiJob
    
        public string detailUrl  get; set; 
        public string jobTitle  get; set; 
        public string company  get; set; 
        public string location  get; set; 
        public string date  get; set; 
    

当我使用 HttpClient 调用 URL 并使用 JSON.NET 反序列化时,我确实可以正确取回数据。

这是我从控制台应用程序的Main 方法调用的代码(因此static 列表,我认为这可以更好地重构??)

   private static List<DiceApiJob> GetDiceJobs()
    
        HttpClient httpClient = new HttpClient();
        var jobs = new List<DiceApiJob>();

        var task = httpClient.GetAsync("http://service.dice.com/api/rest/jobsearch/v1/simple.json?skill=ruby")
          .ContinueWith((taskwithresponse) =>
          
              var response = taskwithresponse.Result;
              var jsonString = response.Content.ReadAsStringAsync();
              jsonString.Wait();

              var result =  JsonConvert.DeserializeObject<DiceApiJobWrapper>(jsonString.Result);
              if (result != null)
              
                  if (result.DiceApiJobs.Any())
                      jobs = result.DiceApiJobs.ToList();

                  if (result.nextUrl != null)
                  
                      //
                      // do this GetDiceJobs again in a loop? How?? Any other efficient elegant way??
                  
              
          );
        task.Wait();

        return jobs;
    

但是现在,我如何使用nextUrl 字段检查是否还有更多工作?我知道我可以检查它是否不为空,如果不为空,则意味着还有更多工作需要下拉。

Results from my debugging and stepping through

如何以递归方式执行此操作,并且不会挂起并且有一些延迟,这样我就不会超过 API 限制?我想我必须使用 TPL(任务并行库),但我很困惑。

谢谢! ~肖恩

【问题讨论】:

在有人推荐递归路由之前,不要这样做。如果您有足够的页面,您将获得堆栈溢出。我会把它放在一个循环中(while (nextUrl != null) ),然后在 while 循环之前将 nextUrl 分配给第一个,等等。 难道你不能只有一个队列列表,然后循环它并在迭代时添加/弹出。还需要跟踪您查看过的网址,以避免抓取已报废的页面。 @john 谢谢,我同意这很糟糕,我将标题改为Loop。 @Phill 我听说过 QueueList,你能详细说明一下,或者在 Answer 中提供一个工作代码。我对 TPL 和异步任务库等非常陌生。 @SeanPatel 您正在使用 DeserializeObject 将其映射到 c# 类。如果 json 中缺少任何内容,它将破坏您的代码。 @AnirudhaGupta 哦。那么推荐的方法是什么?抱歉,对这一切都很陌生。是否有不同的更安全的“防御性编码”方法? 【参考方案1】:

如果您担心应用的响应时间,并希望在实际从 API 获取所有页面/数据之前返回一些结果,您可以在循环中运行您的流程,并为其提供一个回调方法来执行它从 API 获取每一页数据。

这是一个示例:

public class Program

    public static void Main(string[] args)
    
        var jobs = GetDiceJobsAsync(Program.ResultCallBack).Result;
        Console.WriteLine($"\nAll jobs.Count jobs displayed");
        Console.ReadLine();
    

    private static async Task<List<DiceApiJob>> GetDiceJobsAsync(Action<DiceApiJobWrapper> callBack = null)
    
        var jobs = new List<DiceApiJob>();
        HttpClient httpClient = new HttpClient();
        httpClient.BaseAddress = new Uri("http://service.dice.com");
        var nextUrl = "/api/rest/jobsearch/v1/simple.json?skill=ruby";

        do
        
            await httpClient.GetAsync(nextUrl)
                .ContinueWith(async (jobSearchTask) =>
                
                    var response = await jobSearchTask;
                    if (response.IsSuccessStatusCode)
                    
                        string jsonString = await response.Content.ReadAsStringAsync();
                        var result = JsonConvert.DeserializeObject<DiceApiJobWrapper>(jsonString);
                        if (result != null)
                        
                            // Build the full list to return later after the loop.
                            if (result.DiceApiJobs.Any())
                                jobs.AddRange(result.DiceApiJobs.ToList());

                            // Run the callback method, passing the current page of data from the API.
                            if (callBack != null)
                                callBack(result);

                            // Get the URL for the next page
                            nextUrl = (result.nextUrl != null) ? result.nextUrl : string.Empty;
                        
                    
                    else
                    
                        // End loop if we get an error response.
                        nextUrl = string.Empty;
                    
                );                

         while (!string.IsNullOrEmpty(nextUrl));
        return jobs;
    


    private static void ResultCallBack(DiceApiJobWrapper jobSearchResult)
    
        if (jobSearchResult != null && jobSearchResult.count > 0)
        
            Console.WriteLine($"\nDisplaying jobs jobSearchResult.firstDocument to jobSearchResult.lastDocument");
            foreach (var job in jobSearchResult.DiceApiJobs)
            
                Console.WriteLine(job.jobTitle);
                Console.WriteLine(job.company);
            
        
    

请注意,上述示例允许回调方法访问GetDiceJobsAsync 方法接收到的每一页数据。在这种情况下,控制台会在每个页面可用时显示它。如果您不想要回调选项,您可以简单地不向GetDiceJobsAsync 传递任何内容。

GetDiceJobsAsync 在完成时也会返回所有作业。所以你可以选择对GetDiceJobsAsync末尾的整个列表进行操作。

至于达到 API 限制,您可以在循环中插入一小段延迟,就在您重复循环之前。但是当我尝试它时,我没有遇到限制我请求的 API,所以我没有将它包含在示例中。

【讨论】:

这很漂亮。工作,真的很快!谢谢弗兰克!我接受了答案,并为你投票,但由于我是新人,它说我的投票很重要,但没有出现或其他什么。希望你能投票。非常感谢!!!

以上是关于如何在 C# HttpClient 中循环调用分页 URL 以从 JSON 结果中下载所有页面的主要内容,如果未能解决你的问题,请参考以下文章

如何使用httpclient c#调用bitly v4 api来缩短url?

Fiddler 没有看到来自 C# HttpClient() 的 API 调用

如何在 C# 中包装 HttpClient 以实现可测试性

第二次调用 C# HttpClient 错误请求

C#中HttpClient使用注意:预热与长连接

Angular:如何从 httpClient 请求中获取参数并用于循环相同的请求