python的urllib2详解
简介:
urllib2是python的一个获取url(Uniform Resource Locators,统一资源定址器)的模块。它用urlopen函数的形式提供了一个非常简洁的接口。这使得用各种各样的协议获取url成为可能。它同时 也提供了一个稍微复杂的接口来处理常见的状况-如基本的认证,cookies,代理,等等。这些都是由叫做opener和handler的对象来处理的。
以下是获取url最简单的方式:
import urllib2
response = urllib2.urlopen('http://python.org/')
html = response.read()
许多urlib2的使用都是如此简单(注意我们本来也可以用一个以”ftp:”"file:”等开头的url取代”HTTP”开头的url).然 而,这篇教程的目的是解释关于HTTP更复杂的情形。HTTP建基于请求和回应(requests &responses )-客户端制造请求服务器返回回应。urlib2用代 表了你正在请求的HTTP request的Request对象反映了这些。用它最简单的形式,你建立了一个Request对象来明确指明你想要获取的url。调用urlopen函 数对请求的url返回一个respons对象。这个respons是一个像file的对象,这意味着你能用.read()函数操作这个respon对象:
import urllib2
req = urllib2.Request('http://www.voidspace.org.uk')
response = urllib2.urlopen(req)
the_page = response.read()
注意urlib2利用了同样的Request接口来处理所有的url协议。例如,你可以像这样请求一个ftpRequest:
req = urllib2.Request('ftp://example.com/')
对于HTTP,Request对象允许你做两件额外的事:第一,你可以向服务器发送数据。第二,你可以向服务器发送额外的信息(metadata),这些信息可以是关于数据本身的,或者是关于这个请求本身的–这些信息被当作HTTP头发送。让我们依次看一下这些。
数据:
有时你想向一个URL发送数据(通常这些数据是代表一些CGI脚本或者其他的web应用)。对于HTTP,这通常叫做一个Post。当你发送一个你 在网上填的form(表单)时,这通常是你的浏览器所做的。并不是所有的Post请求都来自HTML表单,这些数据需要被以标准的方式encode,然后 作为一个数据参数传送给Request对象。Encoding是在urlib中完成的,而不是在urlib2中完成的。
import urllib
import urllib2
url = 'http://www.someserver.com/cgi-bin/register.cgi'
values = {'name' : 'Michael Foord',
'location' : 'Northampton',
'language' : 'Python' }
data = urllib.urlencode(values)
req = urllib2.Request(url, data)
response = urllib2.urlopen(req)
the_page = response.read()
如果你不传送数据参数,urlib2使用了一个GET请求。一个GET请求和POST请求的不同之处在于POST请求通常具有边界效应:它们以某种 方式改变系统的状态。(例如,通过网页设置一条指令运送一英担罐装牛肉到你家。)虽然HTTP标准清楚的说明Post经常产生边界效应,而get从不产生 边界效应,但没有什么能阻止一个get请求产生边界效应,或一个Post请求没有任何边界效应。数据也能被url自己加密(Encoding)然后通过一 个get请求发送出去。
这通过以下实现:
>>> import urllib2
>>> import urllib
>>> data = {}
>>> data['name'] = 'Somebody Here'
>>> data['location'] = 'Northampton'
>>> data['language'] = 'Python'
>>> url_values = urllib.urlencode(data)
>>> print url_values
name=Somebody+Here&language=Python&location=Northampton
>>> url = 'http://www.example.com/example.cgi'
>>> full_url = url + '?' + url_values
>>> data = urllib2.open(full_url)
头:
我们将会在这里讨论一个特殊的HTTP头,来阐释怎么向你的HTTP请求中加入头。
有一些网站不希望被某些程序浏览或者针对不同的浏览器返回不同的版本。默认情况下,urlib2把自己识别为Python-urllib/x.y(这里的 xy是python发行版的主要或次要的版本号,如, Python-urllib/2.5),这些也许会混淆站点,或者完全不工作。浏览器区别自身的方式是通过User-Agent头。当你建立一个 Request对象时,你可以加入一个头字典。接下来的这个例子和上面的请求一样,不过它把自己定义为IE的一个版本。
import urllib
import urllib2
url = 'http://www.someserver.com/cgi-bin/register.cgi'
user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
values = {'name' : 'Michael Foord',
'location' : 'Northampton',
'language' : 'Python' }
headers = { 'User-Agent' : user_agent }
data = urllib.urlencode(values)
req = urllib2.Request(url, data, headers)
response = urllib2.urlopen(req)
the_page = response.read()
Respons同样有两种有用的方法。当我们出差错之后,看一下关于info and geturl的部分。
异常处理:
不能处理一个respons时,urlopen抛出一个urlerror(虽然像平常一样对于python APIs,内建异常如,ValueError, TypeError 等也会被抛出。)
HTTPerror是HTTP URL在特别的情况下被抛出的URLError的一个子类。
urlerror:
通常,urlerror被抛出是因为没有网络连接(没有至特定服务器的连接)或者特定的服务器不存在。在这种情况下,含有reason属性的异常将被抛出,以一种包含错误代码和文本错误信息的tuple形式。
e.g.
>>> req = urllib2.Request('http://www.pretend_server.org')
>>> try: urllib2.urlopen(req)
>>> except URLError, e:
>>> print e.reason
>>>
(4, 'getaddrinfo failed')
当一个错误被抛出的时候,服务器返回一个HTTP错误代码和一个错误页。你可以使用返回的HTTP错误示例。这意味着它不但具有code属性,而且 同时具有read,geturl,和info,methods属性。>>> req = urllib2.Request('http://www.python.org/fish.html')>>> try:>>> urllib2.urlopen(req)>>> except URLError, e:>>> print e.code>>> print e.read()>>>404...... etc
容错:
如果你准备处理HTTP错误和URL错误这里有两种基本的方法,我更倾向于后一种:
1.
from urllib2 import Request, urlopen, URLError, HTTPError
req = Request(someurl)
try:
response = urlopen(req)
except HTTPError, e:
print 'The server couldn/'t fulfill the request.'
print 'Error code: ', e.code
except URLError, e:
print 'We failed to reach a server.'
print 'Reason: ', e.reason
else:
# everything is fine
注意:HTTP错误异常必须在前面,否则URL错误也会捕获一个HTTP错误。
2
from urllib2 import Request, urlopen, URLError
req = Request(someurl)
try:
response = urlopen(req)
except URLError, e:
if hasattr(e, 'reason'):
print 'We failed to reach a server.'
print 'Reason: ', e.reason
elif hasattr(e, 'code'):
print 'The server couldn/'t fulfill the request.'
print 'Error code: ', e.code
else:
# everything is fine
注意:URL错误是IO错误异常的一个子类。这意味着你能避免引入(import)URL错误而使用:
from urllib2 import Request, urlopen
req = Request(someurl)
try:
response = urlopen(req)
except IOError, e:
if hasattr(e, 'reason'):
print 'We failed to reach a server.'
print 'Reason: ', e.reason
elif hasattr(e, 'code'):
print 'The server couldn/'t fulfill the request.'
print 'Error code: ', e.code
else:
# everything is fine
极少数环境下,urllib2能够抛出socket.error.
INFO and GETURL
urlopen返回的response(或者HTTP错误实例)有两个有用的方法:info和geturl。
geturl–它返回被获取网页的真正的url。这是很有用的,因为urlopen(或使用的opener对象)也许会伴随一个重定向。
获取的网页url也许和要求的网页url不一样。
info–它返回一个像字典的对象来描述获取的网页,尤其是服务器发送的头。它现在一般是httplib.HTTPMessage的一个实例。
典型的头包含'Content-length', 'Content-type', 等等。看一下Quick Reference to HTTP Headers中,HTTP头列表,还有
关于他们简单的解释和使用方法。
Openers 和Handlers
当你获取一个URL时,你使用一个opener(一个可能以一个比较迷糊名字命名的实例–urllib2.OpenerDirector)。正常情况下
我们一直使用默认的opener,通过urlopen,但你也可以创建自定义的openers。opener使用操作器(handlers)。所有的重活都交给这些handlers来做。每一个handler知道
怎么打开url以一种独特的url协议(http,ftp等等),或者怎么处理打开url的某些方面,如,HTTP重定向,或者HTTP cookie。
你将会创建openers如果你想要用安装特别的handlers获取url,例如,获取一个处理cookie的opener,或者一个不处理重定向的opener。
枚举一个OpenerDirector,然后多次调用.add_handler(some_handler_instance)来创建一个opener。
或者,你可以用build_opener,这是一个很方便的创建opener对象的函数,它只有一个函数调用。build_opener默认会加入许多
handlers,但是提供了一个快速的方法添加更多东西和/或使默认的handler失效。
其他你想要的handlers能够处理代理,authentication和其他平常但是又有些特殊的情况。
install_opener能被用于创建一个opener对象,(全局)默认的opener。这意味着调用urlopen将会用到你刚安装的opener。
opener对象有一个open方法,它可以被直接调用来获取url以一种和urlopen函数同样的方式:没有必要调用install_opener,除非是为了方便。
Basic Authentication:(基本验证)
为了解释创建和安装一个handler,我们将会使用 HTTPBasicAuthHandler。更多关于这个东西的内容和详细讨论—包括一个 Basic Authentication如何工作的解说–参见 Basic Authentication Tutorial.
当需要Authentication的时候,服务器发送一个头(同时还有401代码)请求Authentication。它详细指明了一个Authentication和一个域。这个头看起来像:
Www-authenticate: SCHEME realm=”REALM”.
e.g.
Www-authenticate: Basic realm=”cPanel Users”
客户端然后就会用包含在头中的正确的帐户和密码重新请求这个域。这是”基本验证”。为了简化这个过程,我们可以创建一个
HTTPBasicAuthHandler和opener的实例来使用这个handler。
HTTPBasicAuthHandler用一个叫做密码管理的对象来处理url和用户名和密码的域的映射。如果你知道域是什么(从服务器发送的authentication
头中),那你就可以使用一个HTTPPasswordMgr。多数情况下人们不在乎域是什么。那样使用HTTPPasswordMgrWithDefaultRealm就很方便。它
允许你为一个url具体指定用户名和密码。这将会在你没有为一个特殊的域提供一个可供选择的密码锁时提供给你。我们通过提供None作为add_password方法域的参数指出
这一点。
最高级别的url是需要authentication的第一个url。比你传递给.add_password()的url更深的url同样也会匹配。
# create a password manager
password_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
# Add the username and password.
# If we knew the realm, we could use it instead of “None“.
top_level_url = “http://example.com/foo/”
password_mgr.add_password(None, top_level_url, username, password)
handler = urllib2.HTTPBasicAuthHandler(password_mgr)
# create “opener” (OpenerDirector instance)
opener = urllib2.build_opener(handler)
# use the opener to fetch a URL
opener.open(a_url)
# Install the opener.
# Now all calls to urllib2.urlopen use our opener.
urllib2.install_opener(opener)
注意:在以上的示例中我们只给build_opener提供了HTTPBasicAuthHandler。默认opener有对普通情况的操作器 (handlers)- ProxyHandler, UnknownHandler, HTTPHandler, HTTPDefaultErrorHandler, HTTPRedirectHandler, FTPHandler, FileHandler, HTTPErrorProcessor.
高级别url实际上是一个完整的url(包括http:协议组件和主机名可选的端口号),如”http://example.com”或者是一个授权(同样,主机名,可选的端口号)
如”"example.com” 或 “example.com:8080″(后一个示例包含了一个端口号)。授权,如果被呈现,一定不能包含用户信息-如”oe@password:example.com”
是不正确的、
代理:
urllib2将会自动检测你的代理设置并使用它们。这是通过 ProxyHandler实现的,它是操作器链的一部分。正常情况下,这是个好东西,但是也有它不那么有用的偶然情况。
一个做这些的方法是安装我们自己的ProxyHandler,不用任何定义任何代理。使用一个和建立Basic Authentication操作器相似的步骤可以实现:
>>> proxy_support = urllib2.ProxyHandler({})
>>> opener = urllib2.build_opener(proxy_support)
>>> urllib2.install_opener(opener)
注意:
目前urllib2不支持通过代理获取HTTPs位置。这是一个问题。
sockets和layers
python支持获取层叠的网页的源码。urllib2使用httplib library,而httplib library反过来使用socket library。
对于python2.3你可以指明一个socket应该在超时之前等待response多久。这在这些不得不获取网页的应用中很有用。默认socket模块没有超时而且能够挂起。
目前,socket超时在urllib2或者httplib水平中不可见。然而,你可以全局地为所有socket设置默认的超时。
import socket
import urllib2
# timeout in seconds
timeout = 10
socket.setdefaulttimeout(timeout)
# this call to urllib2.urlopen now uses the default timeout
# we have set in the socket module
req = urllib2.Request('http://www.voidspace.org.uk')
response = urllib2.urlopen(req)