使用开放 API 和工具快速开发情景式 mashup 应用
2009-12-24 00:00:00 来源:WEB开发网情景式应用是快速开发的满足用户特定需求的“足够好”的应用。mashup 作为快速整合数据的一种应用开发模式,能够非常快速的把与某个主题相关的信息整合在一起,以满足情景式应用的需求。情景式 mashup 应用要求能够比较快速的构建,利用开放 API 和工具就成为最佳的选择。
在 Web 2.0 的时代,每个人的热情和创造性都被释放出来,张扬自己的个性。一方面,用户不再满足于大而全的复杂应用,而是有很多个性化的需求;另外一方面,开放 API 和工具的流行,使得具备一定编程基础的普通用户也能进行应用开发。海量的个性化需求已经无法由一般的企业应用来满足,情景式应用和 mashup 就成为满足用户个性化需求的最佳工具。下面首先介绍情景式 mashup 应用的相关背景。
情景式 mashup 应用
在介绍情景式 mashup 应用之前,首先介绍一下一般意义上的情景式应用。
情景式应用
情景式应用(situational application)不同于一般的企业应用。一般的企业应用是用来解决一系列复杂的业务问题。开发企业应用需要比较长的时间周期,需要详细的项目计划,也需要较多的人力资源。而情景式应用则通常只用来满足某个特定的需求,因此开发时间较短,所需的人力也较少。通常少数几个开发人员用几天的时间就可以完成一个情景式应用。企业应用的使用和维护时间都比较长,后期的维护成本比较高;而情景式应用的生命周期较短,是用完就可以丢弃的。
与企业应用相比,情景式应用目前受到越来越多的关注,其中的原因主要有下面几个方面:
更快的满足用户的需求。情景式应用的开发周期短,可以从用户最核心的需求出发,快速的搭建出可以运行的版本,从而更早的获取用户反馈并不断改进。
“长尾”的需求。每个用户对于应用的具体需求是不同的。一般的企业应用功能较多,并且比较通用,属于“大头”应用;而情景式应用着眼于解决每个用户具体的独特的需求,属于“长尾”应用。目前越来越多的普通人投入到软件开发的行列中来,他们只需要了解不多的技术知识,就可以利用现有的 API 和工具来创建属于自己的情景式应用。
“足够好”的应用。与企业应用开发中严谨的需求分析、开发和测试的流程不同,情景式应用追求的是“足够好”的目标。也就是情景式应用只需要在大部分情况下能够工作就可以了。这使得开发人员可以更快的完成并发布自己的应用。
mashup 是一类典型的情景式应用,也是本文的主题。下面对 mashup 做简要的介绍。
mashup
mashup 一般翻译成“混搭”。这个词来源于流行音乐中的一种编曲方法。这种方法把来自不同曲目的各个部分组合起来,形成新的歌曲。这个词被应用到计算机领域中之后,不同的人对它有不同的见解。维基百科上对于 mashup 的解释是:mashup 是将来自多个数据源的数据组合成一个集成的工具的 Web 应用,这个工具能提供原来的数据源所不能提供的新的 Web 服务。mashup 的精髓在于提供新的服务,这就是所谓的“1+1>2”,从组合中获取新的价值。用两个或多个数据源作为输入,能产生的输出却不仅仅是这些输入的简单相加。
典型的 mashup 会使用企业内容或是 Web 上的数据,并围绕一个特定的主题来组织。在创建 mashup 的过程中,可以使用 Web 上的开放 API 和工具。本文将根据一个主题来创建 mashup 应用,并介绍相关的开放 API 与工具。下面简要介绍此主题。
示例 mashup 应用 - 甲型 H1N1 流感最新动态 mashup
甲型 H1N1 流感自流行以来,一直是大家关注的焦点。大家都比较关心流感的最新状态,包括相关新闻、疫情动态和防治知识等。如果要了解甲型 H1N1 流感的动态,可以查看的信息类型有很多,包括新闻、博客、图片、视频和微博客等。本文的示例 mashup 应用将把与甲型 H1N1 流感相关的各类信息都整合起来,在一个 Web 应用中呈现出来。
该 mashup 应用使用了一系列 Web 上的开放 API 与工具来完成。这些 API 和工具包括 Google 地图、屏幕抓取、微软必应、雅虎 Pipes、Delicious、Flickr 和 Identi.ca 等,具体见 表 1。示例 mashup 应用使用的 JavaScript 框架是 Dojo 1.3.1。
表 1. 示例 mashup 中使用的 API 和工具列表
API 和工具 | 作用 |
Google 地图 | 在地图上展示全球各个国家和地区的甲型 H1N1 流感的确认病例和死亡人数。 |
屏幕抓取 | 从 HTML 页面中抓取数据,提供给 Google 地图使用。 |
微软必应 | 搜索甲型 H1N1 流感相关的视频。 |
雅虎 Pipes | 将甲型 H1N1 流感相关的新闻和博客的多个订阅源进行整合。 |
Google AJAX 供稿 API | 将 RSS/Atom 订阅源转换成 JSON 格式。 |
Delicious | 搜索甲型 H1N1 流感相关的网址。 |
Flickr | 搜索甲型 H1N1 流感相关的图片。 |
Identi.ca | 搜索甲型 H1N1 流感相关的微博客内容。 |
Google App Engine | 部署示例 mashup 应用。 |
最终完成的示例 mashup 的运行效果见 图 1。
图 1. 甲型 H1N1 流感最新动态 mashup
查看原图(大图)
在介绍具体的 API 和工具之前,下面先介绍开放 API 的相关话题。
开放 API
开放 API 指的是服务提供商将其服务暴露成编程接口。开放 API 是目前应用最广,功能最强大的 mashup 组件。目前已经有很多公司提供内容丰富的开放 API,这些公司包括 Google、雅虎、eBay、亚马逊等。从 Programmable Web 网站上面可以找到非常多的开放 API。这些 API 所使用的协议也有所不同,典型的协议有 Atom/RSS 订阅源、REST、SOAP、JSON 和 XML-RPC 等。要使用某个开放 API,需要根据它的协议要求编写对应的代码。不同协议的复杂度也不相同,SOAP 和 XML-RPC 比较复杂,而 Atom/RSS 订阅源和 REST 使用起来比较简单,JSON 则非常适合在浏览器端使用。一般来说,开放 API 都提供多种可选的协议,可以根据需要选择适合的协议来使用。
在本文的示例 mashup 应用中,主要使用的是 JSON 协议。使用 JSON 协议的好处是在浏览器中可以直接使用,不需要额外的解析过程。另外一个好处是 HTML 中的 <script>元素是可以直接访问跨域的 JavaScript 脚本的;而 XMLHttpRequest 对象只能访问当前域的内容,访问跨域的内容需要服务器端提供代理的支持。使用 JSON 协议可以大大简化 mashup 的开发。在具体的开发过程中,还会使用 JSON 协议的一种扩展形式:JSONP。
JSONP
JSONP 是 JSON 协议的扩展,它要求在请求的时候添加一个额外的参数作为 JavaScript 方法的名字,JSON 数据本身是该 JavaScript 方法的一个参数而传入的,形成一个方法调用。这样当这段 JavaScript 代码被 <script>元素加载之后,该方法会被自动执行。代码清单 1给出了使用 JSONP 的典型做法。
清单 1. JSONP 的使用 // 通过 <script> 元素加载 JavaScript 文件
var head = document.getElementsByTagName("head")[0] ||
document.documentElement;
var script = document.createElement("script");
script.src = "http://www.example.org/json?callback=displayResult;
script.charSet = "utf-8";
head.appendChild(script);
// 回调方法 displayResult
function displayResult(result) {
}
在 代码清单 1中,JavaScript 文件的 URL 中的参数 callback的值表示回调方法的名字,此处为 displayResult。当调用的结果返回之后,displayResult会被自动执行,其参数 result包含了此次调用的结果。需要注意的是,不同的 API 所使用的表示回调方法名称的参数不一定相同,需要参考 API 的相关文档。本示例 mashup 的开发过程中使用了 Dojo,可以直接利用 Dojo 库中提供的 dojo.io.script.get来执行这样的请求,下面会具体说明。
在介绍完开放 API 及其使用之后,下面开始介绍示例 mashup 中用到的开放 API 的具体用法。首先介绍的是利用屏幕抓取技术与 Google 地图来显示目前各个国家和地区的甲型 H1N1 流感的最新感染人数。
使用 Google 地图与屏幕抓取
随着甲型 H1N1 流感在全球范围内的蔓延,很多人都关心各个国家和地区的确认病例和死亡人数。把这样的信息呈现给用户的最直观的方式是使用地图。虽然确认病例和死亡人数的数据可以在各大门户网站上面查到,但是这些数据并不是以结构化的方式暴露出来的,而是嵌在半结构化的 HTML 文档中的。如果需要把这些数据应用在 mashup 中,就需要将其提取出来。屏幕抓取是解决这类问题的一种技术,下面将具体讨论该技术。
屏幕抓取
目前互联网上的绝大部分数据都是以 HTML 页面的方式呈现的。HTML 页面方便最终用户进行浏览,图文并茂,样式丰富。但是 HTML 文档是半结构化的,数据本身和数据的呈现是混合在一起的。半结构化数据是无法直接供 mashup 应用直接消费的。虽然目前越来越多的公司,如 Google、雅虎、eBay 和亚马逊等,都提供结构化的数据(如 ATOM/RSS 订阅源和 JSON)供第三方来使用,但毕竟是少数,仍然有海量的信息被埋藏在 HTML 文档中。屏幕抓取(screen scraping)的作用就是将数据从半结构化的 HTML 文档中提取出来,以结构化的形式发布出来,供应用来消费。屏幕抓取最直接的方式是对 HTML 文档进行分析,利用 DOM 操作或是正则表达式提取其中的数据,再以 XML 或 JSON 格式进行发布。下面将具体介绍使用 Java 来解析 HTML 文档并发布成 JSON 格式的数据。
关于甲型 H1N1 流感的最新动态数据,来源于新浪网上相关专题的一个页面,该页面以表格的形式列举出了全球各个国家和地区的甲型 H1N1 流感的确认病例和死亡人数。下面要做的是解析该页面的内容,并抽取表格中的数据。解析 HTML 文档所使用的是 CyberNeko HTML 解析器。在解析 HTML 文档之前,需要通过查看 HTML 文档源代码来分析其结构。该页面的表格中有九列,每三列为一组,分别表示国家和地区的名称、确认病例和死亡人数。具体的解析方式见 代码清单 2。
清单 2. 使用 CyberNeko 解析 HTML 文档 public JSONArray scrape() {
JSONArray result = new JSONArray();
try {
URL url = new URL(PAGE_URL);
BufferedReader reader = new BufferedReader(new InputStreamReader(
url.openStream(), "gb2312"));
DOMParser parser = new DOMParser();
InputSource source = new InputSource(reader);
parser.parse(source);
reader.close();
Document doc = parser.getDocument();
NodeList trs = doc.getElementsByTagName("TR");
for (int i = 1; i < trs.getLength(); i++) {
Element tr = (Element) trs.item(i);
NodeList tds = tr.getElementsByTagName("TD");
int num = tds.getLength();
if (num < 3) {
continue;
}
for (int j = 0; j < num;) {
Element countryCell = (Element) tds.item(j);
Element confirmedCell = (Element) tds.item(++j);
Element deathCell = (Element) tds.item(++j);
if (countryCell != null && confirmedCell != null &&
deathCell != null) {
JSONObject data = new JSONObject();
data.put("country", serialize(countryCell, doc
.createDocumentFragment()));
data.put("confirmed", serialize(confirmedCell, doc
.createDocumentFragment()));
data.put("death", serialize(deathCell, doc
.createDocumentFragment()));
result.put(data);
}
}
}
} catch (Exception e) {
logger.log(Level.WARNING, e.getMessage(), e);
}
return result;
}
如 代码清单 2所示,在对 HTML 文档进行解析之后,通过 getElementsByTagName方法获得所有的 TR元素,再获得每个 TR下的 TD元素。TD元素以三个作为一组,分别获取其内容。解析之后的返回结果是一个 JSON 数组,其中的每个元素都是一个 JSON 对象,包含 country、confirmed和 death三个属性,分别表示国家和地区名称、确认病例和死亡人数。serialize方法的作用等价于 innerHTML,用来获取 TD元素的 HTML 内容。
通过使用屏幕抓取技术,已经可以从网页中获得所需的数据,并以 JSON 格式发布出来,下面就可以利用 Google 地图来显示这些数据了。
使用 Google 地图
Google 地图是由 Google 推出的地图服务。它提供了丰富的 API,可以非常方便的在自己的应用中使用它来在地图上面显示信息。Google 地图 API 是目前在 mashup 中用得最多的 API。根据 Programmable Web 网站上面的统计,大约 28% 的 mashup 使用了 Google 地图 API。在上一小节中,已经通过屏幕抓取技术得到了全球各个国家和地区的最新状态数据,不过这些数据中只有国家和地区的中文名称,并没有经纬度信息,无法直接在地图上面显示出来。为了解决这个问题,笔者从 Google 地图 API 讨论组中找到一份包含全球各个国家和地区的英文名称和经纬度信息的 Excel 文件,并利用 Google 翻译添加了国家和地区的中文名称,就得到了一份国家和地区中文名称与经纬度信息的对照表。利用此对照表就可以在 Google 地图上显示相关数据了。具体的使用方式见 代码清单 3。
清单 3. 使用 Google 地图 API dojo.declare("h1n1mashup.TrendMap", null, {
constructor : function(domNode) {
this.domNode = domNode;
this._initMap();
},
_initMap : function() {
var mapContainer = dojo.doc.createElement("div");
dojo.addClass(mapContainer, "map_canvas");
this.domNode.appendChild(mapContainer);
var map = new GMap2(mapContainer);
map.setCenter(new GLatLng(39.908173, 116.397947), 2);
map.addControl(new GLargeMapControl());
this._countryGeocodeStore = new dojox.data.CsvStore({
"url" : "data/CountriesGeoCode.csv",
"identifier" : "name_zh",
"label" : "name_zh"
});
this._loadStatusData(map);
},
_loadStatusData : function(map) {
function buildInfoWindowHtml(item) {
return ["<div class='info_window'><
div class='header'>", item.country, "</div>",
"<div class='death'> 死亡人数:", dojo.trim(item.death),
"</div>", "<div class='confirmed'> 确认病例:",
dojo.trim(item.confirmed), "</div>"
].join("");
}
dojo.xhrGet({
url : "/status",
handleAs : "json",
load : dojo.hitch(this, function(data) {
dojo.forEach(data, function(item) {
var country = item.country.replace(/<(.|\n)*?>/g, "");
this._countryGeocodeStore.fetchItemByIdentity({
"identity" : country,
"scope" : this,
"onItem" : function(citem) {
if (!citem) {
return;
}
var lat = this._countryGeocodeStore.getValue(citem, "lat");
var lon = this._countryGeocodeStore.getValue(citem, "long");
var gLatLng = new GLatLng(parseFloat(lat), parseFloat(lon));
var marker = new GMarker(gLatLng, {"title" : country});
marker.bindInfoWindowHtml(buildInfoWindowHtml(item));
map.addOverlay(marker);
}
});
}, this);
}),
error : function() {
alert("数据获取失败,请重试!");
}
});
}
});
从 代码清单 3中可以看到,首先通过传入一个 DOM 节点来创建 GMap2对象,该对象是 Google 地图 API 的核心,通过它来调用各种对地图的操作。接着通过 GMap2对象的 setCenter方法来设置地图的中心坐标,通过 addControl方法来添加用来进行缩放的地图控件。到这个时候,地图就已经完成了初始化,可以显示数据了。地图上用来显示数据的主要是标记(marker)和信息窗口(information window)。标记用来在地图上根据经纬度信息标识出一个一个的点,而信息窗口则用来提供关于标记的附加信息。此处还创建了一个 dojox.data.CsvStore对象,其作用是保存上面提到的国家和地区的中文名称与经纬度信息的对照表。在完成初始化之后,接下来就是显示最新的状态数据了。首先通过 Dojo 的 XMLHttpRequest 获得由屏幕抓取技术得到的 JSON 数据(地址是 /status),然后通过数据中的国家和地区的中文名称在对照表中进行查找,找到之后就获取对应的经纬度信息。利用经纬度信息可以创建 GMarker对象来表示标记,同时通过 bindInfoWindowHtml绑定与之对应的信息窗口的内容,最后通过 GMap2的 addOverlay方法把标记添加到地图上。这样当用户点击代表某个国家和地区的标记的时候,会弹出对应的信息窗口,里面包含了具体的确认病例和死亡人数。实际的运行效果如 图 2所示。
图 2. 使用 Google 地图显示甲型 H1N1 流感最新动态
在介绍完 Google 地图和屏幕抓取之后,下面说明如何使用雅虎 Pipes 来整合新闻和博客文章,并利用 Google AJAX 供稿 API 来进行显示。
新闻追踪与博客动态
关于甲型 H1N1 流感的 mashup 自然少不了提供最新的相关新闻,与此同时,互联网上大量的博客也是重要的信息来源。本节将介绍如何使用雅虎 Pipes 来整合新闻和博客文章,并利用 Google AJAX 供稿 API 来进行显示。
使用雅虎 Pipes 进行数据整合
雅虎 Pipes 是一个可以把互联网上的数据整合起来并进行操纵的强大工具。它提供了许多模块可以获取数据和对数据进行操纵。这些模块可以以类似 Unix 管道的方式串联起来,从而完成复杂的功能。比如把多个订阅源组合成一个,然后进行过滤和排序操纵,最后输出新的单个订阅源。雅虎 Pipes 提供的模块非常丰富,大概可以分为下面几类:
数据源模块:用来获取各种类型的数据源,为其它模块提供数据来源。数据源模块能够处理的数据源类型有 Atom/RSS 订阅源、CSV、XML、JSON 数据和 HTML 页面等。
用户输入模块:用来获取用户的输入。用户输入的值可以作为 Pipes 运行时候的参数。比如一个 Pipe 可以用来查询某个城市的餐馆信息,就可以使用用户输入模块将城市名称作为参数暴露出来。这样不同的用户可以根据其需要输入不同的城市名称来获得其需要的餐馆信息。用户可以输入的数据类型有 URL、数字、文本、日期和地理位置等。
数据操作模块:用来对数据进行处理。这些是雅虎 Pipes 中核心的模块,可以进行的数据处理操作包括过滤、排序、合并、拆分、去头、去尾、去重、倒序、重命名、计数等。
字符串处理模块:用来对字符串进行处理。这些模块提供的功能包括字符串拼接、执行正则表达式、替换、提取子字符串、分词、提取术语和翻译等。
其它模块:这些模块所提供的功能包括构建 URL、构建日期、构建地理位置、格式化日期和进行简单数学运算等。
具体到示例 mashup 应用来说,使用雅虎 Pipes 的基本思路是:从互联网上找到合适的订阅源作为数据来源,使用相应的模块获取这些数据,然后通过合并模块把订阅源整合起来,考虑到可能出现的重复,再通过去重模块删除标题重复的订阅源条目。从订阅源来说,新闻方面,使用的是百度新闻搜索和 Google 资讯;博客文章方面,使用的是百度博客搜索、Google 博客搜索和有道博客搜索。上面这些数据源都提供 RSS 订阅源作为输出格式,可以使用雅虎 Pipes 中的订阅源获取模块(Fetch Feed)来获取内容。需要注意的是,百度新闻搜索和博客搜索的 RSS 订阅源由于编码问题,不能被订阅源获取模块识别,需要使用 YQL 模块来获取。整合博客文章的雅虎 Pipes 如 图 3所示。
图 3. 整合博客文章的 Pipe
查看原图(大图)
图 3的 Pipe 中,左上的 YQL 模块是用来获取百度博客搜索的 RSS 订阅源,右上的两个订阅源获取模块分别用来获取 Google 博客搜索和有道博客搜索的 RSS 订阅源,中间的合并(Union)模块用来合并三个订阅源的条目,下面的去重(Unique)模块用来删除标题相同的条目。整合新闻的 Pipes 与 图 3所示的整合博客文章的 Pipes 类似。
雅虎 Pipes 提供 RSS 订阅源、JSON 和 PHP 作为 Pipe 运行结果的输出格式。一般来说,在浏览器端直接使用 JSON 数据比较简单。不过对于订阅源来说,还有一个更好的选择是使用 Google AJAX 供稿 API。下面将主要介绍如何使用 Google AJAX 供稿 API 来解析雅虎 Pipes 的输出结果。
Google AJAX 供稿 API
Google AJAX 供稿 API 是一套 JavaScript 库,可以很方便的对互联网上的 ATOM 和 RSS 订阅源进行处理。ATOM 和 RSS 订阅源是许多互联网上的服务所使用的数据发布格式。在 mashup 应用中,经常会需要使用这些来自外部的订阅源。不过在浏览器中直接使用 ATOM 和 RSS 订阅源,并不是一件简单的事情。主要有下面几个困难点:
跨域访问受限:受限于浏览器的安全模型,JavaScript 是不能访问不在同一域中的内容。如果要使用外部的订阅源的话,则需要在 mashup 应用的服务器端添加额外的代理。
XML 处理:ATOM 和 RSS 订阅源都是 XML 格式的。而在浏览器中通过 JavaScript 来直接处理 XML 文档,需要解决不同浏览器的兼容性问题和性能问题。
外部订阅源暂时无法访问:在某些情况下,可能会出现提供订阅源的外部网站无法访问的情况,会导致 mashup 应用无数据可用。
Google AJAX 供稿 API 可以比较好的解决上面几个问题。首先,它可以处理来自不同域的订阅源;其次,它提供 JSON 格式的结果,方便在浏览器端的处理;最后,通过该 API 获取的订阅源的内容实际上是存放在 Google 的服务器上的。Google 的服务器充当了一个缓存的作用,同时 Google 的爬虫会定期更新这些订阅源,从而保证订阅源的实时性。下面说明该 API 的具体使用方式。
Google AJAX 供稿 API 提供一个服务,只需要输入订阅源的 URL,就可以得到 JSON 格式的订阅源内容。在示例 mashup 应用中,只需要以雅虎 Pipes 的 RSS 订阅源 URL 作为输入,就可以得到 JSON 格式的内容,并在浏览器中显示出来。API 返回的 JSON 数据的格式如 代码清单 4所示。
清单 4. Google AJAX 供稿 API 返回的 JSON 格式数据 {
"responseData" : {
"feed" : {
"title" : "H1N1 News",
"link" : "http://pipes.yahoo.com/pipes/pipe.info?_id",
"author" : "",
"description" : "Pipes Output",
"type" : "rss20",
"entries" : [
{"title" : "", "content" : "", "contentSnippet" : ""}
]
}
},
"responseDetails" : null,
"responseStatus" : 200
}
在 代码清单 4中给出的 JSON 格式中,responseStatus指明了该次请求的完成状态,值为 200 表明正确完成,其它值表明出现错误。当请求正确完成的时候,responseData给出了订阅源的 JSON 内容;当出现错误的时候,responseDetails给出了错误的详细信息。在了解了返回结果的格式之后,下面给出具体使用该 API 的代码,如 代码清单 5所示。
清单 5. 使用 Google AJAX 供稿 API dojo.declare("h1n1mashup.FeedReader", null, {
defaultOptions : {
numEntries : 50
},
constructor : function(domNode, options) {
this.domNode = domNode;
this.options = dojo.mixin({}, this.defaultOptions, options);
if (!this.options.feedUrl) {
throw new Error("Feed Url is required!");
}
dojo.addClass(this.domNode, "feed_reader");
this._loadFeed();
},
_loadFeed : function() {
var baseUrl = this.options.feedUrl;
var url = "http://ajax.googleapis.com/ajax/services/feed/load?v=1.0&num="
+ this.options.numEntries + "&q=" + encodeURIComponent(baseUrl);
dojo.html.set(this.domNode, this.LOADING_TEMPLATE);
dojo.io.script.get({
url : url,
callbackParamName: "callback",
handleAs: "json",
preventCache: true,
load : dojo.hitch(this, function(data) {
if (data.responseStatus == 200) {
this._displayFeed(data.responseData.feed);
}
else {
this._displayError(data.responseDetails);
}
}),
error : dojo.hitch(this, this._displayError)
});
},
_normalizeContent : function(content) {
return content.replace(/<\/?font(.|\n)*?>/gi, "").
replace(/<(.|\n)*?font>/gi, "");
},
_displayFeed : function(feed) {
dojo.html.set(this.domNode, "");
var ul = dojo.doc.createElement("ul");
dojo.forEach(feed.entries, function(entry) {
var li = dojo.doc.createElement("li");
entry.content = this._normalizeContent(entry.content);
var markup = dojo.string.substitute(this.ENTRY_TEMPLATE, entry);
dojo.html.set(li, markup);
var titleNode = dojo.query("span:first-child", li)[0];
var bodyNode = dojo.query(".body", li)[0];
dojo.connect(titleNode, "onclick", this, function(event) {
var expanded = dojo.attr(event.target, "_expanded");
var display = expanded === "true" ? "none" : "block";
dojo.style(bodyNode, "display", display);
dojo.attr(event.target, "_expanded",
expanded === "true" ? "false" : "true");
});
ul.appendChild(li);
}, this);
this.domNode.appendChild(ul);
},
_displayError : function() {
dojo.html.set(this.domNode, this.ERROR_TEMPLATE);
var reloadLink = dojo.query("a.reload", this.domNode)[0];
dojo.connect(reloadLink, "onclick", this, this._loadFeed);
}
});
在 代码清单 5中,http://ajax.googleapis.com/ajax/services/feed/load是 Google AJAX 供稿 API 提供的订阅源解析服务的 URL,其中参数 v表示 API 的版本号,值为 1.0;num表示返回的订阅源中的条目数;q表示查询关键字,此处为订阅源 URL 即可。在使用 dojo.io.script.get获得 JSON 格式的返回结果之后,如果请求正确完成,从 responseData.feed可以拿到订阅源的内容。对于订阅源中的每个条目,生成相应的显示。此处当用户点击条目的标题的时候,条目的内容可以展开或收缩。
在介绍完使用雅虎 Pipes 和 Google AJAX 供稿 API 来整合并显示新闻和博客文章之后,下面介绍如何使用微软必应进行视频搜索。
使用微软必应搜索视频
随着 Youtube、优酷网和土豆网等视频分享网站的流行,越来越多的用户选择在互联网上查看视频。视频资料也是一个非常重要的信息来源。本文中的示例 mashup 应用中也需要搜集与甲型 H1N1 流感相关的视频资料,方便用户查看。下面将介绍如何利用微软必应提供的 API 来进行视频搜索。
必应是微软最新推出的搜索引擎,可以用来搜索网页、图片、视频等资料。必应也提供相应的搜索 API 供开发人员使用。在使用必应 API 之前,首先需要申请一个应用 ID。获取了应用 ID 之后,就可以利用 API 进行搜索了。必应支持三种请求格式,分别是:
JSON:返回 JSON 格式的数据,适合在浏览器中通过 JavaScript 来使用。可以选择返回原始数据本身、包装在 JavaScript 方法调用中,以及 JavaScript 方法等三种子格式。
XML:返回 XML 格式的数据,适用于 Flash 和 Silverlight 开发。
SOAP:通过 SOAP 协议来访问 API,适用于桌面应用和服务器端程序。
本文中的示例应用使用的是 JSON 格式。使用 JSON 格式结果的 API 访问的 URL 是 http://api.bing.net/json.aspx,参数 JsonType用来控制上面说到的三种子格式。如果该参数的值为 raw的话,返回的是纯 JSON 数据;如果值为 callback的话,JSON 数据会包装在一个 JavaScript 方法调用中,该方法的名字由参数 JsonCallback来指定;如果值为 function的话,返回的是一个 JavaScript 方法,当该方法被调用的时候,搜索结果会被返回。这里使用的是第二种 JSON 格式。
在确定了请求格式之后,下一步是确定搜索源类型。必应提供了多种搜索源可供使用,包括网页、图像、视频、新闻、航班状态等。不同搜索源可选的参数也不相同。可以在一次搜索中指定多个搜索源。此处使用的是单一的视频搜索源。通过在请求中使用参数 Sources=Video指明使用视频搜索源。参数 AppId表示申请的应用 ID,是每次请求必须的。搜索的关键词通过参数 Query来指明。参数 Video.Count用来限定结果中包含的记录数。视频搜索的 JSON 数据格式如 代码清单 6所示。
清单 6. 必应视频搜索的 JSON 格式结果 {
"SearchResponse":{
"Version":"2.2","Query":{"SearchTerms":"H1N1"},
"Video":{
"Total":31500,"Offset":0,
"Results":[
{"Title":"H1N1 甲型流感食谱",
"PlayUrl":"http://www.tudou.com/programs/view/HJgMIf8BODI/",
"SourceTitle":"Tudou",
"RunTime":79882,
"ClickThroughPageUrl":"",
"StaticThumbnail":
{"Url":"","ContentType":"image/jpeg",
"Width":160,"Height":120,"FileSize":5232}
}
]
}
}
}
在 代码清单 6中,SearchResponse中的 Version给出了 API 的版本号;Query给出了查询的信息;Video则包含了视频的搜索结果。对于 Video属性,其中的 Total表示搜索结果记录总数;Offset表示当前结果页的起始位置;Results则是包含当前结果页的记录的数组。对数组中的每个元素,Title表示视频的标题;PlayUrl表示视频的原始地址;SourceTitle表示视频所发布的网站的名称;RunTime表示视频长度的毫秒数;ClickThroughPageUrl是必应提供的播放页面;StaticThumbnail则包含缩略图的信息。如果搜索请求执行过程中发生错误,返回的也是 JSON 数据,如 代码清单 7所示。
清单 7. 必应视频搜索的 JSON 格式的错误结果 {
"SearchResponse":{
"Version":"2.2","Query":{"SearchTerms":"H1N1"},
"Errors":[
{"Code":1002,"Message":"Parameter has invalid value.",
"Parameter":"SearchRequest.AppId",
"Value":"",
"HelpUrl":"http://msdn.microsoft.com/en-us/library/dd251042.aspx"}
]
}
}
在 代码清单 7中表示错误的 JSON 数据中,Errors是一个包含所有错误信息的数组,对于其中的每个元素,Code表示错误代码,Message表示错误的详细说明,Parameter表示参数名称,Value表示提供的参数值,HelpUrl表示包含帮助信息的网址。在介绍了视频搜索结果的请求格式之后,下面介绍如何显示视频搜索结果,具体如 代码清单 8所示。
清单 8. 显示必应视频搜索的结果 dojo.declare("h1n1mashup.Video", null, {
constructor : function(domNode) {
this.domNode = domNode;
dojo.addClass(this.domNode, "videos");
this._loadVideos();
},
LOADING_TEMPLATE : "加载中,请稍候 ......",
VIDEO_TEMPLATE : [
"<span class='video'>",
"<div class='thumb'><a href='${PlayUrl}'>
<img src='${StaticThumbnail.Url}'", "style='height:120px;width:160px;'/>
</a></div>",
"<div class='meta'><a href='${PlayUrl}'
target='_blank'>${Title}</a></div>",
"</span>"
].join("\n"),
ERROR_TEMPLATE : "<div class='error'>获取视频时发生错误,请点击 <a href='javascript:;'" +
"class='reload'> 重试 </a>。</div>",
_loadVideos : function() {
var keyword = "H1N1";
var url = "http://api.bing.net/json.aspx?AppId=&Version=2.2&Market=zh-CN" +
"&Sources=Video&Video.Count=20&JsonType=callback&Query=" +
encodeURIComponent(keyword);
dojo.html.set(this.domNode, this.LOADING_TEMPLATE);
var errorHandler = dojo.hitch(this, function() {
dojo.html.set(this.domNode, this.ERROR_TEMPLATE);
var reloadLink = dojo.query("a.reload", this.domNode)[0];
dojo.connect(reloadLink, "onclick", this, this._loadVideos);
});
dojo.io.script.get({
url : url,
callbackParamName : "JsonCallback",
handleAs: "json",
load : dojo.hitch(this, function(data) {
dojo.html.set(this.domNode, "");
if (!data) {
errorHandler();
return;
}
var hasError = data.SearchResponse && data.SearchResponse.Errors;
if (hasError) {
errorHandler();
return;
}
var results = data.SearchResponse.Video.Results;
dojo.forEach(results, function(video) {
var span = dojo.doc.createElement("span");
var markup = dojo.string.substitute(this.VIDEO_TEMPLATE, video);
dojo.html.set(span, markup);
this.domNode.appendChild(span);
}, this);
}),
error : errorHandler
});
}
});
在 代码清单 8中,基本的思路是通过 Dojo 的 dojo.io.script.get来获取必应视频搜索的 JSON 结果,如果请求正确完成,就把视频显示出来;否则就显示错误信息。
在介绍完使用微软必应的视频搜索之后,下面介绍如何获取 Delicious 上的书签。
获取 Delicious 的书签
Delicious 是一个非常流行的在线书签分享网站。用户可以把自己看过的不错的网页的地址发布到 Delicious 上面,添加一些标签和说明来描述;也可以查看其它用户分享出来的网址。在本文的示例 mashup 应用中,希望可以提供给用户与甲型 H1N1 流感相关的网址,Delicious 是一个不错的数据来源。本节中将介绍如何使用 Delicious 提供的 API 和订阅源来获取网址信息。
Delicious 提供开放 API 和订阅源来允许第三方使用其数据。开放 API 和订阅源的功能和使用场景不同:
开放 API开放 API 主要用来对 Delicious 上某个注册用户的书签和标签进行获取、创建、更新和删除操作。由于涉及到操作用户相关的信息,在使用 API 的时候需要进行认证。API 使用基于 HTTP 协议的认证方式。使用 API 的时候只需要按照格式要求构造相应的 URL 并发送 HTTP GET 请求即可,请求的返回结果是 XML 文档。订阅源与开放 API 不同,订阅源所提供的信息是只读的,使用的时候无需认证。使用订阅源的方式与开放 API 相同,只需要按照格式要求构造 HTTP GET 请求即可。订阅源提供两种结果返回格式:RSS 与 JSON。RSS 格式适合直接在浏览器和订阅源阅读器中查看,而 JSON 格式适合供第三方程序使用。
根据示例应用的实际需要,本节中将主要介绍 JSON 返回格式的订阅源的使用。
示例应用中需要查询与甲型 H1N1 流感相关的网址,只需要根据标签在 Delicious 上进行查询即可。此处使用“猪流感”作为标签来查询,可以获得比较多的相关网址。根据标签来获取书签的订阅源地址格式是 http://feeds.delicious.com/v2/json/tag/{tag},因此示例应用使用的地址是 http://feeds.delicious.com/v2/json/tag/%E7%8C%AA%E6%B5%81%E6%84%9F,其中 %E7%8C%AA%E6%B5%81%E6%84%9F是“猪流感”编码之后的格式。订阅源提供参数 count来限制返回结果的记录数,可选值范围是 1 到 100,默认值是 15。在使用 JSON 作为返回格式的时候,还可以提供参数 callback来指定包装在 JSON 数据外的 JavaScript 方法的名字。订阅源返回的结果是一个 JSON 数组,其中的每个元素包含了书签相关的信息,如 代码清单 9所示。
清单 9. Delicious 订阅源 JSON 格式说明 [
{
"u":"http://science.solidot.org/article.pl?sid=09/07/03/1059206",
"d":"英国率先宣布:甲型 H1N1 流感失控",
"t":["英国","猪流感"],
"dt":"2009-07-03T12:19:53Z",
"n":"英国政府宣布,甲型 H1N1 流感已经无法控制,......",
"a":"Akcipitro"
}
]
代码清单 9中给出了一个书签的 JSON 表示,其中的属性 u表示书签的 URL;d是书签的标题;t是书签的标签;dt是该书签添加的时间;n是书签的简要描述;a是共享此书签的 Delicious 用户名。获取书签并显示的具体方式如 代码清单 10所示。
清单 10. 获取 Delicious 上的书签并显示 dojo.declare("h1n1mashup.DeliciousBookmark", null, {
defaultOptions : {
count : 30
},
LOADING_TEMPLATE : "加载中,请稍候 ......",
BOOKMARK_TEMPLATE : [
"<div class='bookmark'>",
"<h4><a href='${u}' class='link' target='_blank'>${d}</a></h4>",
"<div class='description'>${n}</div>",
"</div>"
].join("\n"),
ERROR_TEMPLATE : "<div class='error'> 获取书签时发生错误,请点击 <a href='javascript:;'" +
"class='reload'> 重试 </a>。</div>",
constructor : function(domNode, options) {
this.domNode = domNode;
this.options = dojo.mixin({}, this.defaultOptions, options);
dojo.addClass(this.domNode, "bookmarks");
this._loadBookmarks();
},
_loadBookmarks : function() {
var tag = "猪流感";
var url = "http://feeds.delicious.com/v2/json/tag/"
+ encodeURIComponent(tag) + "?count=" + this.options.count;
dojo.html.set(this.domNode, this.LOADING_TEMPLATE);
dojo.io.script.get({
url : url,
callbackParamName : "callback",
handleAs: "json",
load : dojo.hitch(this, function(data) {
dojo.html.set(this.domNode, "");
var ul = dojo.doc.createElement("ul");
dojo.forEach(data, function(bookmark) {
var li = dojo.doc.createElement("li");
var markup = dojo.string.substitute(this.BOOKMARK_TEMPLATE, bookmark);
dojo.html.set(li, markup);
ul.appendChild(li);
}, this);
this.domNode.appendChild(ul);
}),
error : dojo.hitch(this, function() {
dojo.html.set(this.domNode, this.ERROR_TEMPLATE);
var reloadLink = dojo.query("a.reload", this.domNode)[0];
dojo.connect(reloadLink, "onclick", this, this._loadBookmarks);
})
});
}
});
代码清单 10中的基本思路是利用 Dojo 提供的获取 JSON 格式数据的功能,获取 JSON 格式的订阅源返回结果,并按照一定的格式显示出来。dojo.io.script.get的参数格式与 Dojo 的 XMLHttpRequest 相关的方法(如 dojo.xhrGet)类似,不同的是多了属性 callbackParamName用来设置表示包装在 JSON 数据外的 JavaScript 方法的名字的参数名称。对于 Delicious 来说,callbackParamName的值是 callback。
在介绍完使用 Delicious 提供的书签之后,下面介绍如何使用 Flickr 提供的图片。
使用 Flickr 的图片
Flickr 是目前最流行的在线照片分享网站。大量的用户在 Flickr 上共享自己拍摄的照片,并对其他人的照片进行评论。在本文的示例 mashup 应用中,希望可以提供给用户与甲型 H1N1 相关的图片,Flickr 是一个非常好的数据来源。Flickr 也提供了丰富的开放 API 供第三方程序使用。本节中将介绍如何使用这些开放 API 来获取图片,并使用 Dojo 提供的幻灯片控件来显示图片。
在使用 Flickr 的开放 API 之前,需要申请一个 API 密钥(API key)。开发者需要注册一个 Flickr 帐号,然后在特定的网页上面申请。申请了 API 密钥之后,就可以使用 Flickr 提供的开放 API 了。Flickr 提供了一系列的方法,用来对照片、博客、收藏夹、分组、标签以及照片相关的评论、注释和地理位置信息等进行操作。使用这些方法时需要按照特定的格式发出请求,同时需要对请求的返回结果进行解析,以得到相关数据。Flickr 的开放 API 提供非常丰富的请求和响应格式供开发者选择,其中请求格式有 REST、XML-RPC 和 SOAP 等三种,响应格式有 REST、XML-RPC、SOAP、JSON 和 PHP 等五种。从开发的角度来说,REST 请求格式和 JSON 的响应格式是最适合开发 mashup 的,下面将主要介绍这两种格式。
使用 REST 格式的请求非常简单,只需要使用相关的参数构造 URL,然后发送 HTTP GET 和 POST 请求即可。REST 请求的 URL 基本格式是 http://api.flickr.com/services/rest/?method={method}&name=value,其中 method是需要调用的 API 方法名称,name=value表示该方法所需要的一系列参数。JSON 响应格式是一个字符串,可以在浏览器中转换成 JavaScript 对象。通过在发送请求时指定参数 format的值为 json来返回 JSON 格式的结果。默认情况下返回的 JSON 数据是包装在一个名为 jsonFlickrApi的方法中的。可以通过额外的参数 jsoncallback来指定该方法的名字,也可以设置参数 nojsoncallback的值为 1 来声明只返回原始的 JSON 数据。
当从 Flickr 搜索图片的时候,需要使用 API 中的 flickr.photos.search方法,该方法可以使用的参数有很多。一般来说,常用的有 text,用来指明全文搜索的关键词;tags,用来指明以逗号分隔的一系列标签和 sort,用来指明对结果的排序方式。该方法返回的是 JSON 格式响应如 代码清单 11所示。
清单 11. flickr.photos.search 方法的返回结果 {
"photos":{
"page":1, "pages":2, "perpage":20, "total":"37",
"photo":[
{"id":"989584269", "owner":"42962212@N00", "secret":"9d37f786e3",
"server":"1109", "farm":2,
"title":"Swine Flu (...the other day)", "ispublic":1, "isfriend":0, "isfamily":0,
"ownername":"kozumel", "dateupload":"1186092951",
"datetaken":"2006-08-26 12:17:34", "datetakengranularity":"0"}
]
},
"stat":"ok"
}
如 代码清单 11中所示,如果该方法成功执行的话,返回的 JSON 对象中 stat的值为 ok;如果出错的话,会给出错误代码和详细信息。photos属性提供了图片相关的信息,其中 page、pages、perpage和 total与搜索结果的分页相关,分别表示当前页数、总页数、每页记录数和总记录数;photo是一个数组,包含当前页的图片信息。对 photo数组中的每个元素来说,其中的属性 id、owner、secret、server、farm、title、ispublic、isfriend、isfamily、ownername、dateupload、datetaken、datetakengranularity分别表示图片的编号、所有者的编号、图片秘密编码、服务器编号、服务器集群编号、图片标题、是否公开、是否只对朋友可见、是否只对家人可见、所有者用户名、上传时间、拍摄时间和拍摄时间的精确度。需要注意的是,返回结果中并不直接包含图片的 URL,需要按照一定的规则来构造。图片 URL 的模板是 http://farm{farm}.static.flickr.com/{server}/{id}_{secret}.jpg,只需要用结果中的数据进行替换就可以得到实际的 URL。在分析 Flickr 图片搜索的 JSON 格式的返回结果之后,就可以利用这些数据来按照实际需要来显示图片。下面说明如何以幻灯片的形式来显示从 Flickr 搜索到的图片。
虽然可以根据 Flickr 的 JSON 结果从头开始实现一套图片显示代码,不过更好的方式是利用已有的 JavaScript 库。Dojo 提供了幻灯片控件,可以很容易的创建出幻灯片的效果。示例 mashup 应用中使用的是 dojox.image.Gallery这个控件,这是一个附带缩略图的幻灯片控件。dojox.image.Gallery控件的数据来源是 dojox.data.FlickrRestStore。dojox.data.FlickrRestStore封装了通过 Flickr 提供的 API 来搜索图片的相关逻辑,可以直接提供给 dojox.image.Gallery控件使用。具体的使用方式见 代码清单 12。
清单 12. 使用 Dojo 的幻灯片控件 dojo.declare("h1n1mashup.Photos", null, {
constructor : function(domNode, options) {
this.domNode = domNode;
dojo.addClass(this.domNode, "photos");
var galleryNode = dojo.doc.createElement("div");
this.domNode.appendChild(galleryNode);
var gallery = new dojox.image.Gallery({}, galleryNode);
this._loadPhotos(gallery);
},
_loadPhotos : function(gallery) {
var keyword = "猪流感";
var flickrRestStore = new dojox.data.FlickrRestStore();
var req = {
query : {
apikey : "申请的 API 密钥",
text : keyword
}
};
gallery.setDataStore(flickrRestStore, req);
}
});
在 代码清单 12中,首先创建一个 dojox.image.Gallery控件,接着创建一个 dojox.data.FlickrRestStore对象作为数据源,并提供 API 密钥和搜索关键词,通过 gallery.setDataStore(flickrRestStore, req)就可以设置幻灯片控件使用该数据源,并把搜索结果中的图片显示出来。
在介绍完利用 Dojo 的幻灯片控件显示 Flickr 的图片之后,下面说明如何使用微博客 Identi.ca。
微博客:Identi.ca
微博客(micro-bloging)的概念目前比较流行,指的是通过各种平台发布短小的消息。目前的微博客平台,国外比较流行的有 Twitter、Identi.ca 和 Jaiku 等,而国内则有饭否和叽歪等。以大家对甲型 H1N1 流感的关注度来说,在微博客上面有相当多相关的信息。由于目前 Twitter 暂时无法访问,下面以 Identi.ca 为例来说明,如何获取这些信息并展示出来。Identi.ca 提供的 API 是与 Twitter 的 API 兼容的,因此使用 Identi.ca 的 mashup 几乎不需要做什么修改就可以切换到 Twitter 的 API。
Identi.ca 提供相应的 API 可以对用户发布的微博客内容进行搜索。搜索结果提供 JSON 格式的返回结果。搜索的地址是 http://identi.ca/api/search.json,添加参数 q作为查询关键字。搜索结果的 JSON 格式如 代码清单 13所示。
清单 13. Identi.ca 的搜索结果的 JSON 格式 {
"results":[
{
"text":"A Run-Down on H1N1 Preparation http:\/\/post.ly\/3jjh",
"to_user_id":null,
"to_user":null,
"id":9281244,
"from_user_id":"50314",
"from_user":"gellenburg",
"iso_language_code":"en_US",
"source":"Posterous",
"profile_image_url":"",
"created_at":"Wed, 02 Sep 2009 12:09:29 +0000"
}
]
}
代码清单 13中 results是一个数组,给出了全部的查询结果,其中每一个元素表示一条记录。对于每条记录,text表示微博客的内容,to_user_id表示接收者的用户 ID,to_user表示接收者的用户名,from_user表示发送者的用户名,id表示记录的编号,from_user_id表示发送者的 ID,from_user表示发送者的用户名,iso_language_code表示内容的语言代码,source表示发送此消息的工具,profile_image_url表示发送者的头像 URL,created_at表示发送时间。JSON 格式数据的存在,使得使用 Identi.ca 的搜索结果变得非常简单。
饭否同样也提供相关的 API 用来搜索微博客的内容,限于篇幅,此处不再赘述。
此处并没有给出显示 Identi.ca、Twitter 和饭否的搜索结果的具体代码,可以从 下载部分找到全部源代码。
部署到 Google App Engine
在完成 mashup 应用的开发之后,一个重要的步骤是把它部署到 Web 上,让其他人使用。这是获取用户反馈和不断改进应用的重要途径。部署到 Web 上的方式有很多,可以租用主机,也可以自己架设服务器。目前来说,一个更好的选择是利用已有的应用开发和部署平台。Google App Engine 运行开发人员在 Google 的基础设施之上创建 Web 应用,并提供数据存储、高速缓存、邮件和图像处理等服务。目前 Google App Engine 支持 Python 和 Java 两种编程语言。本文的示例 mashup 使用 Java 语言开发。
使用 Java 语言开发的过程比较简单,首先需要下载对应的 SDK 和 Eclipse 插件,然后就可以在 Eclipse 中创建 Web 应用。接下来就可以进行开发工作,在 Eclipse 中可以运行测试服务器来进行测试。在开发完成之后,就可以进行部署。在部署之前需要注册一个 Google 帐号并申请一个应用 ID(application ID),之后在 Eclipse 插件中把 Web 应用于该 ID 关联起来,就可以在 Eclipse 直接进行部署。部署完成之后可以通过浏览器来查看应用的实现运行效果。
总结
情景式应用由于能够快速满足一般用户的个性化需求,目前越来越受到关注。而 mashup 作为一种数据、界面和业务的快速整合方式,非常适合创建各种情景式应用。互联网上的各种开放 API 和工具,更加方便了 mashup 的创建。本文从一个贴近现实的 mashup 示例出发,详细说明了 Google 地图、屏幕抓取、微软必应、雅虎 Pipes、Delicious、Flickr 和 Identi.ca 等开放 API 和工具的使用,可以帮助大家更加快速的创建 mashup。
本文示例源代码或素材下载
更多精彩
赞助商链接