Amazon.com, 常称为世界上最大的存储库,它已经开放了产品的所有目录,以便采用 SOAP 或 REST (HTTP 之上的 XML)技术直接集成到其他的 Web 站点中去。通过各种合作项目,合作者与第三方供应商都可以从中赚取介绍费,并且管理详细目录,搜索产品信息,以获取有竞争力的价格。为了展示 Amazon.com Web 服务 API 的一些用途,我们将向您演示如何创建 GUI 工具,这种工具使您能够在 Amazon.com 目录的各种产品类别中搜索相似类型的条目。由于它是典型的 Web 服务提供者,所以由 Amazon.com 开发者工具包提供的示例和工具不包括 Python 绑定或库。即使这样也不用担心 —— 通过 Python 使用 Amazon API 是非常简单易懂的,甚至有可能比一般的语言和工具都还要简单。
对于这个项目,我们将使用 Python 2.3 精心地设计我们的工具;0.10.4 版本的 SOAPpy 用来代理和编组 API,我们将使用 wxPython 工具包来创建 GUI。0.10.3 版本之后的 SOAPpy 还要依赖于 IEEE 754 浮点处理程序包 fpconst(当前版本 0.6.0)。这很有可能包括在以后的 Python 发行版本中。请参阅 参考资料以获得下载这些工具的链接。
登录到 Amazon.com Web 服务站点(请参阅
参考资料),下载免费的开发者工具包,并且进行注册,以获取一个免费开发者令牌,您需要这个令牌来将每个方法调用传送到 Web 服务 API。在我们继续创建工具之前,让我们先测试一下 Amazon API。我们在 Amazon.com
上执行关键字搜索功能,以查找与“spotted
owl”有关的书籍。通过深入分析 API 的 WSDL
,我们找到了一种合适的方法,
KeywordSearchRequest (请参见
清单 1)。
清单 1. Amazon Web 服务 API WSDL 的专家展示 KeywordSearchRequest.
...
<message name="KeywordSearchRequest">
<!-- Messages for Amazon Web APIs -->
<part name="KeywordSearchRequest" type="typens:KeywordRequest"/>
</message>
<message name="KeywordSearchResponse">
<part name="return" type="typens:ProductInfo"/>
</message>
<xsd:complexType name="KeywordRequest">
<xsd:all>
<xsd:element name="keyword" type="xsd:string"/>
<xsd:element name="page" type="xsd:string"/>
<xsd:element name="mode" type="xsd:string"/>
<xsd:element name="tag" type="xsd:string"/>
<xsd:element name="type" type="xsd:string"/>
<xsd:element name="devtag" type="xsd:string"/>
<xsd:element name="sort" type="xsd:string" minOccurs="0"/>
<xsd:element name="locale" type="xsd:string" minOccurs="0"/>
<xsd:element name="price" type="xsd:string" minOccurs="0"/>
</xsd:all>
</xsd:complexType>
<xsd:complexType name="ProductInfo">
<xsd:all>
<xsd:element name="TotalResults" type="xsd:string" minOccurs="0"/>
<!-- Total number of Search Results -->
<xsd:element name="TotalPages" type="xsd:string" minOccurs="0"/>
<!-- Total number of Pages of Search Results -->
<xsd:element name="ListName" type="xsd:string" minOccurs="0"/>
<!-- Listmania list name -->
<xsd:element name="Details" type="typens:DetailsArray" minOccurs="0"/>
</xsd:all>
</xsd:complexType>
...
|
KeywordSearchRequest
接受单个参数,包含查询所需参数的结构。显而易见,首先就是
keyword 参数。Amazon.com
以每页不超过 10 个条目的方式返回结果,这由
page
参数指定。其余的参数就不那么明显了。查阅 API
文档(是的,我们必须看看
WSDL 之外的东西),我们发现
mode
指定了产品种类,比如“书籍(books)”、“音乐(music)”、“dvd”或“厨房用具(kitchen)”——所用的术语必须与特定类别的列表相匹配。此处我们将使用的是美国(US)版本的
mode 。还有一种办法就是在 API
中包含
mode
的英国(UK)、德国(German)、日本(Japanese)版本 ——例如,各个条目分别就是“books-uk”、“books-de”、“books-jp”。
Amazon
的合作项目中的用户可以将他们的合作者 ID 放在
tag
参数中,有可能赚取与查询有关的购买的介绍费。所返回的每个条目的详细信息可以通过
type 参数来设置,允许值有“lite”或“heavy”
。最后一个参数(对 US 而言)就是
devtag ,它必须包含您的开发者令牌。剩下的参数很简单易懂,我们这里就不再介绍了。
一旦成功地完成查询,就会返回
KeywordSearchResponse ,或者如果查询没有找到匹配的条目,就会抛出一个 SOAP
Fault,其中包含说明情况的消息。实际响应信息是包装在
ProductInfo
结构中的。除了显而易见的
TotalResults 和
TotalPages
之外,我们还对
Details
组件感兴趣。对于每个结果,
DetailsArray 中的
Details
结构的内容都依赖于模式产品种类,正如 API
文档中所指定的。(请参阅
参考资料)。
现在,让我们来编写一个执行测试查询的简单应用程序(请参见 清单 2)。
清单 2. amazon_soap_test.py —— 用于测试 Amazon Web 服务 API 的简单 Python 代码。
#!/usr/bin/python
import SOAPpy
url = 'http://soap.amazon.com/schemas3/AmazonWebServices.wsdl'
proxy = SOAPpy.WSDL.Proxy(url)
# show methods retrieved from WSDL
print '%d methods in WSDL:' % len(proxy.methods) + '\\n'
for key in proxy.methods.keys():
print key
print
# search request
_query = 'spotted owl'
request = { 'keyword': _query,
'page': '1',
'mode': 'books',
'tag': '',
'type': 'lite',
'devtag': 'INSERT YOUR TOKEN HERE' }
results = proxy.KeywordSearchRequest(request)
# display results
print 'Amazon.com search for " ' + _query + ' "\\n'
print 'total pages of results (max 10 per page): ' + str(results.TotalPages)
print 'total results: ' + str(results.TotalResults) + '\\n'
# only show first result here
if (results.TotalResults > 0):
print 'displaying first result (of %s):\\n' %results.TotalResults
details = results.Details[0]
# we must use the _keys() method of SOAPpy Types.py for arrayType
for key in details._keys():
print key + ': ' + details[key]
print
|
在导入 SOAPpy
库之后,我们使用这个库来为发布在各自站点上的 WSDL 中的 Amazon Web 服务 API 创建一个代理。SOAPpy 中的
WSDL.Proxy
为访问通过 WSDL 定义的 API
提供了功能强大的工具。在使用代理进行任何调用之前,我们先将 Amazon API
中公开的方法列表打印出来。这样一来,正如 WSDL 中所指定的和 API 文档中所描述的一样,调用 SOAP
方法就与构造合适参数并在代理对象上调用方法一样简单了。在本例下,我们创建一个
request
结构,然后使用适当的参数将这个结构填充到我们的查询中去。
注意,我们使用的是
SOAPpy 的 Types.py 模块中的
_keys()
方法,因为结果的详细情况(details)组件是 SOAP
arrayType ,它通常映射到 Python
字典。
0.10.4 版本的 SOAPpy 在试着从 URL 加载的 WSDL 定义时存在着一个错误。(请参见 清单 3)。
清单 3. 使用 SOAPpy 从 URL 加载 WSDL 的跟踪信息
Traceback (most recent call last):
File "./amazon_soap_test.py", line 21, in ?
proxy = SOAPpy.WSDL.Proxy(url)
File "/usr/lib/python2.3/site-packages/SOAPpy/WSDL.py", line 61, in __init__
self.wsdl = reader.loadFromStream(stream)
File "/usr/lib/python2.3/site-packages/SOAPpy/wstools/WSDLTools.py",
line 28, in loadFromStream
wsdl.location = file.name
AttributeError: addinfourl instance has no attribute 'name'
|
直接从 URL 加载 WSDL
需要能够快速修复 SOAPpy
0.10.4 中的 WSDL.py 模块——如
清单 4 所示——您主要是将赋值调用从
reader.loadFromStream(stream) 更改到了
reader.loadFromURL(wsdlsource) 。
清单 4. 展示快速修复 SOAPpy 0.10.4 中的 WSDL.py 的不同之处
60,61c60
< stream = urllib.urlopen(wsdlsource)
< self.wsdl = reader.loadFromStream(stream)
---
> self.wsdl = reader.loadFromURL(wsdlsource)
|
瞧,行了!我们的小测试应用程序正常工作了(请参见 清单 5)。当然了,不修改 SOAPpy,我们也能使用浏览器从 Amazon.com 中检索到 WSDL 文件(请参阅 参考资料),然后从本地文件中进行加载——这对 SOAPpy 的 0.10.4 版本同样是有效的。
清单 5. amazon_soap_test.py 的输出结果。
[scott@baal amazon_api]$ python amazon_soap_test.py
26 methods in WSDL:
BlendedSearchRequest
WishlistSearchRequest
ClearShoppingCartRequest
MarketplaceSearchRequest
BrowseNodeSearchRequest
SimilaritySearchRequest
SkuSearchRequest
SellerSearchRequest
SellerProfileSearchRequest
ActorSearchRequest
AsinSearchRequest
ListManiaSearchRequest
AuthorSearchRequest
GetShoppingCartRequest
PowerSearchRequest
ExchangeSearchRequest
DirectorSearchRequest
TextStreamSearchRequest
ModifyShoppingCartItemsRequest
KeywordSearchRequest
ArtistSearchRequest
UpcSearchRequest
GetTransactionDetailsRequest
AddShoppingCartItemsRequest
RemoveShoppingCartItemsRequest
ManufacturerSearchRequest
Amazon.com search for " spotted owl "
total pages of results (max 10 per page): 6
total results: 52
displaying first result (of 52):
Asin: 0060248912
ImageUrlSmall: http://images.amazon.com/images/P/0060248912.01.THUMBZZZ.jpg
ProductName: There's an Owl in the Shower
ListPrice: $14.95
Availability: THIS TITLE IS CURRENTLY NOT AVAILABLE.
If you would like to purchase this title, we recommend
that you occasionally check this page to see if it has become available.
ReleaseDate: September, 1995
Catalog: Book
<SOAPpy.Types.typedArrayType at 1084200524>
Manufacturer: Harpercollins Juvenile Books
Url: http://www.amazon.com/exec/obidos/ASIN/0060248912/?dev-t=
D1V63VYIH286CL%26camp=2025%26link_code=sp1
UsedPrice: $1.25
ImageUrlMedium: http://images.amazon.com/images/P/0060248912.01.MZZZZZZZ.jpg
ImageUrlLarge: http://images.amazon.com/images/P/0060248912.01.LZZZZZZZ.jpg
OurPrice: $14.95
|
从
清单 5
的第一部分中您可以看到,有 26 个请求方法定义在 Amazon API
的 WSDL 中。关于参数和返回结构的详细消息可以在 Amazon 中的 WSDL 和开发者文档中找到。尽管代码无疑可以被扩展以执行更为高级的查询,但是在本例中我们仅使用
KeywordSearchRequest
方法。
现在我们可以利用关于 Amazon Web 服务 API 的知识来做些有用的事情。Python 经常被很多人责备,认为它在 GUI 创建的领域中跟不上其他理解 SOAP 的语言。其实,利用 Python 可以很容易地创建出强大且真正意义上跨平台的用户界面。为了证明这一点,我们将使用 Python 和 wxPython UI 库来创建一个小 GUI 应用程序,以便执行关键字搜索来查找书籍(book)、音乐(music)和 dvd。 图 1展示了我们运行中的 GUI。
选定产品种类(在 Amazon 中也称为 模式),输入您的关键字查询(各个关键字由空格格开)并点击‘Search Amazon’按钮。您的网络集线器上的灯就会开始闪烁,几秒钟后,‘Query Results’列表框就将添满。选择一个条目来查看详细信息,如果可能的话,查看条目的图像。为了简单起见,我们只获取不超过 10 个条目的第一页。这很容易扩展。
清单 6 展示了我们的应用程序代码。基本上,它是我们的测试应用程序,嵌入到一个用户界面中,并扩展为搜索音乐(music)和 dvd,并显示所获取条目的详细信息以及图像。
清单 6. amazon_widget.py —— Amazon.com 搜索 GUI 的 Python 代码.
#!/usr/bin/python
# setup SOAP proxy
import SOAPpy
file = 'AmazonWebServices.wsdl'
amazon = SOAPpy.WSDL.Proxy(file)
# import the wxPython libraries
import wxPython.wx
from wxPython.wx import *
import urllib
# developer token for amazon api
DEV_TOKEN = 'INSERT YOUR TOKEN HERE'
# event ids
ID_SEARCH = 100
ID_SELCHG = 101
#------------------------------------------------------------------------------
# amazonWidgetFrame - the main window for our app
#------------------------------------------------------------------------------
class amazonWidgetFrame(wxFrame):
def __init__(self, parent, ID, title):
wxFrame.__init__(self, parent, ID, title, wxDefaultPosition, wxSize(500, 475))
# add a status bar
self.CreateStatusBar()
self.SetStatusText("")
# add other widgets to frame
frame_box = wxBoxSizer(wxVERTICAL)
# query part
query_box = wxBoxSizer(wxHORIZONTAL)
# set the mode (product line to search)
mode_box =
wxStaticBoxSizer(wxStaticBox(self, -1, "Product Line"), wxVERTICAL)
self.mode_books =
wxRadioButton(self, -1, "books",
wxDefaultPosition, wxDefaultSize, wxRB_GROUP)
self.mode_music = wxRadioButton(self, -1, "music",
wxDefaultPosition, wxDefaultSize)
self.mode_dvds = wxRadioButton(self, -1, "dvd",
wxDefaultPosition, wxDefaultSize)
mode_box.Add(self.mode_books, 0, wxLEFT|wxRIGHT, 10)
mode_box.Add(self.mode_music, 0, wxLEFT|wxRIGHT, 10)
mode_box.Add(self.mode_dvds, 0, wxLEFT|wxRIGHT, 10)
query_box.Add(mode_box, 0, wxALL, 5)
# edit and button
qtext_box =
wxStaticBoxSizer(wxStaticBox(self, -1, "Enter Query"), wxHORIZONTAL)
self.search_text =
wxTextCtrl(self, -1, "python web", wxDefaultPosition, (200, -1))
qtext_box.Add(self.search_text, 1, wxALL, 5)
qtext_box.Add(wxButton(self, ID_SEARCH, "Search Amazon"), 0, wxALL, 5)
query_box.Add(qtext_box, 0, wxALL, 5)
frame_box.Add(query_box, 0, wxBOTTOM, 5)
# results part
results_box = wxBoxSizer(wxHORIZONTAL)
# details
details_box =
wxStaticBoxSizer(wxStaticBox(self, -1, "Query Results"), wxVERTICAL)
self.results_list =
wxListBox(self, ID_SELCHG, wxDefaultPosition, (300,175),
[], wxLB_ALWAYS_SB)
details_box.Add(self.results_list, 0, wxALL, 5)
hbox = wxBoxSizer(wxHORIZONTAL)
tbox = wxBoxSizer(wxVERTICAL)
tbox.Add(wxStaticText(self, -1, "Author/Artist: "), 0, wxLEFT, 5)
tbox.Add(wxStaticText(self, -1, ""), 0, wxLEFT, 5)
hbox.Add(tbox, 0)
hbox.Add(20,0,1)
self.authors = wxListBox(self, -1, wxDefaultPosition, (200,65), [])
hbox.Add(self.authors, 0, wxALIGN_RIGHT)
details_box.Add(hbox, 0, wxBOTTOM, 5)
hbox = wxBoxSizer(wxHORIZONTAL)
hbox.Add(wxStaticText(self, -1, "Release Date: "), 0, wxLEFT, 5)
self.release_date = wxStaticText(self, -1, " ",
wxDefaultPosition, wxDefaultSize)
hbox.Add(self.release_date, 0)
details_box.Add(hbox, 0)
hbox = wxBoxSizer(wxHORIZONTAL)
hbox.Add(wxStaticText(self, -1, "List Price: "), 0, wxLEFT, 5)
self.list_price = wxStaticText(self, -1, " ",
wxDefaultPosition, wxDefaultSize)
hbox.Add(self.list_price, 0)
details_box.Add(hbox, 0)
hbox = wxBoxSizer(wxHORIZONTAL)
hbox.Add(wxStaticText(self, -1, "Amazon.com Price: "), 0, wxLEFT, 5)
self.amazon_price = wxStaticText(self, -1, " ",
wxDefaultPosition, wxDefaultSize)
hbox.Add(self.amazon_price, 0)
details_box.Add(hbox, 0, wxBOTTOM, 5)
results_box.Add(details_box, 0, wxALL, 5)
# image
image_box =
wxStaticBoxSizer(wxStaticBox(self, -1, "Product Image"), wxHORIZONTAL)
# inline class for drawing our product image
class amazonImagePanel(wxPanel):
def __init__(self, parent, id, position, size):
wxPanel.__init__(self, parent, id, position, size)
self.parent = parent
self.image = None
EVT_PAINT(self, self.OnPaint)
def OnPaint(self, evt):
dc = wxPaintDC(self)
if self.image:
bitmap = wxBitmapFromImage(self.image)
dc.DrawBitmap(bitmap, 0, 0, false)
self.image_canvas = amazonImagePanel(self, -1, wxDefaultPosition, (130,150))
image_box.Add(self.image_canvas, 1, wxALL, 5)
results_box.Add(image_box, 0, wxALL, 5)
# finally...
frame_box.Add(results_box, 0, wxTOP, 5)
# add main sizer
self.SetSizer(frame_box)
# set event handlers
EVT_BUTTON(self, ID_SEARCH, self.OnSearch)
EVT_LISTBOX(self, ID_SELCHG, self.OnSelectionChange)
#---------------------------------------------------------------------
# build and call query - if returns OK, set the GUI fields
#---------------------------------------------------------------------
def OnSearch(self, event):
# fetch mode and query text
query = self.search_text.GetValue()
self.mode = 'books'
if self.mode_music.GetValue():
self.mode = 'music'
elif self.mode_dvds.GetValue():
self.mode = 'dvd'
request = { 'keyword': query, 'page': '1', 'mode': self.mode,
'tag': '', 'type': 'lite', 'devtag': DEV_TOKEN }
# do the query
try:
tmp_results = amazon.KeywordSearchRequest(request)
except SOAPpy.faultType:
self.SetStatusText('There were no exact matches for the search')
else:
self.results = tmp_results
self.SetStatusText('total results: ' + str(self.results.TotalResults) +
' (only showing first 10)')
# load up results box
items = []
images = []
for detail in self.results.Details:
items.append(detail['ProductName'])
images.append(detail['ImageUrlMedium'])
self.results_list.Set(items)
self.results_list.SetFirstItem(0)
# clear displayed image
self.authors.Set([])
self.release_date.SetLabel('')
self.list_price.SetLabel('')
self.amazon_price.SetLabel('')
self.image_canvas.image = None
self.image_canvas.Refresh()
# cache images
self.loadImages(images)
#---------------------------------------------------------------------
# whenever the product selection changes, update other fields
#---------------------------------------------------------------------
def OnSelectionChange(self, event):
# fetch selected item from results
sel = self.results_list.GetSelections()
detail = self.results.Details[sel[0]]
# set the author/artist field
items = []
if self.mode == 'books':
if 'Authors' in detail._keys():
authors = detail['Authors']
for author in authors:
items.append(author)
self.authors.Set(items)
elif self.mode == 'music':
if 'Artists' in detail._keys():
authors = detail['Artists']
for author in authors:
items.append(author)
self.authors.Set(items)
elif self.mode == 'dvd':
# only in 'heavy' search type
if 'Directors' in detail._keys():
authors = detail['Directors']
for author in authors:
items.append(author)
self.authors.Set(items)
# set release date and pricing fields
if 'ReleaseDate' in detail._keys():
self.release_date.SetLabel(detail['ReleaseDate'])
if 'ListPrice' in detail._keys():
self.list_price.SetLabel(detail['ListPrice'])
if 'OurPrice' in detail._keys():
self.amazon_price.SetLabel(detail['OurPrice'])
# fetch image
image_name = 'image_tmp_' + str(sel[0]) + '.jpg'
self.image_canvas.image = wxImage(image_name)
self.image_canvas.Refresh()
#---------------------------------------------------------------------
# retrieve from amazon.com and temporarily save images
#---------------------------------------------------------------------
def loadImages(self, images):
i = 0
for url in images:
image_name = 'image_tmp_' + str(i) + '.jpg'
pic = urllib.urlopen(url)
img = pic.read()
pic.close()
f = open(image_name,'wb')
f.write(img)
f.close()
i = i+1
#------------------------------------------------------------------------------
# simple wxApp
#------------------------------------------------------------------------------
class amazonWidgetApp(wxApp):
def __init__(self, parent):
wxApp.__init__(self, parent)
def OnInit(self):
# start GUI
frame = amazonWidgetFrame(NULL, -1, "Amazon.com SOAP Widget")
frame.Show(true)
self.SetTopWindow(frame)
return true
#------------------------------------------------------------------------------
# run this when module gets called
#------------------------------------------------------------------------------
if __name__ == '__main__':
# must call this for wxImage to load JPEGs
wx.wxInitAllImageHandlers()
app = amazonWidgetApp(0)
app.MainLoop()
|
我们选择使用 wxPython
库而不使用 TkInter 或者 Python 中所包含的其他更小的 GUI 工具包。在 wxWindows 库周围主要就是 Python
包装,它是一种非常快速以及完完全全跨平台的窗口操作框架(windowing
framework)。为了演示创建 GUI
的目的,我们手工操作控制布局,而不使用可用的 wxWindows WYSIWYG
布局工具,比如 wxDesigner。这样就形成了
amazonFrame.__init__( )
方法中的大量代码。我们广泛应用
wxBoxSizer
控件来对有趣的控件的布局。
需要引起注意的是,由于 wxWindows 事件环(event loop)的线程问题,需要在导入 wxPython 之前导入 SOAPpy 并创建代理。而且,因为启动执行的原因,我们要从本地文件导入而不是从 Amazon.com 站点导入 WSDL。
当运行应用程序时,就创建了
amazonWidgetApp 并调用
MainLoop( )
来启动事件环。请留意
amazonWidgetFrame 的
__init__( )
方法内部定义的
amazonImagePanel
类。我们定义了两个主要的事件处理程序:
OnSearch( )
用于处理按下 “Search Amazon” 按钮,
OnSelectionChange( )
用于处理 “Query Results” 列表框中的条目选择。
OnSearch( ) 获取 GUI 中的
模式设置,构建请求结构,然后调用
amazon SOAPpy 代理上的
KeywordSearchRequest SOAP
方法。SOAP 方法调用是在
try - except
块中执行的,一般来说,这种方式比较好,但这里我们这样做只是捕捉 SOAP.faultType
异常。我们应该检查异常类型,但此处我们将假定它意味着没有找到相应结果。如果所有这一切都正常,我们就保留
amazonWidgetFrame
中的
self.results 属性,填充“Query Results”列表框,然后从 Amazon.com
中获取结果详细信息中所引用的全部图像。
OnSelectionChange( ) 从
self.results
中获取适当的详细信息并将各自的组件填充到
amazonWidgetFrame
上去,最后加载正确的图像副本并执行重新布局。
- 您可以参阅本文在 developerWorks 全球站点上的
英文原文.
-
参与有关本文的
讨论论坛。(您也可以单击文章上方或下方的
讨论来访问论坛。)
- 在
Amazon.com Web
服务注册,以获取一个免费开发者令牌并下载开发者工具包。工具包包括文档、SOAP WSDL
以及用其他编程语言编写的示例和工具。
- 直接从
http://soap.amazon.com/schemas3/AmazonWebServices.wsdl
站点检索 Amazon Web API WSDL 文件。
- 查看 Python 世界的
www.Python.org
站点,其中包括了标准文档以及供下载的 Python。
- 登录
wxPython.org
获取 wxPython 的最新副本、文档以及其他资源
。
- 在
Google
Web 服务 API阅读本系列先前的专栏或查阅
此系列中所有的 Python Web
服务开发者专栏。

Scott Archer 是一位软件架构师,也是 GlowingOrb, Inc. 的创始人。他是一名软件工具开发人员,主要研究模型驱动的解决方案与核心业务流程的集成。Archer 在香港大学获得了 Computational Molecular Biology 的 M.Phil 学位。您可以通过 scott.archer at glowingorb.com 与 Archer 联系。

Uche Ogbuji 是一名顾问以及 Fourthought Inc. 的创始人,一名软件供应商与顾问,他专长于企业知识管理应用程序的 XML 解决方案。 Fourthought 开发出了 4Suite,XML 中间件的开放源码平台。您可以通过 uche.ogbuji@fourthought.com 与 Ogbuji 联系。