第 8 章 使用Web服务:XML-RPC、SOAP和REST
本章攻略:
- 查询本地XML-RPC服务器
- 编写一个多线程、多调用XML-RPC服务器
- 运行一个支持HTTP基本认证的XML-RPC服务器
- 使用REST从Flickr中收集一些照片信息
- 找出亚马逊S3 Web服务支持的SOAP方法
- 使用谷歌搜索定制信息
- 通过商品搜索API在亚马逊中搜索图书
8.1 简介
本章介绍一些有趣的Python攻略,通过三种不同的方式使用Web服务。这三种方式是“XML远程过程调用”(XML Remote Procedure Call,简称XML-RPC)、“简单对象访问协议”(Simple Object Access Protocol,简称SOAP)和“表现层状态转化”(Representational State Transfer,简称REST)。Web服务的目的,是让两个软件组件通过精心设计的协议在网络中交互。接口是机器可读的。多种不同的协议为Web服务的使用提供了便利。
本章包含这三种常用协议的示例。XML-RPC使用HTTP作为传输媒介,使用XML格式的内容通信。实现XML-RPC的服务器等待适配的客户端调用。客户端使用不同的参数调用服务器,执行远程过程。XML-RPC较为简单,但安全性不高。SOAP有一组丰富的协议,用于增强远程过程调用。REST是一种架构风格,让Web服务变得简单。REST架构中的操作使用HTTP请求方法完成,即GET
、POST
、PUT
和DELETE
。本章介绍这些Web服务协议和风格的用法,完成一些常见任务。
8.2 查询本地XML-RPC服务器
如果你经常做Web编程,可能会遇到这样的任务:从支持XML-RPC服务的网站中获取一些信息。在深入介绍XML-RPC服务之前,我们先架设一个XML-RPC服务器并和它通信。
8.2.1 准备工作
这个攻略要使用Python Supervisor程序。Supervisor广泛用于启动和管理可执行程序。Supervisor可以作为后台守护进程运行,能监控子进程,且子进程意外退出后能重启子进程。执行下面的命令即可安装Supervisor:
$ pip install supervisor
8.2.2 实战演练
我们要为Supervisor创建一个配置文件。这个攻略中提供了一个示例配置,定义了一个Unix HTTP服务器套接字和一些其他参数。注意rpcinterface:supervisor
部分,其中rpcinterface_factory
是用来和客户端通信的。
在program:8_2_ multithreaded_multicall_xmlrpc_server.py
部分,我们使用Supervisor配置了一个简单的服务器程序,指定了命令和一些其他参数。
代码清单8-1a是一个简单的Supervisor配置,如下所示:
[unix_http_server]
file=tmpsupervisor.sock ; (the path to the socket file)
chmod=0700 ; socket file mode (default 0700)
[supervisord]
logfile=tmpsupervisord.log
loglevel=info
pidfile=tmpsupervisord.pid
nodaemon=true
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
[program:8_2_multithreaded_multicall_xmlrpc_server.py]
command=python 8_2_multithreaded_multicall_xmlrpc_server.py ; the program (relative uses PATH, can take args)
process_name=%(program_name)s ; process_name expr (default %(program_name)s)
如果在你最喜欢的编辑器中创建上述Supervisor配置文件,调用这个文件就可以运行Supervisor。
现在,我们可以编写一个XML-RPC客户端,作为Supervisor的代理,获取运行中的进程信息。
代码清单8-1b是查询XML-RPC本地服务器的代码,如下所示:
#!usrbin/env python
# Python Network Programming Cookbook -- Chapter - 8
# This program is optimized for Python 2.7.
# It may run on any other version with/without modifications.
import supervisor.xmlrpc
import xmlrpclib
def query_supervisr(sock):
transport = supervisor.xmlrpc.SupervisorTransport(None, None,
'unix://%s' %sock)
proxy = xmlrpclib.ServerProxy('http://127.0.0.1',
transport=transport)
print "Getting info about all running processes via Supervisord..."
print proxy.supervisor.getAllProcessInfo()
if __name__ == '__main__':
query_supervisr(sock='tmpsupervisor.sock')
运行这个Supervisor守护进程,会看到类似下面的输出:
chapter8$ supervisord
2013-09-27 16:40:56,861 INFO RPC interface 'supervisor' initialized
2013-09-27 16:40:56,861 CRIT Server 'unix_http_server' running
without any HTTP authentication checking
2013-09-27 16:40:56,861 INFO supervisord started with pid 27436
2013-09-27 16:40:57,864 INFO spawned:
'8_2_multithreaded_multicall_xmlrpc_server.py' with pid 27439
2013-09-27 16:40:58,940 INFO success:
8_2_multithreaded_multicall_xmlrpc_server.py entered RUNNING state,
process has stayed up for > than 1 seconds (startsecs)
注意,运行后启动了子进程8_2_multithreaded_multicall_xmlrpc_server.py
。
现在,如果运行客户端代码,它会查询Supervisor的XML-RPC服务器接口,列出运行中的进程,如下所示:
$ python 8_1_query_xmlrpc_server.py
Getting info about all running processes via Supervisord...
[{'now': 1380296807, 'group':
'8_2_multithreaded_multicall_xmlrpc_server.py', 'description': 'pid
27439, uptime 0:05:50', 'pid': 27439, 'stderr_logfile':
'tmp8_2_multithreaded_multicall_xmlrpc_server.py-stderr---
supervisor-i_VmKz.log', 'stop': 0, 'statename': 'RUNNING', 'start':
1380296457, 'state': 20, 'stdout_logfile':
'tmp8_2_multithreaded_multicall_xmlrpc_server.py-stdout---
supervisor-eMuJqk.log', 'logfile':
'tmp8_2_multithreaded_multicall_xmlrpc_server.py-stdout---
supervisor-eMuJqk.log', 'exitstatus': 0, 'spawnerr': '', 'name':
'8_2_multithreaded_multicall_xmlrpc_server.py'}]
8.2.3 原理分析
这个攻略要在后台运行Supervisor守护进程(通过rpcinterface
配置)。Supervisor会启动另一个XML-RPC服务器,即8_2_ multithreaded_multicall_xmlrpc_server.py
。
客户端代码中定义了query_supervisr()
方法,其参数是Supervisor套接字。在这个方法中,使用Unix套接字路径创建了一个SupervisorTransport
实例。然后,实例化xmlrpclib
模块中的ServerProxy
类创建了一个XML-RPC服务器代理。传给ServerProxy
类构造方法的参数是服务器地址和前面创建的transport
。
随后,XML-RPC服务器代理调用Supervisor中的getAllProcessInfo()
方法,把子进程的信息打印出来。打印的信息包括pid
、statename
和description
等。
8.3 编写一个多线程、多调用XML-RPC服务器
你可以让你的XML-RPC服务器同时接受多个调用。这意味着,多个函数调用可以只返回一个结果。而且,如果服务器支持多线程,服务器启动后还能在单个线程中执行更多的代码。此时,程序的主线程处于非阻塞模式中。
8.3.1 实战演练
我们可以定义一个ServerThread
类,继承自threading.Thread
类,而且还可以把这个类的一个属性设为SimpleXMLRPCServer
实例。这样就能接受多个调用了。
然后,我们可以定义两个函数:一个用来启动多线程、多调用XML-RPC服务器,另一个用于创建连接服务器的客户端。
代码清单8-2是编写多线程、多调用XML-RPC服务器所需的代码,如下所示:
#!usrbin/env python
# Python Network Programming Cookbook -- Chapter - 8
# This program is optimized for Python 2.7.
# It may run on any other version with/without modifications.
import argparse
import xmlrpclib
import threading
from SimpleXMLRPCServer import SimpleXMLRPCServer
# some trivial functions
def add(x,y):
return x+y
def subtract(x, y):
return x-y
def multiply(x, y):
return x*y
def divide(x, y):
return x/y
class ServerThread(threading.Thread):
def __init__(self, server_addr):
threading.Thread.__init__(self)
self.server = SimpleXMLRPCServer(server_addr)
self.server.register_multicall_functions()
self.server.register_function(add, 'add')
self.server.register_function(subtract, 'subtract')
self.server.register_function(multiply, 'multiply')
self.server.register_function(divide, 'divide')
def run(self):
self.server.serve_forever()
def run_server(host, port):
# server code
server_addr = (host, port)
server = ServerThread(server_addr)
server.start() # The server is now running
print "Server thread started. Testing the server..."
def run_client(host, port):
# client code
proxy = xmlrpclib.ServerProxy("http://%s:%s/" %(host, port))
multicall = xmlrpclib.MultiCall(proxy)
multicall.add(7,3)
multicall.subtract(7,3)
multicall.multiply(7,3)
multicall.divide(7,3)
result = multicall()
print "7+3=%d, 7-3=%d, 7*3=%d, 7/3=%d" % tuple(result)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Multithreaded multicall XMLRPC Server/Proxy')
parser.add_argument('--host', action="store", dest="host", default='localhost')
parser.add_argument('--port', action="store", dest="port", default=8000, type=int)
# parse arguments
given_args = parser.parse_args()
host, port = given_args.host, given_args.port
run_server(host, port)
run_client(host, port)
运行这个脚本后,会看到类似下面的输出:
$ python 8_2_multithreaded_multicall_xmlrpc_server.py --port=8000
Server thread started. Testing the server...
localhost - - [25/Sep/2013 17:38:32] "POST HTTP1.1" 200 -
7+3=10, 7-3=4, 7*3=21, 7/3=2
8.3.2 原理分析
在这个攻略中,我们定义了ServerThread
类,继承自Python线程库中的Thread
类。这个子类初始化时把server
属性设为一个SimpleXMLRPCServer
服务器实例。XML-RPC服务器的地址可以在命令行中指定。为了启用多调用功能,我们在服务器实例上调用了register_multicall_functions()
方法。
然后把四个简单的函数注册到XML-RPC服务器上:add()
、subtract()
、multiply()
和divide()
。这几个函数的作用正如其名称所示。
为了启动服务器,要把主机和端口传给run_server()
函数。在这个函数中,使用前面说明的ServerThread
类创建了一个服务器实例。然后在这个服务器实例上调用start()
方法启动XML-RPC服务器。
再看客户端。run_client()
函数同样从命令行中获取主机和端口。然后使用xmlrpclib
中的ServerProxy
类创建一个XML-RPC服务器代理实例。再把这个代理实例传给MultiCall
类,创建一个实例multicall
。现在,可以运行前面定义的四个RPC方法了,即add()
、subtract()
、multiply()
和divide()
。最后,我们只通过一次调用(multicall()
)获取结果,再把结果中的元组在一行中打印出来。
8.4 运行一个支持HTTP基本认证的XML-RPC服务器
有时,你需要在XML-RPC服务器中实现认证功能。这个攻略介绍了一个简单的示例,说明如何在XML-RPC服务器中实现HTTP基本认证。
8.4.1 实战演练
我们可以定义一个SimpleXMLRPCServer
类的子类,重新定义请求处理方法,以便当请求进入时,和指定的登录凭据比对。
代码清单8-3a是在XML-RPC服务器中实现HTTP基本认证的代码,如下所示:
#!usrbin/env python
# Python Network Programming Cookbook -- Chapter - 8
# This program is optimized for Python 2.7.
# It may run on any other version with/without modifications.
import argparse
import xmlrpclib
from base64 import b64decode
from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler
class SecureXMLRPCServer(SimpleXMLRPCServer):
def __init__(self, host, port, username, password, args, *kargs):
self.username = username
self.password = password
# authenticate method is called from inner class
class VerifyingRequestHandler(SimpleXMLRPCRequestHandler):
# method to override
def parse_request(request):
if SimpleXMLRPCRequestHandler.parse_request(request):
# authenticate
if self.authenticate(request.headers):
return True
else:
# if authentication fails return 401
request.send_error(401, 'Authentication failed, Try agin.')
return False
# initialize
SimpleXMLRPCServer.__init__(self, (host, port), requestHandler=VerifyingRequestHandler, args, *kargs)
def authenticate(self, headers):
headers = headers.get('Authorization').split()
basic, encoded = headers[0], headers[1]
if basic != 'Basic':
print 'Only basic authentication supported'
return False
secret = b64decode(encoded).split(':')
username, password = secret[0], secret[1]
return True if (username == self.username and password == self.password) else False
def run_server(host, port, username, password):
server = SecureXMLRPCServer(host, port, username, password)
# simple test function
def echo(msg):
"""Reply client in uppser case """
reply = msg.upper()
print "Client said: %s. So we echo that in uppercase: %s" %(msg, reply)
return reply
server.register_function(echo, 'echo')
print "Running a HTTP auth enabled XMLRPC server on %s:%s..." %(host, port)
server.serve_forever()
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Multithreaded multicall XMLRPC Server/Proxy')
parser.add_argument('--host', action="store", dest="host", default='localhost')
parser.add_argument('--port', action="store", dest="port", default=8000, type=int)
parser.add_argument('--username', action="store", dest="username", default='user')
parser.add_argument('--password', action="store", dest="password", default='pass')
# parse arguments
given_args = parser.parse_args()
host, port = given_args.host, given_args.port
username, password = given_args.username, given_args.password
run_server(host, port, username, password)
运行这个脚本后,默认会看到如下输出:
$ python 8_3a_xmlrpc_server_with_http_auth.py
Running a HTTP auth enabled XMLRPC server on localhost:8000...
Client said: hello server.... So we echo that in uppercase: HELLO
SERVER...
localhost - - [27/Sep/2013 12:08:57] "POST RPC2 HTTP1.1" 200 -
现在,我们来创建一个简单的客户端代理,使用的登录凭据和服务器一样。
代码清单8-3b是XML-RPC客户端的代码,如下所示:
#!usrbin/env python
# Python Network Programming Cookbook -- Chapter - 8
# This program is optimized for Python 2.7.
# It may run on any other version with/without modifications.
import argparse
import xmlrpclib
def run_client(host, port, username, password):
server = xmlrpclib.ServerProxy('http://%s:%s@%s:%s' %(username, password, host, port, ))
msg = "hello server..."
print "Sending message to server: %s " %msg
print "Got reply: %s" %server.echo(msg)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Multithreaded multicall XMLRPC Server/Proxy')
parser.add_argument('--host', action="store", dest="host", default='localhost')
parser.add_argument('--port', action="store", dest="port", default=8000, type=int)
parser.add_argument('--username', action="store", dest="username", default='user')
parser.add_argument('--password', action="store", dest="password", default='pass')
# parse arguments
given_args = parser.parse_args()
host, port = given_args.host, given_args.port
username, password = given_args.username, given_args.password
run_client(host, port, username, password)
运行这个脚本后,会看到如下输出:
$ python 8_3b_xmprpc_client.py
Sending message to server: hello server...
Got reply: HELLO SERVER...
8.4.2 原理分析
在服务器脚本中,继承SimpleXMLRPCServer
类定义了SecureXMLRPCServer
子类。在这个子类的初始化代码中,定义了用于拦截请求的VerifyingRequestHandler
类,使用authenticate()
方法做基本认证。
我们把HTTP请求的首部传给authenticate()
方法,检查Authorization
首部的值。如果值等于Basic
,就使用base64
标准模块中的b64decode()
方法解码编码后的密码。提取出用户名和密码后,再和服务器指定的登录凭据比对。
在run_server()
函数中定义了一个简单的echo()
子函数,将其注册到SecureXMLRPCServer
实例上。
在客户端脚本中,run_client()
函数的参数是服务器地址和登录凭据,再把这些参数传给ServerProxy
类构造方法,创建一个实例。然后使用echo()
方法发送一行消息。
8.5 使用REST从Flickr中收集一些照片信息
很多网站都通过REST API提供Web服务接口。Flickr这个著名的照片分享网站就提供了REST接口。让我们来收集一些照片信息,构建一个特殊的数据库或者开发照片相关的应用。
8.5.1 实战演练
我们需要REST URL发起HTTP请求。简单起见,这些URL硬编码在脚本中。我们可以使用第三方模块requests
发起REST请求。requests
模块提供了一些便利的方法,包括get()
、post()
、put()
和delete()
。
为了和Flickr的Web服务通信,你需要注册一个账户,然后获取API密钥。API密钥可写入local_settings.py文件,或者在命令行中提供。
代码清单8-4是使用REST从Flickr中收集照片信息所需的代码,如下所示:
#!usrbin/env python
# Python Network Programming Cookbook -- Chapter - 8
# This program is optimized for Python 2.7.
# It may run on any other version with/without modifications.
# Supply Flickr API key via local_settings.py
import argparse
import json
import requests
try:
from local_settings import flickr_apikey
except ImportError:
pass
def collect_photo_info(api_key, tag, max_count):
"""Collects some interesting info about some photos from Flickr.com for a given tag """
photo_collection = []
url = "http://api.flickr.com/services/rest/?method=flickr.photos.search&tags=%s&format=json&nojsoncallback=1&api_key=%s" %(tag, api_key)
resp = requests.get(url)
results = resp.json()
count = 0
for p in results['photos']['photo']:
if count >= max_count:
return photo_collection
print 'Processing photo: "%s"' % p['title']
photo = {}
url = "http://api.flickr.com/services/rest/?method=flickr.photos.getInfo&photo_id=" + p['id'] + "&format=json&nojsoncallback=1&api_key=" + api_key
info = requests.get(url).json()
photo["flickrid"] = p['id']
photo["title"] = info['photo']['title']['content']
photo["description"] = info['photo']['description']['content']
photo["page_url"] = info['photo']['urls']['url'][0]['content']
photo["farm"] = info['photo']['farm']
photo["server"] = info['photo']['server']
photo["secret"] = info['photo']['secret']
# comments
numcomments = int(info['photo']['comments']['content'])
if numcomments:
#print " Now reading comments (%d)..." % numcomments
url = "http://api.flickr.com/services/rest/?method=flickr.photos.comments.getList&photo_id=" + p['id'] + "&format=json&nojsoncallback=1&api_key=" + api_key
comments = requests.get(url).json()
photo["comment"] = []
for c in comments['comments']['comment']:
comment = {}
comment["body"] = c['content']
comment["authorid"] = c['author']
comment["authorname"] = c['authorname']
photo["comment"].append(comment)
photocollection.append(photo)
count = count + 1
return photo_collection
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Get photo info from Flickr')
parser.add_argument('--api-key', action="store", dest="api_key", default=flickr_apikey)
parser.add_argument('--tag', action="store", dest="tag", default='Python')
parser.add_argument('--max-count', action="store", dest="max_count", default=3, type=int)
# parse arguments
given_args = parser.parse_args()
api_key, tag, max_count = given_args.api_key, given_args.tag, given_args.max_count
photo_info = collect_photo_info(api_key, tag, max_count)
for photo in photo_info:
for k,v in photo.iteritems():
if k == "title":
print "Showiing photo info...."
elif k == "comment":
"\tPhoto got %s comments." %len(v)
else:
print "\t%s => %s" %(k,v)
Flickr API密钥可以放在local_settings.py文件中,也可在命令行中提供(通过--api-key
参数)。除了API密钥,还可指定搜索标签和结果的最大数量。默认情况下,这个脚本搜索Python
标签,结果限制为3个,如下面的输出所示:
$ python 8_4getflickr_photo_info.py
Processing photo: "legolas"
Processing photo: ""The Dance of the Hunger of Kaa""
Processing photo: "Rocky"
description => Stimson Python
Showiing photo info....
farm => 8
server => 7402
secret => 6cbae671b5
flickrid => 10054626824
page_url => http://www.flickr.com/photos/102763809@N03/10054626824/
description => " 'Good. Begins now the dance--the Dance of the
Hunger of Kaa. Sit still and watch.'
He turned twice or thrice in a big circle, weaving his head from right to left.
Then he began making loops and figures of eight with his body, and soft,
oozy triangles that melted into squares and five-sided figures, and
coiled mounds, never resting, never hurrying, and never stopping his
low humming song. It grew darker and darker, till at last the dragging,
shifting coils disappeared, but they could hear the rustle of the
scales."
(From "Kaa's Hunting" in "The Jungle Book" (1893) by Rudyard Kipling)
These old abandoned temples built around the 12th century belong to the
abandoned city which inspired Kipling's Jungle Book.
They are rising at the top of a mountain which dominates the jungle at
811 meters above sea level in the centre of the jungle of Bandhavgarh
located in the Indian state Madhya Pradesh.
Baghel King Vikramaditya Singh abandoned Bandhavgarh fort in 1617 when
Rewa, at a distance of 130 km was established as a capital.
Abandonment allowed wildlife development in this region.
When Baghel Kings became aware of it, he declared Bandhavgarh as their
hunting preserve and strictly prohibited tree cutting and wildlife
hunting...
Join the photographer at <a href="http://www.facebook.com/laurent.
goldstein.photography" rel="nofollow">www.facebook.com/laurent.goldstein.
photography</a>
© All photographs are copyrighted and all rights reserved.
Please do not use any photographs without permission (even for private
use).
The use of any work without consent of the artist is PROHIBITED and will
lead automatically to consequences.
Showiing photo info....
farm => 6
server => 5462
secret => 6f9c0e7f83
flickrid => 10051136944
page_url => http://www.flickr.com/photos/designldg/10051136944/
description => Ball Python
Showiing photo info....
farm => 4
server => 3744
secret => 529840767f
flickrid => 10046353675
page_url => http://www.flickr.com/photos/megzzdollphotos/10046353675/
8.5.2 原理分析
这个攻略演示了如何使用Flickr的REST API和它交互。在这个示例中,collect_photo_info()
函数有三个参数:Flickr API密钥、搜索标签和想要得到的搜索结果数量。
我们构建的第一个URL用于搜索照片。注意,在这个URL中,method
参数的值是flickr.photos.search
,希望得到的结果格式是JSON。
第一次调用get()
方法得到的结果存储在变量resp
中,然后在这个变量上调用json()
方法,将其转换成JSON格式。然后在一个循环中读取['photos']['photo']
中的JSON数据。得到的信息保存在photo_collection
列表中。在这个列表中,每张照片的信息由一个字典表示。字典的键从前面的JSON格式响应中提取,照片的信息从另一个GET
请求中获取。
注意,若想获取照片的评论,要再发起一个GET
请求,从JSON格式响应中的['comments']['comment']
元素获取。最后,把这些评论插入一个列表中,再添加到照片对应的字典里。
在__main__
块中,我们从photo_collection
列表中读取各字典,打印各张照片的一些有用信息。
8.6 找出亚马逊S3 Web服务支持的SOAP方法
如果你需要与实现了简单对象访问协议(SOAP)的Web服务交互,可以参考这个攻略。
8.6.1 准备工作
在这个攻略中我们可以使用第三方库SOAPpy
,执行下面的命令即可安装这个库:
$ pip install SOAPpy
8.6.2 实战演练
我们要创建一个代理对象,在调用服务器上的方法之前,先找出支持哪些方法。
在这个攻略中,我们要和亚马逊S3存储服务交互。我们已经有了Web服务API的测试URL。你需要有API密钥才能执行这个简单的任务。
代码清单8-5是搜索亚马逊S3 Web服务支持的SOAP方法所需的代码,如下所示:
#!usrbin/env python
# Python Network Programming Cookbook -- Chapter - 8
# This program requires Python 2.7 or any later version
import SOAPpy
TEST_URL = 'http://s3.amazonaws.com/ec2-downloads/2009-04-04.ec2.wsdl'
def list_soap_methods(url):
proxy = SOAPpy.WSDL.Proxy(url)
print '%d methods in WSDL:' % len(proxy.methods) + '\n'
for key in proxy.methods.keys():
print "Key Name: %s" %key
print "Key Details:"
for k,v in proxy.methods[key].__dict__.iteritems():
print "%s ==> %s" %(k,v)
break
if __name__ == '__mani__':
list_soap_methods(TEST_URL)
运行这个脚本后,会打印出支持“Web服务定义语言”(Web Services Definition Language,简称WSDL)的可用方法总数,以及随意一个方法的详情,如下所示:
$ python 8_5_search_amazonaws_with_SOAP.py
homefaruq/env/lib/python2.7/site-packages/wstools/XMLSchema.py:1280:
UserWarning: annotation is ignored
warnings.warn('annotation is ignored')
43 methods in WSDL:
Key Name: ReleaseAddress
Key Details:
encodingStyle ==> None
style ==> document
methodName ==> ReleaseAddress
retval ==> None
soapAction ==> ReleaseAddress
namespace ==> None
use ==> literal
location ==> https://ec2.amazonaws.com/
inparams ==> [<wstools.WSDLTools.ParameterInfo instance at 0x8fb9d0c>]
outheaders ==> []
inheaders ==> []
transport ==> http://schemas.xmlsoap.org/soap/http
outparams ==> [<wstools.WSDLTools.ParameterInfo instance at 0x8fb9d2c>]
8.6.3 原理分析
在这个脚本中,定义了一个名为list_soap_methods()
的方法,其参数是一个URL。然后调用SOAPpy
模块中的WSDL.Proxy()
方法创建一个SOAP代理对象。可用的SOAP方法可通过这个代理对象的methods
属性获取。
然后遍历代理对象methods
属性中的键,获取各方法对应的键名。在for
循环中打印出某个具体SOAP方法的详情,包括键名和键的详情。
8.7 使用谷歌搜索定制信息
对很多人来说,每天都要使用谷歌搜索信息。我们来试试使用谷歌搜索一些信息。
8.7.1 准备工作
这个攻略要使用一个第三方Python库requests
,可以使用pip
安装,如下面的命令所示:
$ pip install requests
8.7.2 实战演练
谷歌的搜索API很复杂,你要注册一个账户,再按照指定的方式获取API密钥。简单起见,我们使用谷歌以前的简单异步JavaScript(AJAX)API,搜索Python相关的图书信息。
代码清单8-6是使用谷歌搜索定制信息所需的代码,如下所示:
#!usrbin/env python
# Python Network Programming Cookbook -- Chapter - 8
# This program requires Python 2.7 or any later version.
# It may run on any other version with/without modifications.
import argparse
import json
import urllib
import requests
BASE_URL = 'http://ajax.googleapis.com/ajax/services/search/web?v=1.0'
def get_search_url(query):
return "%s&%s" %(BASE_URL, query)
def search_info(tag):
query = urllib.urlencode({'q': tag})
url = get_search_url(query)
response = requests.get(url)
results = response.json()
data = results['responseData']
print 'Found total results: %s' % data['cursor']['estimatedResultCount']
hits = data['results']
print 'Found top %d hits:' % len(hits)
for h in hits:
print ' ', h['url']
print 'More results available from %s' % data['cursor']['moreResultsUrl']
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Search info from Google')
parser.add_argument('--tag', action="store", dest="tag", default='Python books')
# parse arguments
given_args = parser.parse_args()
search_info(given_args.tag)
运行这个脚本时,如果在--tag
参数中指定了搜索关键字,它会在谷歌中搜索,并打印出结果总数和点击量最多的四个网址,如下所示:
$ python 8_6_search_products_from_Google.py
Found total results: 12300000
Found top 4 hits:
https://wiki.python.org/moin/PythonBooks
http://www.amazon.com/Python-Languages-Tools-Programming-Books/b%3Fie%3DUTF8%26node%3D285856
http://pythonbooks.revolunet.com/
http://readwrite.com/2011/03/25/python-is-an-increasingly-popu
More results available from
http://www.google.com/search?oe=utf8&ie=utf8&source=uds&start=0&hl=en&q=Python+books
8.7.3 原理分析
在这个攻略中,我们定义了一个简短的函数get_search_url()
,使用BASE_URL
常量和查询字符串组成搜索URL。
执行搜索的函数search_info()
接收一个参数,即搜索关键字,然后构建查询字符串。requests
库的作用是提供get()
方法,得到的响应被转换成JSON格式。
我们从JSON格式数据的responseData
键中提取搜索结果。预期的结果和点击量从相应的键中获取。然后把点击量排在前四位的URL打印出来。
8.8 通过商品搜索API在亚马逊中搜索图书
如果你想在亚马逊中搜索商品,并把它们添加到自己的网站或应用中,可以参考这个攻略。我们要介绍如何在亚马逊中搜索图书。
8.8.1 准备工作
这个攻略要用到一个第三方Python库bottlenose
。这个库可使用pip
安装,如下面的命令所示:
$ pip install bottlenose
首先,要把亚马逊账户的访问密钥、私钥和联盟ID保存在文件local_settings.py中。本书附带的源码提供了一个示例设置文件。你也可以修改下面这个脚本,把设置写进去。
8.8.2 实战演练
我们可以使用bottlenose
库实现亚马逊商品搜索API。
代码清单8-7是使用商品搜索API在亚马逊中搜索图书所需的代码,如下所示:
#!usrbin/env python
# Python Network Programming Cookbook -- Chapter - 8
# This program requires Python 2.7 or any later version
import argparse
import bottlenose
from xml.dom import minidom as xml
try:
from local_settings import amazon_account
except ImportError:
pass
ACCESS_KEY = amazon_account['access_key']
SECRET_KEY = amazon_account['secret_key']
AFFILIATE_ID = amazon_account['affiliate_id']
def search_for_books(tag, index):
"""Search Amazon for Books """
amazon = bottlenose.Amazon(ACCESS_KEY, SECRET_KEY, AFFILIATE_ID)
results = amazon.ItemSearch(
SearchIndex = index,
Sort = "relevancerank",
Keywords = tag
)
parsed_result = xml.parseString(results)
all_items = []
attrs = ['Title','Author', 'URL']
for item in parsed_result.getElementsByTagName('Item'):
parse_item = {}
for attr in attrs:
parse_item[attr] = ""
try:
parse_item[attr] = item.getElementsByTagName(attr)[0].childNodes[0].data
except:
pass
all_items.append(parse_item)
return all_items
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Search info from Amazon')
parser.add_argument('--tag', action="store", dest="tag", default='Python')
parser.add_argument('--index', action="store", dest="index", default='Books')
# parse arguments
given_args = parser.parse_args()
books = search_for_books(given_args.tag, given_args.index)
for book in books:
for k,v in book.iteritems():
print "%s: %s" %(k,v)
print "-" * 80
运行这个脚本时如果提供了搜索关键字和目录,会看到一些类似下面的输出:
$ python 8_7_search_amazon_for_books.py --tag=Python --index=Books
URL: http://www.amazon.com/Python-In-Day-Basics-Coding/dp/tech-data/1
490475575%3FSubscriptionId%3DAKIAIPPW3IK76PBRLWBA%26tag%3D7052-6929-
7878%26linkCode%3Dxm2%26camp%3D2025%26creative%3D386001%26creativeASIN%3D1490475575
Author: Richard Wagstaff
Title: Python In A Day: Learn The Basics, Learn It Quick, Start Coding
Fast (In A Day Books) (Volume 1)
-------------------------------------------------------------------------
-------
URL: http://www.amazon.com/Learning-Python-Mark-Lutz/dp/tech-data/1449355
730%3FSubscriptionId%3DAKIAIPPW3IK76PBRLWBA%26tag%3D7052-6929-7878%26link
Code%3Dxm2%26camp%3D2025%26creative%3D386001%26creativeASIN%3D1449355730
Author: Mark Lutz
Title: Learning Python
-------------------------------------------------------------------------
-------
URL: http://www.amazon.com/Python-Programming-Introduction-Computer-
Science/dp/tech-data/1590282418%3FSubscriptionId%3DAKIAIPPW3IK76PBRLWBA%2
6tag%3D7052-6929-7878%26linkCode%3Dxm2%26camp%3D2025%26creative%3D386001%
26creativeASIN%3D1590282418
Author: John Zelle
Title: Python Programming: An Introduction to Computer Science 2nd
Edition
---------------------------------------------------------------------
-----------
8.8.3 原理分析
这个攻略使用第三方库bottlenose
中的Amazon
类创建了一个对象,使用商品搜索API在亚马逊中进行搜索。这些操作在顶层函数search_for_books()
中完成。在这个对象上调用ItemSearch()
方法时,分别把search_for_books()
函数的参数传给SearchIndex
和Keywords
键。搜索结果使用relevancerank
方式排序。
搜索结果使用xml
模块中的minidom
接口处理,这个接口提供了一个有用的parseString()
方法,得到的结果是解析后的树状数据结构。在这个数据结构上调用getElementsByTagName()
方法能获取全部搜索结果。然后遍历搜索结果,把属性存入parse_item
字典中。最后,把所有解析后的搜索结果都保存到all_items
列表中,返回给用户。