13.3.3 获取指标
获取有关国家或地区的数据,需要使用世界银行服务的不同函数,函数的路径是 /countries/indicators,可以在 Data Calls 选项卡的 Query Generator(查询生成器)中找到,它能够请求指定国家在特定时间段的有关指标数据。我们不必单独下载每个感兴趣的地区数据,可以一次性地获得所有国家的信息,在内存中进行处理。以这种方式可以下载更多的数据,而请求的数量较少,因为我们不必为每个地区创建请求。
我们还遵循以前的模式,首先下载部分示例数据,然后,使用我们自己的 XML 查询函数进行验证。清单 13.11 显示了如何下载国家的森林覆盖率指标,这项指标的键值为 AG.LND.FRST.ZS,要得到这个值,最好的办法是在查询生成器中模拟查询。我们将下载 1990 年的数据,并请求数据集的第一页。
清单 13.11 获得地区的森林覆盖率 (交互式 F#)
> let ind = "AG.LND.FRST.ZS" | 指定 1990 年地区
let date = "1990:1990" | 森林数据的第一页
let page = 1 |
let props =
[ "countries","indicators"; ind ]
[ "date",date; "page",string(page) ];;
(...)
> let doc = Async.RunSynchronously(worldBankRequest(props)) | [1] 获得数据,
printfn "%s..." (doc.ToString().Substring(0,301));; | 输出
val doc : XDocument
<wb:data xmlns:wb="http://www.worldbank.org"
page="1" pages="3" per_page="100" total="231">
<wb:data>
<wb:indicator id="AG.LND.FRST.ZS">Forest area (% ...</wb:indicator>
<wb:country id="AW">Aruba</wb:country>
<wb:value></wb:value>
<wb:date>1990</wb:date>
</wb:data>...
> doc |> xnested [ "data" ] | 读页数
|> xattr "pages" |> int;; |
val it : int = 3
> doc |> xnested [ "data"; "data"; "country" ] | 读第一个国家的 ID
|> xvalue;; |
val it : string = "Aruba"
清单 13.11 首先定义了一组属性,这是为了创建请求所需要的;然后,用这些属性创建一个列表,这是 worldBankRequest 函数的参数。文档下载之后,我们要浏览一下结构,因此,要转换成字符串,输出前面几行[1]。输出的结果表明,整个数据集有三页;每个国家的信息嵌套在 data 元素中,包含国家的名字和 ID,以及日期(原文 data 有误,应该是 date)信息和实际值。第一个国家没有这个值,所以,在解析数据时,必须要仔细处理这种情况。
接下来两个表达式很简单。首先,需要知道页数[2],这样,才能下载所有数据;第二个表达式读第一个国家名[3],在后面会需要,我们希望用它匹配上一节收集的地区名。
熟悉了数据结构之后,就可以写函数,下载所需的内容了。清单 13.12 是一个异步的工作流,循环运行直至获得所有页面。我们没有并行下载页面,因为这稍微有点难;但是,对于不同指标和不同年份,我们在函数中实现了并行,因此,最终也会有足够的并行度。
清单13.12 异步下载所有指标 (F#)
let rec getIndicatorData(date,indicator,page) = async {
let args = [ "countries"; "indicators"; ind ],[ "date",date; "page",string(page)]
let! doc = worldBankRequest args
let pages =
doc |> xnested [ "data" ] | [1] 得到页数
|> xattr "pages" |> int |
if (pages = page) then
return [doc] <-- [2] 返回最后的页面
else
let page = page + 1
let! rest = getIndicatorData(date,page) <-- [3] 下载其余的页面
return doc::rest }
函数(getIndicatorData)有三个参数,日期、指标以及所需的页数,用它们来生成参列表, 作为 worldBankRequest 函数的参数。得到 XML 以后,我们读出数据集总页数的特性[1];如果当前处理的是最后一页,就返回包含当前页的列表,其中只有一个元素 [2];否则,继续下载其余页面。注意,函数是用 let rec 声明的,所以,可以递归地调用这个函数,获得剩余页面[3]。因为是在异步工作流的内部,所以使用了 let!。得到了其余页面的列表以后,就可以把下载的页面合并起来,结果就返回所有的页面。
在进一步操作之前,我们应该用交互式 F# 来验证函数能否正确运行。用指标为 AG.LND.FRST.ZS,年度范围 1990:1990,页数为 1 生成请求,使用 Async.RunSynchronously 运行工作流,应该得到包含所有国家和地区数据的三个页面。
现在,我们要介绍一下并行度(parallelism),下载我们感兴趣的所有年度的所有指标。我们使用 Async.Parallel 基本操作,因此,需要创建异步工作流的序列。清单 13.13 中的代码通过使用简单的序列表达式,调用 getIndicatorData 函数,组合所有参数来实现。别忘了,调用 getIndicatorData 并不执行读取,它只返回能够进行读取的工作流。
清单13.13 并行下载多年的多指标 (F#)
let downloadAll = seq {
for ind in [ "AG.SRF.TOTL.K2"; "AG.LND.FRST.ZS" ] do | [1]
for year in [ "1990:1990"; "2000:2000"; "2005:2005" ] do |
yield getIndicatorData(year,ind,1) }
let data = Async.RunSynchronously(Async.Parallel(downloadAll)) [2]
脚本首先为我们感兴趣的指标和年度的组合生成工作流[1],然后,再把所有的工作流组合成并行运行的一个工作流,再同步运行,下载所有数据[2]。
序列表达式首先迭代两个指标,第一个指标表示国家或地区总面积,以平方公里计,第二个指标是森林覆盖的百分比,在前面我们已经看到过。如果在世界银行的网站上浏览数据的话,可以看到,森林覆盖指标只提供了三个不同年份,因此,嵌套循环只遍历这些年份。对于这些参数的每种组合,我们都创建(产生)一个工作流,开始运行下载第一页。
这样,我们会得到总共六个任务,其中每一个任务都可能下载多个页面;我们把所有任务组合成一个工作流,使用 Async.RunSynchronously 运行组合的工作流,返回包含这六个结果的数组。下载要花一些时间,正如我们前面讨论的,某些请求可能会失败,然后重新启动。我们得到的结果 data 值,类型是 array<list<XDocument>>
数组,返回指标-年每种组合的页面列表。 因为我们使用的是 F# 脚本,所以不用考虑把设置,比如年和指标,放到配置文件中。此刻,我们写代码只有一个目的,(就是进行验证),以后可以进行修改,使它变得更通用,在今后的开发中会用到。得到了所需要的数据以后,我们就要用它来做一些有用的事情。