20.2 使用Python进行Web应用:创建一个简单的Web客户端

有一点需要记清楚,浏览器只是Web客户端的一种。任何一个通过向服务器端发送请求来获得数据的应用程序都被认为是“客户端”。当然,也可以建立其他的客户端从而在因特网上检索出文档和数据。这样做的一个重要原因就是浏览器的能力有限,也就是说,它主要用于查看并同其他Web站点交互。另一方面,一个客户端程序,有能力做得更多——它不仅可以下载数据,同时也可以存储、操作数据,甚至或可以将其传送到另外一个地方或者传给另外一个应用。

一个使用urllib模块下载或者访问Web上的信息的应用程序[使用urllib. urlopen()或者urllib. urlre-trieve()]可以被认为是简单的Web客户端。你所要做的就是提供一个有效的Web地址。

20.2.1 统一资源定位符

简单的Web应用包括使用被称为URL(统一资源定位器,Uniform Resource Locator)的Web地址。这个地址用来在Web上定位一个文档,或者调用一个CGI程序来为你的客户端产生一个文档。URL是大型标识符URI(统一资源标识,Uniform Resource Identifier)的一部分。这个超集是建立在已有的命名惯例基础上的。一个URL是一个简单的URI,使用已存在的协议或规划(也就是http, ftp等)作为地址的一部分。为了进一步描绘这些,我们将会引入非URL的URI,有时这些被成为URN(统一资源名称,Uniform Resource Name),但是在今天我们唯一使用的一种URI是URL,至于URI和URN你也许没有听到太多,这或许已被保存成XML标识符了。

如街道地址一样,Web地址也有一些结构。美国的街道地址通常是这种格式“号码街道名称”,例如“123主大街”。这个和其他国家不同,他们有自己的规则。URL使用这种格式:

20.2 使用Python进行Web应用:创建一个简单的Web客户端 - 图1

表20.1描述了各个部件。

20.2 使用Python进行Web应用:创建一个简单的Web客户端 - 图2

net_loc可以进一步拆分成多个部件,有些是必备的,其他的是可选部件,net_loc字符串如下:

20.2 使用Python进行Web应用:创建一个简单的Web客户端 - 图3

表20.2中分别描述了这些部件

20.2 使用Python进行Web应用:创建一个简单的Web客户端 - 图4

在这4个部件中,host主机名是最重要的。端口号只有在Web服务器运行其他非默认端口上时才会被使用(如果你不确定所使用的端口号,可以参考第16章)。

用户名和密码部分只有在使用FTP连接时候才有可能用到,因为即使是使用FTP,大多数的连接都是使用“匿名”这时是不需要用户名和密码的。

Python支持两种不同的模块,分别以不同的功能和兼容性来处理URL。一种是urlparse,另一种是urllib。这里我们将会简单的介绍下它们的功能。

20.2.2 urlparse模块

urlpasrse模块提供了操作URL字符串的基本功能。这些功能包括urlparse()、urlunparse()和urljoin().

20.2 使用Python进行Web应用:创建一个简单的Web客户端 - 图5

urlparse()将URL字符串拆分成如上所描述的一些主要部件。语法结构如下:

20.2 使用Python进行Web应用:创建一个简单的Web客户端 - 图6

urlparse()将urlstr解析成一个6元组(prot_sch、net_loc、 path、params、query、frag) 。这里的每个部件在上边已经描述过了。如果urlstr中没有提供默认的网络协议或下载规划时可以使用defProtSch。allowFrag标识一个URL是否允许使用零部件。下边是一个给定URL经urlparse()后的输出:

20.2 使用Python进行Web应用:创建一个简单的Web客户端 - 图7

urlunparse()的功能与urlpase()完全相反:它拼合一个6元组(prot_sch、net_loc、path、params、query、frag) -urltup,它可能是一个URL经urlparse()后的输出返回值。于是,我们可以用如下方式表示:

20.2 使用Python进行Web应用:创建一个简单的Web客户端 - 图8

你或许已经猜到了urlunpase()的语法

20.2 使用Python进行Web应用:创建一个简单的Web客户端 - 图9

在需要多个相关的URL时我们就需要使用urljoin()的功能了,如在一个Web页中生成的一系列页面的URL。Urljoin()的语法是:

20.2 使用Python进行Web应用:创建一个简单的Web客户端 - 图10

20.2 使用Python进行Web应用:创建一个简单的Web客户端 - 图11

urljoin()取得baseurl,并将其基路径(net_loc附加一个完整的路径,但是不包括终端的文件)与newurl连接起来。例如:

20.2 使用Python进行Web应用:创建一个简单的Web客户端 - 图12

在表20.3中可以找到urlparse的功能概述。

20.2.3 urllib模块

20.2 使用Python进行Web应用:创建一个简单的Web客户端 - 图13核心模块:urllib

urllib模块提供了所有你需要的功能,除非你计划写一个更加低层的网络客户端。urllib提供了一个高级的Web交流库,支持Web协议、HTTP、FTP和Gopher协议,同时也支持对本地文件的访问。urllib模块的特殊功能是利用上述协议下载数据(从因特网、局域网、主机上下载)。使用这个模块可以避免使用httplib、ftplib和gopherlib这些模块,除非你想用更低层的功能。在那些情况下这些模块都是可选择的(注意:大多数以*lib命名的模块用于客户端相关协议开发。并不是所有情况都是这样的,或许urllib应该被命名为“internetlib”或其他相似的名字)。

Urllib模块提供了在给定的URL地址下载数据的功能,同时也可以通过字符串的编码、解码来确保它们是有效URL字符串的一部分。我们接下来要谈的功能包括urlopen()、urlretrieve() 、 quote() 、 unquote() 、quote_plus()、unquote_plus()和urlencode()。我们可以使用urlopen()方法返回文件类型对象。你会觉得这些方法不陌生,因为在第9章我们已经涉及到了文件方面的内容。

  1. urllib. urlopen()

urlopen()打开一个给定URL字符串与Web连接,并返回了文件类的对象。语法结构如下:

20.2 使用Python进行Web应用:创建一个简单的Web客户端 - 图14

urlopen()打开urlstr所指向的URL。如果没有给定协议或者下载规划,或者文件规划早已传入,urlopen()则会打开一个本地的文件。

对于所有的HTTP请求,常见的请求类型是“GET”。在这些情况中,向Web服务器发送的请求字符串(编码键值或引用,如urlencode()函数的字符串输出(如下))应该是urlstr的一部分。

如果要求使用“POST”方法,请求的字符串(编码的)应该被放到postQueryData变量中。(要了解更多关于“GET”和”POST”方法的信息,请查看CGI应用编程部分的普通文档或者文本,这些我们在下边也会讨论)。GET和POST请求是向Web服务器上传数据的两种方法。

一旦连接成功,urlopen()将会返回一个文件类型对象,就像在目标路径下打开了一个可读文件。例如,如果我们的文件对象是f,那么我们的“句柄”将会支持可读方法,如f. read() 、f. readline() 、 f. readlines() 、f.close()和f.fileno()。

此外,f.info()方法可以返回MIME(多目标因特网邮件扩展,Multipurpose Internet Mail Extension)头文件。这个头文件通知浏览器返回的文件类型可以用哪类应用程序打开。例如,浏览器本身可以查看HTML(超文本标记语言,HyperText Markup Language),纯文本文件,生成(指由数据显示图像—译者注)PNG (Portable Network Graohics) 、JPEG (Joint Photographic Experts Group)或者GIF (GraphicsInterchange Format)文件。其他如多媒体文件、特殊类型文件需要通过扩展的应用程序才能打开。

最后,geturl()方法在考虑了所有可能发生的间接导向后,从最终打开的文件中获得真实的URL,这些文件类型对象的方法在表20.4中有描述。

20.2 使用Python进行Web应用:创建一个简单的Web客户端 - 图15

如果你打算访问更加复杂的URL或者想要处理更复杂的情况,如基于数字的权限验证、重定位、cookie等问题,我们建议你使用urllib2模块,这个在1. 6版本中有介绍(多数是试验模块)。它同时还有一个urlopen()函数,但也提供了其他的可以打开各种URL的函数和类。关于urllib2的更多信息,将会在本章的下一部分介绍。

  1. urllib.urlretrieve()

如果你对整个URL文档的工作感兴趣,urlretrieve()可以帮你快速处理一些繁重的工作。下面是urlretrieve()的语法:

20.2 使用Python进行Web应用:创建一个简单的Web客户端 - 图16

除了像urlopen()这样从URL中读取内容,urlretrieve()可以方便地将urlstr定位到的整个HTML文件下载到你本地的硬盘上。你可以将下载后的数据存成一个本地文件或者一个临时文件。如果该文件已经被复制到本地或者已经是一个本地文件,后续的下载动作将不会发生。

如果可能,downloadStatusHook这个函数将会在每块数据下载或传输完成后被调用。调用时使用下边三个参数:目前读入的块数、块的字节数和文件的总字节数。如果你正在用文本或图表向用户演示“下载状态”信息,这个函数将会是非常有用的。

urlretrieve()返回一个2元组,(filename, mime_hdrs) .filename是包含下载数据的本地文件名,mime_hdrs是对Web服务器响应后返回的一系列MIME文件头。要获得更多的信息,可以看mimetools的Message类。对本地文件来说mime_hdrs是空的。

关于urlretrieve()的简单应用,可以看11. 4 (grabweb.py)中的例子。在本章的20. 2小节中将会介绍urlretrieve()更深层的应用。

  1. Urllib.quote()和urllib.quote-plus()

quote()函数获取URL数据,并将其编码,从而适用于URL字符串中。尤其是一些不能被打印的或者不被Web服务器作为有效URL接收的特殊字符串必须被转换。这就是quote()函数的功能。quote*()函数的语法如下:

20.2 使用Python进行Web应用:创建一个简单的Web客户端 - 图17

逗号、下划线、句点、斜线和字母数字这类符号是不需要转化的。其他的则均需要转换。另外,那些不被允许的字符前边会被加上百分号(%)同时转换成16进制,例如:“%xx”, “XX”代表这个字母的ASCII码的十六进制值。当调用quote*()时,urldata字符串被转换成了一个可在URL字符串中使用的等价值。safe字符串可以包含一系列不能被转换的字符。默认的是斜线(/).

quote_plus()与quote()很像,另外它还可以将空格编码成(+)号。下边是一个使用quote()和quote_plus()的例子:

20.2 使用Python进行Web应用:创建一个简单的Web客户端 - 图18

  1. urllib.unquote()和urllib.unquote_plus()

也许和你猜到的一样,unquote()函数与quote()函数的功能完全相反——它将所有编码为“%xx”式的字母都转换成它们的ASCII码值。Unquote*()的语法如下:

20.2 使用Python进行Web应用:创建一个简单的Web客户端 - 图19

调用unquote()函数将会把urldata中所有的URL-编码字母都解码,并返回字符串。Unquote_plus()函数会将加号转换成空格符。

  1. urllib. urlencode()

在1. 5. 2版的Python中,urlopen()函数接收字典的键-值对,并将其编译成CGI请求的URL字符串的一部分。键值对的格式是“键=值”,以连接符(&)划分。更进一步,键和它们的值被传到quote_plus()函数中进行适当的编码。下边是urlencode()输出的一个例子:

20.2 使用Python进行Web应用:创建一个简单的Web客户端 - 图20

urllib和urlparse还有一些其他的功能,在这里我们就不一一概述了。阅读相关文档可以获得更多信息。

6.安全套接字层支持

在1. 6版中urllib模块通过安全套接字层(Secure Socket Layer, SSL)支持开放的HTTP连接socket模块的核心变化是增加并实现了SSL。随后,urllib和httplib模块被上传用于支持URL在“https”连接规划中的应用。除了那两个模块以外,其他的含有SSL的模块还有imaplib、poplib和smtplib.

在表20.5中可以看到关于本节讨论的urllib函数的总结。

20.2 使用Python进行Web应用:创建一个简单的Web客户端 - 图21

20.2.4 urllib模块

正如前面所提到的,urllib2可以处理更复杂URL的打开问题。一个例子就是有基本认证(登录名和密码)需求的Web站点。最简单的“获得已验证参数”的方法是使用前边章节中描述的URL部件net_loc,也就是说:http://user:passwd@www.python.org。这种解决方案的问题是不具有可编程性的。然而使用urllib2,我们可以通过两种不同的方式来解决这个问题。

我们可以建立一个基础认证处理器(urllib2. HTTPBasicAuthHandler),同时在基本URL或域上注册一个登录密码,这就意味着我们在Web站点上定义了个安全区域。(关于域的更多信息可以查看RFC2617 (HTTP认证:基本数字认证))。一旦完成这些,你可以安装URL打开器,通过这个处理器打开所有的URL。

另一个可选的办法就是当浏览器提示的时候,输入用户名和密码,这样就发送了一个带有适当用户请求的认证头。在20.1的例子中,我们可以很容易的区分出这两种方法。

逐行解释

1 ~ 7行

普通的初始化过程,外加几个为后续脚本使用的常量。

9 ~ 15行

代码的“handler”版本分配了一个前面提到的基本处理器类,并添加了认证信息。之后该处理器被用于建立一个URL-opener,并安装它以便所有已打开的URL能用到这些认证信息。这段代码和urllib2模块的Python官方文档是兼容的。

例20.1 HTTP认证客户端(urlopenAuth.py)

20.2 使用Python进行Web应用:创建一个简单的Web客户端 - 图22

20.2 使用Python进行Web应用:创建一个简单的Web客户端 - 图23

17 ~ 22行

这段代码的“request”版本创建了一个Request对象,并在HTTP请求中添加了基本的base64编码认证头信息。返回“主体”后(译者注:指for循环)调用urlopen()时,该请求被用来替换其中的URL字符串。注意原始URL内建在Requst对象中,因此在随后的urllib2. urlopen()调用中替换URL字符串才不会产生问题。。这段代码的灵感来自Mike Foord和Lee Harr在Python Cookbook上的回复,具体位置在:

http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/305288

http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/267197

如果能直接用哈尔的HTTPRealmFinder类就更好了,那样我们就没必要在例子里使用硬编码了。

24 ~ 29行

这个脚本的剩余部分只是用两种技术分别打开了给定的URL,并显示服务器返回的HTML页面第一行(舍弃了其他行),当然前提是要通过认证。注意如果认证信息无效的话会返回一个HTTP错误(并且不会有HTML)。

程序的输出应当如下所示:

20.2 使用Python进行Web应用:创建一个简单的Web客户端 - 图24

还有一个很有用的文档可以在http://www.voidspace.org.uk/python/articles/urllib2.shtml找到,你可以把它作为Python官方文档的补充。