我们经常发布文档更新,此页面的翻译可能仍在进行中。有关最新信息,请访问英文文档。如果此页面上的翻译有问题,请告诉我们

此版本的 GitHub Enterprise 已停止服务 March 02, 2021. 即使针对重大安全问题,也不会发布补丁。 要获得更好的性能、改进的安全性和新功能,请升级到 GitHub Enterprise 的最新版本。 如需升级方面的帮助,请联系 GitHub Enterprise 支持

使用分页遍历

通过一些使用搜索 API 的示例,探讨如何使用分页来管理响应。

本文内容

GitHub Enterprise Server API 为开发人员提供大量的可用信息。 在很多时候,您甚至会发现自己请求的信息太多,为了满足我们的服务器,API 会自动对请求的项目进行分页

在本指南中,我们将对 GitHub Enterprise Server 搜索 API 进行一些调用,并使用分页遍历结果。 您可以在平台样本仓库中找到此项目的完整源代码。

分页基础知识

首先需要了解有关接收分页条目的一些事实:

  1. 不同的 API 调用响应具有不同的默认值。 例如,调用列出公共仓库将提供以 30 项为单位的分页结果,而调用 GitHub 搜索 API 将提供以 100 项为单位的分页结果
  2. 您可以指定要接收的条目数(最多 100 个);但是,
  3. 出于技术原因,并非每个端点的行为都相同。 例如,事件不允许设置要接收的最大条目数量。 请务必阅读关于如何处理特定端点分页结果的文档。

有关分页的信息包含在 API 调用的 Link 标头中。 例如,我们向搜索 API 发出一个 curl 请求,以查明 Mozilla 项目使用短语 addClass 的次数:

$ curl -I "http(s)://[hostname]/api/v3/search/code?q=addClass+user:mozilla"

-I 参数表示我们只关注标头,而不关注实际内容。 在检查结果时,您会注意到 Link 标头中的一些信息,如下所示:

链接:<https://api.github.com/search/code?q=addClass+user%3Amozilla&page=2>; rel="next",
  <https://api.github.com/search/code?q=addClass+user%3Amozilla&page=34>; rel="last"

我们来分解说明。 rel="next" 表示下一页是 page=2。 这是合理的,因为在默认情况下,所有分页查询都是从第 1 页开始。rel="last" 提供了更多信息,指示结果的最后一页是第 34 页。 因此,我们还有 33 页有关 addClass 的信息可用。 不错!

始终信赖提供给您的这些链接关系。 不要试图猜测或构建自己的 URL。

现在您知道要接收多少页面,可以开始浏览页面以使用结果。 您可以通过传递 page 参数进行浏览。 默认情况下,page 总是从 1 开始。 让我们跳到第 14 页,看看会发生什么:

$ curl -I "http(s)://[hostname]/api/v3/search/code?q=addClass+user:mozilla&page=14"

以下是再次出现的 Link 标头:

链接:<https://api.github.com/search/code?q=addClass+user%3Amozilla&page=15>; rel="next",
  <https://api.github.com/search/code?q=addClass+user%3Amozilla&page=34>; rel="last",
  <https://api.github.com/search/code?q=addClass+user%3Amozilla&page=1>; rel="first",
  <https://api.github.com/search/code?q=addClass+user%3Amozilla&page=13>; rel="prev"

果然,rel="next" 是 15,而 rel="last" 仍是 34。 但是现在我们得到了更多信息:rel="first" 表示第一页的 URL,更重要的是,rel="prev" 让您知道了上一页的页码。 根据这些信息,您可以构造一些 UI,使用户可以在 API 调用结果列表的第一页、上一页、下一页或最后一页之间跳转。

更改接收的条目数量

通过传递 per_page 参数,您可以指定希望每页返回多少个条目,最多 100 个。 我们来尝试请求 50 个关于 addClass 的条目:

$ curl -I "http(s)://[hostname]/api/v3/search/code?q=addClass+user:mozilla&per_page=50"

请注意它对标头响应的影响:

链接:<https://api.github.com/search/code?q=addClass+user%3Amozilla&per_page=50&page=2>; rel="next",
  <https://api.github.com/search/code?q=addClass+user%3Amozilla&per_page=50&page=20>; rel="last"

您可能已经猜到了,rel="last" 信息表明最后一页现在是第 20 页。 这是因为我们要求每页提供更多的结果相关信息。

使用信息

您不希望仅仅为了能够处理分页而进行低级的 curl 调用,所以我们来编写一个 Ruby 小脚本来完成上述所有任务。

与往常一样,首选我们需要使用 GitHub 的 Octokit.rb Ruby 库,并传递我们的 个人访问令牌

require 'octokit'

# !!! DO NOT EVER USE HARD-CODED VALUES IN A REAL APP !!!
# Instead, set and test environment variables, like below
client = Octokit::Client.new :access_token => ENV['MY_PERSONAL_TOKEN']

接下来,我们将使用 Octokit 的 search_code 方法执行搜索。 与使用 curl 不同的是,我们还可以立即检索结果数量,所以我们输入以下代码:

results = client.search_code('addClass user:mozilla')
total_count = results.total_count

现在,我们来获取最后一页的页码 - 类似于链接标头中的 page=34>; rel="last" 信息。 Octokit.rb 通过“超媒体连接关系”的实现来支持分页信息。 我们不会对这种关系展开详细讨论,只想提醒您,results 变量中的每个元素都有一个被称为 rels 的哈希,它可能包含有关 :next:last:first:prev 的信息,具体取决于您所得结果。 这些关系还包含通过调用 rels[:last].href 所得 URL 的相关信息。

知道了这一点后,我们来获取最后一个结果的页码,并将所有这些信息呈现给用户:

last_response = client.last_response
number_of_pages = last_response.rels[:last].href.match(/page=(\d+).*$/)[1]

puts "There are #{total_count} results, on #{number_of_pages} pages!"

最后,我们来遍历结果。 您可以使用 for i in 1..number_of_pages.to_i 循环进行浏览,但我们这次将根据 rels[:next] 标头来检索每一页的信息。 为简单起见,我们只获取每个页面中第一个结果的文件路径。 为此,我们需要一个循环;在每个循环的最后,我们根据 rels[:next] 信息来检索下一页的数据集。 当没有 rels[:next] 信息可用时(也就是说,我们到达了 rels[:last]),循环将结束。 如下所示:

puts last_response.data.items.first.path
until last_response.rels[:next].nil?
  last_response = last_response.rels[:next].get
  puts last_response.data.items.first.path
end

使用 Octokit.rb 更改每页的条目数非常简单。 只需将 per_page 选项哈希传递给初始客户端构造函数。 之后,您的代码应保持不变:

require 'octokit'

# !!! DO NOT EVER USE HARD-CODED VALUES IN A REAL APP !!!
# Instead, set and test environment variables, like below
client = Octokit::Client.new :access_token => ENV['MY_PERSONAL_TOKEN']

results = client.search_code('addClass user:mozilla', :per_page => 100)
total_count = results.total_count

last_response = client.last_response
number_of_pages = last_response.rels[:last].href.match(/page=(\d+).*$/)[1]

puts last_response.rels[:last].href
puts "There are #{total_count} results, on #{number_of_pages} pages!"

puts "And here's the first path for every set"

puts last_response.data.items.first.path
until last_response.rels[:next].nil?
  last_response = last_response.rels[:next].get
  puts last_response.data.items.first.path
end

一般来说,使用分页时,您的目标不是要抓住所有可能的结果,而是要产生一组导航,如下所示:

分页链接示例

让我们勾勒出一个可能需要做什么的微观版本。

根据上面的代码,我们已经知道可以从第一次调用的分页结果中获取 number_of_pages

require 'octokit'

# !!! DO NOT EVER USE HARD-CODED VALUES IN A REAL APP !!!
# Instead, set and test environment variables, like below
client = Octokit::Client.new :access_token => ENV['MY_PERSONAL_TOKEN']

results = client.search_code('addClass user:mozilla')
total_count = results.total_count

last_response = client.last_response
number_of_pages = last_response.rels[:last].href.match(/page=(\d+).*$/)[1]

puts last_response.rels[:last].href
puts "There are #{total_count} results, on #{number_of_pages} pages!"

我们可以从其中构造美观的数字框 ASCII 表示形式:

numbers = ""
for i in 1..number_of_pages.to_i
  numbers << "[#{i}] "
end
puts numbers

我们通过构造一个随机数来模拟用户单击其中一个框:

random_page = Random.new
random_page = random_page.rand(1..number_of_pages.to_i)

puts "A User appeared, and clicked number #{random_page}!"

现在我们有了页码,可通过传递 :page 选项,使用 Octokit 明确检索各个页面:

clicked_results = client.search_code('addClass user:mozilla', :page => random_page)

如果我们想弄得花哨一些,还可以抓住上下页,以生成后退 (<<) 和前进 (>>) 元素的链接:

prev_page_href = client.last_response.rels[:prev] ? client.last_response.rels[:prev].href : "(none)"
next_page_href = client.last_response.rels[:next] ? client.last_response.rels[:next].href : "(none)"

puts "The prev page link is #{prev_page_href}"
puts "The next page link is #{next_page_href}"