此多部分教程介绍如何为 Power Query 创建新的数据源扩展。 本教程旨在按顺序完成 — 每个课程都基于在上一课中创建的连接器上构建,以增量方式向连接器添加新功能。
在本课中,你将:
- 向连接器添加分页支持
许多 Rest API 在“页面”中返回数据,要求客户端发出多个请求来将结果拼凑在一起。 尽管在分页方面(如 RFC 5988)存在一些常见约定,但通常会在各个 API 之间有所不同。 值得庆幸的是,TripPin 是 OData 服务,OData 标准 定义了使用响应正文中返回的 odata.nextLink 值执行分页的方法。
为了简化 连接器的先前迭代 ,该 TripPin.Feed 函数无法 识别页面。 它只是分析从请求返回的任何 JSON,并将其格式化为表。 如果你熟悉 OData 协议,你可能会注意到,对 响应的格式 进行了许多不正确的假设(例如假设有一个 value 字段包含记录数组)。
在本课中,您将通过使响应处理逻辑具有页面识别能力来改进其处理能力。 将来的教程使页面处理逻辑更加可靠,并且能够处理多种响应格式(包括来自服务的错误)。
注释
无需使用基于 OData.Feed 的连接器实现自己的分页逻辑,因为它会为您自动处理所有这些工作。
分页清单
在实现分页支持时,您需要了解以下与 API 相关的重要信息:
- 如何请求下一页数据?
- 分页机制是否涉及计算值,还是从响应中提取下一页的 URL?
- 如何知道何时停止分页?
- 是否有与分页相关的参数(例如“页面大小”)?
对这些问题的回答会影响你实现分页逻辑的方式。 尽管在分页功能的实现中(如使用 Table.GenerateByPage)存在一些代码复用,但大多数连接器最终需要自定义逻辑。
注释
本课程包含 OData 服务的分页逻辑,该逻辑遵循特定格式。 检查您的 API 文档,以确定如何更改连接器以支持其分页格式。
OData 分页概述
OData 分页由响应有效负载中包含的 nextLink 批注 驱动。 nextLink 值包含指向下一页数据的 URL。 若要确定是否有另一页数据,请查找响应的最外层对象中的 odata.nextLink 字段。 如果没有odata.nextLink字段,则读取所有数据。
{
"odata.context": "...",
"odata.count": 37,
"value": [
{ },
{ },
{ }
],
"odata.nextLink": "...?$skiptoken=342r89"
}
某些 OData 服务允许客户端提供 最大页面大小首选项,但由服务决定是否遵循它。 Power Query 应该能够处理任何大小的响应,因此无需担心指定页大小偏好,你可以支持服务可能返回的任何内容。
有关 服务器驱动分页 的更多信息,请参阅 OData 规范。
测试 TripPin
在修复分页实现之前,请确认 上一教程中扩展的当前行为。 以下测试查询检索 People 表并添加索引列以显示当前行计数。
let
source = TripPin.Contents(),
data = source{[Name="People"]}[Data],
withRowCount = Table.AddIndexColumn(data, "Index")
in
withRowCount
打开 Fiddler,并在 Power Query SDK 中运行查询。 该查询返回一个表,其中包含八行(索引 0 到 7)。
如果你查看 fiddler 的响应正文,它实际上包含一个 @odata.nextLink 字段,表明有更多的可用数据页。
{
"@odata.context": "https://services.odata.org/V4/TripPinService/$metadata#People",
"@odata.nextLink": "https://services.odata.org/v4/TripPinService/People?%24skiptoken=8",
"value": [
{ },
{ },
{ }
]
}
为 TripPin 实现分页
现在对扩展进行以下更改:
- 导入通用
Table.GenerateByPage函数。 - 添加使用
Table.GenerateByPage来将所有页面结合在一起的GetAllPagesByNextLink函数。 - 添加一个可以读取单个数据页的
GetPage函数。 - 添加一个
GetNextLink函数以从响应中提取下一个 URL。 - 更新
TripPin.Feed以使用新的页面读取器函数。
注释
如本教程前面所述,分页逻辑因数据源而异。 此处的实现尝试将逻辑分解为函数,这些函数应该可供使用响应中返回的下一个链接的源重复使用。
Table.GenerateByPage
若要将源返回的多个页面合并到单个表中,请使用 Table.GenerateByPage。 此函数接受一个函数作为参数,getNextPage该函数应执行其名称所暗示的操作:提取下一页数据。
Table.GenerateByPage 重复调用该 getNextPage 函数,每次传递结果时都会生成上次调用它的结果,直到它返回 null 信号,表明没有更多的页面可用。
由于此函数不是 Power Query 标准库的一部分,因此需要将其 源代码 复制到 .pq 文件中。
实现 GetAllPagesByNextLink
函数GetAllPagesByNextLink的主体为Table.GenerateByPage实现getNextPage函数参数。 它调用函数GetPage,并从上一次调用的NextLink记录字段中检索下一页数据的 meta URL。
// Read all pages of data.
// After every page, we check the "NextLink" record on the metadata of the previous request.
// Table.GenerateByPage will keep asking for more pages until we return null.
GetAllPagesByNextLink = (url as text) as table =>
Table.GenerateByPage((previous) =>
let
// if previous is null, then this is our first page of data
nextLink = if (previous = null) then url else Value.Metadata(previous)[NextLink]?,
// if NextLink was set to null by the previous call, we know we have no more data
page = if (nextLink <> null) then GetPage(nextLink) else null
in
page
);
实现 GetPage
函数 GetPage 使用 Web.Contents 从 TripPin 服务检索单个数据页,并将响应转换为表。 它将 来自 Web.Contents 的响应传递给 GetNextLink 函数,以提取下一页的 URL,并在返回的表格(数据页)记录的 meta 项中设置该 URL。
此实现是上一教程调用 TripPin.Feed 的略有修改版本。
GetPage = (url as text) as table =>
let
response = Web.Contents(url, [ Headers = DefaultRequestHeaders ]),
body = Json.Document(response),
nextLink = GetNextLink(body),
data = Table.FromRecords(body[value])
in
data meta [NextLink = nextLink];
实现 GetNextLink
你的 GetNextLink 函数只需检查响应正文中是否包含字段 @odata.nextLink,并返回其值。
// In this implementation, 'response' is the parsed body of the response after the call to Json.Document.
// Look for the '@odata.nextLink' field and simply return null if it doesn't exist.
GetNextLink = (response) as nullable text => Record.FieldOrDefault(response, "@odata.nextLink");
汇总
实现分页逻辑的最后一步是更新 TripPin.Feed 以使用新函数。 现在,你只是简单地调用GetAllPagesByNextLink,但在后续的教程中,你将添加新的功能(例如强制实施模式和查询参数逻辑)。
TripPin.Feed = (url as text) as table => GetAllPagesByNextLink(url);
如果从本教程的前面部分重新运行相同的 测试查询,现在应该会看到页面阅读器正在运行。 还应看到响应中有 24 行,而不是 8 行。
如果查看 fiddler 中的请求,现在应会看到针对每个数据页面的独立请求。
注释
你可能会注意到服务返回的第一页数据中有重复的请求,这样的情况是不理想的。 额外的请求是 M 引擎的架构检查行为的结果。 暂时忽略此问题,并在 下一教程中解决此问题,在此教程中应用显式架构。
结论
本课程介绍了如何实现对 Rest API 的分页支持。 虽然逻辑在 API 之间可能有所不同,但此处建立的模式应可重复使用,但需进行细微修改。
在下一课中,你将学习如何对数据应用显式架构,超越从 Json.Document 中得到的简单 text 和 number 数据类型。