获取有关国家或地区的数据,需要使用世界银行服务的不同函数,函数的路径是 /countries/indicators,可以在 Data Calls 选项卡的 Query Generator(查询生成器)中找到,它能够请求指定国家特定时间段内的有关指标数据。我们不必单独下载每个感兴趣的地区数据,可以一次性地获得所有国家的信息,在内存中进行处理。以这种方式可以下载更多的数据,而请求的数量较少,因为我们不必为每个地区创建请求。
我们按照与以前相同的模式,首先下载部分示例数据,然后,使用我们自己的 XML 查询函数进行验证。清单 13.11 显示了如何下载国家的森林覆盖率指标,这项指标的键值为 AG.LND.FRST.ZS,要得到这个值,最好的办法是在查询生成器中模拟查询。我们将下载 1990 年的数据,并请求数据集的第一页。
清单 13.11 获得地区的森林覆盖率 (F# Interactive)
> 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,以及日期(感觉应该是 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, indicator, page) <-- [3] 下载其余的页面
return doc::rest }
函数有三个参数,日期、指标以及所需的页数,用它们来生成 worldBankRequest 函数参数值列表。当收到 XML 时,我们读出数据集总页数的属性[1]。如果当前处理的是最后一页,我们就会返回当前页,包含在只有一个元素的列表中[2];否则,就下载其余的页面。注意,函数是用 let rec 声明的,所以,可以递归地调用,以获得剩余页面[3]。还使用了 let!,是因为在异步工作流的内部。得到了其余页面的列表以后,我们就把刚刚下载的页面返回进去,结果就返回所有的页面。
在继续之前,我们可以使用 F# Interactive 来验证函数是否正确运行。生成一个要求,指标为 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],然后,再把所有的工作流组合成并行运行的一个工作流,同步运行,下载所有数据。
序列表达式首先迭代两个指标。第一个指标表示国家或地区总面积,以平方公里计,第二个指标是森林覆盖的百分比,我们已经看到过。如果在世界银行的网站上浏览数据的话,可以看到,森林覆盖指标只提供了三个不同年份,因此,嵌套循环只遍历这些年份。对于这些参数的每个组合,我们都创建(产生)一个工作流,第一页开始运行下载。
这就是说,我们会得到总共六个任务,其中每一个都可能下载多个页面;我们将这些任务组合成一个工作流,返回一个包含这六个结果的数组,使用 Async.RunSynchronously 运行组合的工作流。下载要会花一些时间,某些请求可能会失败,然后重新启动,正如我们前面讨论的。我们得到的结果 data 值,类型是 array