第6章 Web客户端访问

获取web页面

#!/usr/bin/env python

import sys,urllib2

req=urllib2.Request(sys.argv[1])
fd=urllib2.urlopen(req)
while True:
    data=fd.read(1024)
    if not len(data):
        break
    sys.stdout.write(data)

首先建立了一个urllib2.Request对象,该对象用URL做参数。然后调用urlopen得到一个文件类对象。当然urlopen也可以直接用url做为参数。。除此以外,还有geturl()函数,用来获得来源的URL,通常情况下,它可以跟踪到重定向的页面。info()函数用来获取页面的meta-information。


认证

有些站点需要HTTP认证后方可访问。HTTP认证一般显示一个弹出窗口,询问用户名和密码,它与基于cookie和form的认证是不同的。
若试图访问一个需要进行认证的URL,通常会得到HTTP错误401,然而urllib可以替你处理认证。例子:

#!/usr/bin/env python

import sys,urllib2,getpass

class TerminalPassword(urllib2.HTTPPasswordMgr):
    def find_user_password(self,realm,authuri):
        retval=urllib2.HTTPPasswordMgr.find_user_password(self,realm,authuri)

        if retval[0]==None and retval[1]==None:
            sys.stdout.write("Login required for %s\n" % (realm,authuri))
            sys.stdout.write(‘Username: ‘)
            username=sys.stdin.readline().strip()
            password=getpass.getpass().rstrip()
            return (username,password)
        else:
            return retval

req=urllib2.Request(sys.argv[1])
opener=urllib2.build_opener(urllib2.HTTPBasicAuthHandler(TerminalPassword()))

fd=opener.open(req)
print ‘Retrieved‘,fd.geturl()
info=fd.info()
for key,value in info.items():
    print ‘%s = %s‘ % (key,value)

这个程序中定义了一个TerminalPassword类,允许程序在需要的时候向操作员询问用户名和密码,另一个调用是build_opener()。这个函数允许指定额外的处理程序,通常在默认情况下就有一些处理程序(例如基本HTTP和FTP支持),而其他的一些处理程序也可以有选择地添加。因为这段代码要支持基本认证,所以必须把HTTPBasicAuthHandler加到处理程序链上。在前一个例子中,代码简单地调用了urllib2.urlopen(),它在内部调用了build_opener(),并且不带任何参数。这就导致了只选择默认的处理程序。注意,一旦连接被打开,就不会有改变。如果需要认证,HTTPBasicAuthHandler会自动地调用TerminalPassword里面合适的函数,不用进一步地检查。还要注意的是,若你访问一个不需要认证的普通站点,这段代码表现地和前面一样。


提交表单数据

CGI脚本和其他交互式的服务器端程序经常从Web客户端收到数据,一般是从表单。有两种方法提交表单数据:GET和POST。至于是用哪种方法,取决于HTML文档中<form>标签里面的方法参数。

1、用GET方法提交

提交表单的GET方法是把表单数据编码至URL。在给出请求的页面后,加上一个问好,接着是表单的元素。每个键和值对被“&”分割。有些字符需要被避免,例如空格需要用“+”代替。因为URL中包含了全部的数据,所以GET方法不太适合数据量比较大的情况。例子:

#!/usr/bin/env python

import sys,urllib2,urllib

def addGETdata(url,data):
    """Adds data to url.Data should be a list or tuple consisting of 2-item lists or tuples of the form:(key,value).
        Items that have no key should have key set to None.
        A given key may occur more than once.
        """
    return url+‘?‘+urllib.urlencode(data)

zipcode=sys.argv[1]
url=addGETdata(‘http://www.wunderground.com/cgi-bin/findweather/getForecast‘,[(‘query‘,zipcode)])
print ‘Using URL‘,url
req=urllib2.Request(url)
fd=urllib2.urlopen(req)
while True:
    data=fd.read(1024)
    if not len(data):
        break
    sys.stdout.write(data)


2、用POST方法提交

编码后的数据以请求的一个单独部分发送。当需要交换大量数据时,POST是一个很好的方法。例子:

#!/usr/bin/env python

import sys,urllib2,urllib

zipcode=sys.argv[1]
url=‘http://www.wunderground.com/cgi-bin/findweather/getForecast‘
data=urllib.urlencode([(‘query‘,zipcode)])
req=urllib2.Request(url)
fd=urllib2.urlopen(req,data)
while True:
    data=fd.read(1024)
    if not len(data):
        break
    sys.stdout.write(data)
#附加的细腻希通过第二个参数传递给urlopen()


处理错误

当和远程Web服务器建立连接时,很多地方都可能出现问题:提供的URL不对;URL也许使用了一个不支持的协议;主机名也许不对;或者访问不到服务器;或者服务器针对请求返回一个错误。。。任何在连接过程中产生的异常要么都是urllib2.URLError的实例,要么是它的一个子类。因此,可以通过捕捉这个超类(指URLError)来捕获异常。。但是HTTP的错误信息实际上还包含一个解释发生了什么的文档。如果直接使用超类将看不到这一文档,为此,urllib2有一个HTTPError的异常类(一个URLError的子类)。HTTPError本身是一种文件类对象,可以被用来读。记住,有些错误不是HTTPError,还是需要处理URLError。例子:

#!/usr/bin/env python

import sys,urllib2

req=urllib2.Request(sys.argv[1])

try:
    fd=urllib2.urlopen(req)
except urllib2.HTTPError,e:
    print ‘Error retrieving data:‘,e
    print ‘Server error document follows:\n‘
    print e.read()
    sys.exit(1)
except urllib2.URLError,e:
    print ‘Error retrieving data:‘,e
    sys.exit(2)

print ‘Retrieved‘,fd.geturl()
info=fd.info()
for key,value in info.items():
    print ‘%s = %s‘ % (key,value)

在读取数据的有两种不同的问题会发生:一是通信错误,会使socket模块在调用read()函数时产生socket.error;二是在没有通信错误的情况下发送的文档被删节。对于第一种情况可以通过处理socket错误的方法来处理。对于第二种情况却有些难度。

对于客户端来说收到被删节的文档而没有任何异常是完全可能的。例如:当一个程序发送文档的时候,服务器出现了问题,这时服务器的问题导致远程socket被正常关闭,所以用户的客户端会简单地收到一个文件结束标志,而无任何异常。

检查这个问题的方法是在服务器的回答中找到内容长度的报头,这样可以对比接受到的数据长度和该报头中提供的长度来检查。但是,内容长度的报头不总是被提供,特别是CGI产生页面都不含此报头。在这种情况下就没有办法检查文件是否被删节。例子:

#!/usr/bin/env python

import sys,urllib2,socket

req=urllib2.Request(sys.argv[1])

try:
    fd=urllib2.urlopen(req)
except urllib2.HTTPError,e:
    print ‘Error retrieving data:‘,e
    print ‘Server error document follows:\n‘
    print e.read()
    sys.exit(1)
except urllib2.URLError,e:
    print ‘Error retrieving data:‘,e
    sys.exit(2)

print ‘Retrieved‘,fd.geturl()

bytesread=0
while True:
    try:
        data=fd.read(1024)
    except socket.error,e:
        print ‘Error reading data:‘,e
        sys.exit(3)
    if not len(data):
        break
    bytesread+=len(data)
    sys.stdout.write(data)

if fd.info().has_key(‘Content-Length‘) and long(fd.info()[‘Content-Length‘])!=long(bytesread):
    print ‘Expected a document of size %d , but read %d bytes‘ % (long(fd.info()[‘Content-Length‘]),bytesread)
    sys.exit(4)


本文出自 “莲的思念” 博客,请务必保留此出处http://liandesinian.blog.51cto.com/7737219/1555361

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。