使用 Web 服务
一旦使用程序通过 HTTP 检索文档和解析文档变得容易,没过多久就发展出一种方法,我们开始制作专门设计给其他程序使用的文档(即,不是要在浏览器中显示的 HTML)。
我们在网络上交换数据时常用的两种格式是:可扩展标记语言(XML)已经使用了很长时间,最适合交换文档样式的数据。当程序只想彼此交换字典、列表或其他内部信息时,它们使用 JavaScript 对象表示法(JSON)(参见 www.json.org)。我们将研究这两种格式。
可扩展标记语言 - XML
XML 看起来非常类似于 HTML,但 XML 比 HTML 结构更严谨。以下是一个 XML 文档的示例:
<person>
<name>Chuck</name>
<phone type="intl">
+1 734 303 4456
</phone>
<email hide="yes" />
</person>
每对开始标签(例如 <person>
)和结束标签(例如 </person>
)代表一个元素 (element) 或节点 (node),其名称与标签相同(例如 person
)。每个元素可以包含一些文本、一些属性(例如 hide
)以及其他嵌套元素。如果一个 XML 元素是空的(即没有内容),则可以用自闭合标签表示(例如 <email />
)。
通常将 XML 文档看作一个树形结构会很有帮助,其中有一个顶层元素(这里是 person
),其他标签(例如 phone
)被绘制为其父 (parent) 元素的子 (children) 元素。
XML 的树形表示
解析 XML
这是一个解析一些 XML 并从中提取一些数据元素的简单应用程序:
import xml.etree.ElementTree as ET
data = '''
<person>
<name>Chuck</name>
<phone type="intl">
+1 734 303 4456
</phone>
<email hide="yes" />
</person>'''
tree = ET.fromstring(data)
print('Name:', tree.find('name').text)
print('Attr:', tree.find('email').get('hide'))
# 代码: https://www.py4e.com/code3/xml1.py
三个单引号 ('''
) 以及三个双引号 ("""
) 允许创建跨多行的字符串。
调用 fromstring
将 XML 的字符串表示形式转换为 XML 元素的“树”。当 XML 呈树状结构时,我们可以调用一系列方法来从 XML 字符串中提取数据部分。find
函数在 XML 树中搜索并检索与指定标签匹配的元素。
Name: Chuck
Attr: yes
使用像 ElementTree
这样的 XML 解析器的优点是,虽然这个例子中的 XML 非常简单,但事实证明关于有效的 XML 有很多规则,而使用 ElementTree
使我们能够从 XML 中提取数据,而无需担心 XML 语法的规则。
遍历节点
通常 XML 有多个节点,我们需要编写一个循环来处理所有节点。在下面的程序中,我们遍历所有的 user
节点:
import xml.etree.ElementTree as ET
input = '''
<stuff>
<users>
<user x="2">
<id>001</id>
<name>Chuck</name>
</user>
<user x="7">
<id>009</id>
<name>Brent</name>
</user>
</users>
</stuff>'''
stuff = ET.fromstring(input)
lst = stuff.findall('users/user')
print('User count:', len(lst))
for item in lst:
print('Name', item.find('name').text)
print('Id', item.find('id').text)
print('Attribute', item.get('x'))
# 代码: https://www.py4e.com/code3/xml2.py
findall
方法检索一个 Python 列表,其中包含表示 XML 树中 user
结构的子树。然后我们可以编写一个 for
循环来查看每个用户节点,并打印出 name
和 id
文本元素以及来自 user
节点的 x
属性。
User count: 2
Name Chuck
Id 001
Attribute 2
Name Brent
Id 009
Attribute 7
在 findall
语句中包含除顶层元素(例如 stuff
)之外的所有父级元素(例如 users/user
)是很重要的。否则,Python 将找不到任何所需的节点。
import xml.etree.ElementTree as ET
input = '''
<stuff>
<users>
<user x="2">
<id>001</id>
<name>Chuck</name>
</user>
<user x="7">
<id>009</id>
<name>Brent</name>
</user>
</users>
</stuff>'''
stuff = ET.fromstring(input)
lst = stuff.findall('users/user')
print('User count:', len(lst))
lst2 = stuff.findall('user')
print('User count:', len(lst2))
lst
存储了所有嵌套在其 users
父元素内的 user
元素。lst2
查找那些没有嵌套在顶层 stuff
元素内的 user
元素,而那里并没有这样的元素。
User count: 2
User count: 0
JavaScript 对象表示法 - JSON
JSON 格式的灵感来源于 JavaScript 语言中使用的对象和数组格式。但由于 Python 是在 JavaScript 之前发明的,Python 的字典和列表语法影响了 JSON 的语法。所以 JSON 的格式几乎与 Python 列表和字典的组合完全相同。
下面是一个大致相当于上面简单 XML 的 JSON 编码:
{
"name" : "Chuck",
"phone" : {
"type" : "intl",
"number" : "+1 734 303 4456"
},
"email" : {
"hide" : "yes"
}
}
你会注意到一些差异。首先,在 XML 中,我们可以向“phone”标签添加像“intl”这样的属性。在 JSON 中,我们只有键值对。此外,XML 的“person”标签消失了,被一组外层花括号取代。
总的来说,JSON 结构比 XML 简单,因为 JSON 的功能比 XML 少。但 JSON 的优点在于它直接映射到字典和列表的某种组合。而且由于几乎所有编程语言都有与 Python 的字典和列表等效的东西,JSON 是让两个协作程序交换数据的一种非常自然的格式。
由于其相对简单性(与 XML 相比),JSON 正迅速成为几乎所有应用程序之间数据交换的首选格式。
解析 JSON
我们根据需要通过嵌套字典和列表来构建我们的 JSON。在这个例子中,我们表示一个用户列表,其中每个用户是一组键值对(即一个字典)。所以我们有一个字典列表。
在下面的程序中,我们使用内置的 json
库来解析 JSON 并通读数据。请仔细比较这个与上面等效的 XML 数据和代码。JSON 的细节较少,所以我们必须事先知道我们得到的是一个列表,并且该列表包含的是用户,每个用户是一组键值对。JSON 更简洁(优点),但也更少自描述性(缺点)。
import json
data = '''
[
{ "id" : "001",
"x" : "2",
"name" : "Chuck"
} ,
{ "id" : "009",
"x" : "7",
"name" : "Brent"
}
]'''
info = json.loads(data)
print('User count:', len(info))
for item in info:
print('Name', item['name'])
print('Id', item['id'])
print('Attribute', item['x'])
# 代码: https://www.py4e.com/code3/json2.py
如果你比较从解析后的 JSON 和 XML 中提取数据的代码,你会看到我们从 json.loads()
得到的是一个 Python 列表,我们使用 for
循环遍历它,而该列表中的每个项都是一个 Python 字典。一旦 JSON 被解析,我们可以使用 Python 的索引运算符来为每个用户提取各种数据片段。我们不必使用 JSON 库来深入挖掘解析后的 JSON,因为返回的数据就是原生的 Python 结构。
这个程序的输出与上面的 XML 版本完全相同。
User count: 2
Name Chuck
Id 001
Attribute 2
Name Brent
Id 009
Attribute 7
总的来说,行业趋势是逐渐从 XML 转向 JSON 用于 Web 服务。因为 JSON 更简单,并且更直接地映射到我们编程语言中已有的原生数据结构,所以在处理 JSON 时,解析和数据提取代码通常更简单、更直接。但 XML 比 JSON 更具自描述性,因此在某些应用中 XML 仍然具有优势。例如,大多数文字处理器内部使用 XML 而不是 JSON 来存储文档。
应用程序编程接口
我们现在有能力使用超文本传输协议(HTTP)在应用程序之间交换数据,并有一种方法使用可扩展标记语言(XML)或 JavaScript 对象表示法(JSON)来表示我们在这些应用程序之间来回发送的复杂数据。
下一步是开始使用这些技术来定义和记录应用程序之间的“合约”。这些应用程序到应用程序合约的通用名称是应用程序编程接口 (Application Program Interfaces, APIs)。当我们使用 API 时,通常一个程序会提供一组服务 (services) 供其他应用程序使用,并发布必须遵循的 API(即“规则”)以访问该程序提供的服务。
当我们开始构建程序时,如果程序的功能包括访问其他程序提供的服务,我们将这种方法称为面向服务的架构 (Service-Oriented Architecture, SOA)。SOA 方法是指我们的整体应用程序利用其他应用程序的服务。非 SOA 方法是指应用程序是一个单一的独立应用程序,其中包含实现该应用程序所需的所有代码。
当我们在使用网络时,会看到许多 SOA 的例子。我们可以去一个网站预订机票、酒店和汽车,所有这些都在一个网站上完成。酒店的数据并不存储在航空公司的计算机上。相反,航空公司的计算机联系酒店计算机上的服务,检索酒店数据并呈现给用户。当用户同意使用航空公司网站进行酒店预订时,航空公司网站会使用酒店系统上的另一个 Web 服务来实际进行预订。而当需要为整个交易向你的信用卡收费时,还有其他的计算机会参与到这个过程中。
面向服务的架构
面向服务的架构有很多优点,包括:(1)我们始终只维护一份数据副本(这对于像酒店预订这样我们不希望超额预订的事情尤其重要)和(2)数据的所有者可以设定关于其数据使用的规则。有了这些优点,SOA 系统必须精心设计才能具有良好的性能并满足用户的需求。
当一个应用程序通过网络在其 API 中提供一组服务时,我们称这些为Web 服务 (web services)。
安全性和 API 使用
通常你需要一个 API 密钥 (API key) 才能使用供应商的 API。其基本思想是他们想知道谁在使用他们的服务以及每个用户的使用量。也许他们有免费和付费的服务层级,或者有政策限制单个用户在特定时间段内可以发出的请求数量。
有时,一旦你获得了 API 密钥,你只需将密钥作为 POST 数据的一部分包含进去,或者在调用 API 时作为 URL 上的参数。
其他时候,供应商希望对请求的来源有更高的保证,因此他们期望你使用共享密钥和秘密发送经过加密签名的消息。一种非常常见的用于在互联网上签署请求的技术叫做 OAuth。你可以在 www.oauth.net 阅读更多关于 OAuth 协议的信息。
值得庆幸的是,有许多方便且免费的 OAuth 库,因此你可以避免通过阅读规范从头开始编写 OAuth 实现。这些库的复杂性各不相同,丰富程度也各不相同。OAuth 网站上有关于各种 OAuth 库的信息。
术语表
API (Application Program Interface) 应用程序编程接口 - 应用程序之间的合约,定义了两个应用程序组件之间的交互模式。 ElementTree 一个内置的 Python 库,用于解析 XML 数据。 JSON (JavaScript Object Notation) JavaScript 对象表示法 - 一种允许基于 JavaScript 对象语法标记结构化数据的格式。 SOA (Service-Oriented Architecture) 面向服务的架构 - 当应用程序由通过网络连接的组件构成时。 XML (eXtensible Markup Language) 可扩展标记语言 - 一种允许标记结构化数据的格式。
如果你在本书中发现错误,欢迎使用 Github 给我发送修正。