Urllib2 总结
Urllib2 总结
介绍
Urllib2是用于获取URLs(统一资源定位符)的一个Python模块。它以urlopen函数的形式提供了非常简单的接口。能够使用各种不同的协议来获取网址。它还提供一个稍微复杂的接口用于处理常见的情况:如基本身份验证、cookies、proxies(代理)等。这些是由handlers和openers对象提供。
Urllib2使用相关的网络协议(FTP,http),支持多种获取URLs的方案(以URL前面的”: ”定义,如:ftp://python.org),这里主要讲最常见的http。
一般情况下,使用urllib2是非常简单的。当打开网页遇到错误或异常时,你需要理解HTTP(超文本传输协议),最全面最权威HTTP协议可参考RFC2616。本文旨在说明使用urllib2。
获取URLs
简单的使用urllib2如下:
import urllib2
response = urllib2.urlopen('http://python.org')
html = response.read()
print html
可以看到请求的网页已经被打印出来了。使用urllib2就是如此简单。
(可使用'ftp:'、'file'代替'http:')
HTTP是基于请求和响应---客户端发出请求和服务器端发送响应。Urllib2 对应Request对象表示你做出HTTP请求,最简单的形式,创建一个指定要获取的网址的Request对象。这个Request对象调用urlopen,返回URL请求的Response对象。Response对象是一个类似于文件对象,你可以在Response中使用 .read()。
import urllib2
req = urllib2.Request('http://python.org')
response = urllib2.urlopen(req)
the_page = response.read()
print the_page
urlib2可以使用相同Request接口来处理所有URL方案,例如,你可以创建一个FTP请求:
req = urllib2.Request('ftp://example.com/')
在HTTP协议中,Request对象有两个额外的事情可以做,第一,你可以通过将数据发送到服务器;第二,你可以通过数据的额外的信息(metadata)或请求到服务器本身,这个信息是发送HTTP'headers'。
Data
有时你想发送一个数据到URL(通常这个URL指向到CGI(公共网关接口)脚本或其他Web应用程序)。
在HTTP中,经常使用POST请求。这个通常是浏览器做的,当你在你填写的网站上提交HTML表单。
不是所有的POSTs都使用表单的形式:你可以使用POST传输任意数据到自己的应用中。在通常的HTML表单中,这些数据需要以标准的形式进行编码,然后传递到Request对象作为data参数。编码是通过使用urllib库而不是urllib2库。
import urllib
import urllib2
url = 'http://www.someserver.com/cgi-bin/register.cgi'
values = {}
values['name'] = 'Michael Foord'
values['location'] = 'Northampton'
values['language'] = 'Python'
data = urllib.urlencode(values) #数据进行编码
req = urllib2.Request(url,data) #作为data参数传递到Request对象中
response = urllib2.urlopen(req)
the_page = response.read()
print the_page
如果你不传递data参数,urllib2使用GET请求。GET和POST请求不同的是,POST请求经常有副作用:它们在某种程度上改变系统的状态。
尽管HTTP标准有明确的说明POSTs总是引起副作用,而GET请求从不会引起副作用,没有什么可以防止GET请求没有副作用,POST请求有副作用。
数据也可以通过在URL本身中HTTP 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.urlopen(full_url) #这个完整的URL是添加一个?到URL中,其次是编码的值。
Headers
在这里讨论一个特定的HTTP头,说明如何添加头到你的HTTP请求。
一些网站(google)不喜欢由程序来访问或不同的浏览器发送不同的版本,默认情况下,urllib2标识自己为python urllib / x.y(x.y是Python版本号eg : 2.7),这个可能会混淆网站,或只是简单的不工作。
浏览器标识自己的方式是通过用户代理(User-Agent)头。当创建一个Request对象时,你可以通过一个Headers的字典。下面的例子使相同的请求,但将其标识为一个版本的Internet Explorer。
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 = {}
values['name'] = 'Michael Foord'
values['location'] = 'Northampton'
values['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()
print the_page
response还有两个常用的方法(info、geturl)。
Handling Exceptions
当urlopen无法处理响应时将引起URLError(尽管像往常一样使用Python APIs,内建的异常如ValueError、TypeError等也可以引起),在特定的HTTP URLs情况下,HTTPError 是URLError的子类。
URLError
通常,网络没有连接或没有路由到指定的服务器或指定的服务器不存在将引起URLError.在这种情形下,异常引发将有个reason属性,这是一个包含错误代码和一个文本错误信息的元组。
import urllib2
req = urllib2.Request('http://www.pretend_server.org')
try:
urllib2.urlopen(req)
except urllib2.URLError,e:
print e.reason
[Errno 11004] getaddrinfo failed
HTTPError
每个HTTP响应从服务器包含一个数字状态码”,有时,这个状态码表明服务器无法满足请求。默认处理程序将处理一些响应(例如:如果响应是一个“重定向”,则要求客户端从不同的网址提取文档,urllib2将自行处理);对于那些它不能处理的,urlopen将引起HTTPError。典型的错误包括404(没有找到网页)、403(禁止请求)、401(需要验证)。
HTTPError 实例提出的将一个整型的’code ‘属性,对应服务器发出的错误Error Codes。因为默认处理程序处理重定向(代码在300范围内),代码在100-299表明成功,你通常会看到在400-599范围错误代码。
basehttpserver.basehttprequesthandler.responses是一个有用的字典响应码,显示所有的的响应码由RFC2616的应用。
# Table mapping response codes to messages; entries have the
# form {code: (shortmessage, longmessage)}.
responses = {
100: ('Continue', 'Request received, please continue'),
101: ('Switching Protocols',
'Switching to new protocol; obey Upgrade header'),
200: ('OK', 'Request fulfilled, document follows'),
201: ('Created', 'Document created, URL follows'),
202: ('Accepted',
'Request accepted, processing continues off-line'),
203: ('Non-Authoritative Information', 'Request fulfilled from cache'),
204: ('No Content', 'Request fulfilled, nothing follows'),
205: ('Reset Content', 'Clear input form for further input.'),
206: ('Partial Content', 'Partial content follows.'),
300: ('Multiple Choices',
'Object has several resources -- see URI list'),
301: ('Moved Permanently', 'Object moved permanently -- see URI list'),
302: ('Found', 'Object moved temporarily -- see URI list'),
303: ('See Other', 'Object moved -- see Method and URL list'),
304: ('Not Modified',
'Document has not changed since given time'),
305: ('Use Proxy',
'You must use proxy specified in Location to access this '
'resource.'),
307: ('Temporary Redirect',
'Object moved temporarily -- see URI list'),
400: ('Bad Request',
'Bad request syntax or unsupported method'),
401: ('Unauthorized',
'No permission -- see authorization schemes'),
402: ('Payment Required',
'No payment -- see charging schemes'),
403: ('Forbidden',
'Request forbidden -- authorization will not help'),
404: ('Not Found', 'Nothing matches the given URI'),
405: ('Method Not Allowed',
'Specified method is invalid for this server.'),
406: ('Not Acceptable', 'URI not available in preferred format.'),
407: ('Proxy Authentication Required', 'You must authenticate with '
'this proxy before proceeding.'),
408: ('Request Timeout', 'Request timed out; try again later.'),
409: ('Conflict', 'Request conflict.'),
410: ('Gone',
'URI no longer exists and has been permanently removed.'),
411: ('Length Required', 'Client must specify Content-Length.'),
412: ('Precondition Failed', 'Precondition in headers is false.'),
413: ('Request Entity Too Large', 'Entity is too large.'),
414: ('Request-URI Too Long', 'URI is too long.'),
415: ('Unsupported Media Type', 'Entity body in unsupported format.'),
416: ('Requested Range Not Satisfiable',
'Cannot satisfy request range.'),
417: ('Expectation Failed',
'Expect condition could not be satisfied.'),
500: ('Internal Server Error', 'Server got itself in trouble'),
501: ('Not Implemented',
'Server does not support this operation'),
502: ('Bad Gateway', 'Invalid responses from another server/proxy.'),
503: ('Service Unavailable',
'The server cannot process the request due to a high load'),
504: ('Gateway Timeout',
'The gateway server did not receive a timely response'),
505: ('HTTP Version Not Supported', 'Cannot fulfill request.'),
}
服务器响应引起的错误通过返回一个HTTP错误代码和错误页面,可以使用HTTPError实例为页面上的响应中返回。这个也有code属性,也有read、geturl、info方法。
import urllib2
req = urllib2.Request('http://www.python.org/fish.html')
try:
urllib2.urlopen(req)
except urllib2.URLError,e:
print e.code
print e.read()
print e.geturl()
print e.info()
Wrapping it up(包装)
如果你想编写HTTPError或URLError有两个基本方法。(更喜欢第二种方法)
Number1
同时处理HTTPError和URLError,应该把HTTPError放在URLError前面。由于HTTPError是URLError的子类,否则URLError也会捕获一个HTTPError错误。
from urllib2 import Request,urlopen,URLError,HTTPError
req = Request('http://www.python.org/fish.html')
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 reache a server.'
print 'Reason:',e.reason
else:
#everything is fine
response.getcode()
Number 2
由于HTTPError是URLError的子类,可以避免导入HTTPError.改进如下:
from urllib2 import Request,urlopen,URLError
req = Request('http://www.python.org/fish.html')
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
response.getcode()
注意,URLError是内置异常IOError的子类,同样也可避免导入URLError.在某些情况下,urllib2也可能会引起scoker.error。
from urllib2 import Request,urlopen
req = Request('http://www.python.org/fish.html')
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
response.getcode()
BadStatusLine and HttpException
由一两种情况例外,不继承IOError引起的异常,一种是由httplib模块定义的BadStatusLine异常,当请求的页页面是空白时会引起这个异常。它没有继承IOError,而是从HttpException继承(在httplib中重新定义,直接从Exception中继承),可能还有其他的情况下,这些例外可以渗入到用户urllib2。你可以将这些异常类型,从httplib中直接抓取,或者用一个"catch-all"异常语句来处理任何发生的错误。
info and geturl
以urlopen返回的响应(或HTTPError实例)有两个有用的信息info和方法geturl。
geturl --- 返回抓取页面的真正的URL。这是非常有用的,因为urlopen可能跟着一个重定向。获取的页面的网址可能与请求的地址不相同。
info --- 返回一个描述页面抓取的字典对象,特别是服务器发送的头文件。这是当前的一个httplib.httpmessage实例。典型的heade包括'Content-length', 'Content-type'等。
Openers and Handlers
当使用opener(也许是urllib2.openerdirector的命名实例)获取一个网页时,通常我们通过urlopen已经使用默认的opener, 但是我们可以创建自定义openers。openers使用处理程序。所有的“重起”是由处理程序完成的。每个处理程序知道如何为一个特定的URL方案(HTTP,FTP等)打开网址,或如何处理网址打开的一个方面,例如,HTTP重定向或者HTTP cookies。
如果你想获取安装了特定处理程序的网址,需要创建openers。例如,获得opener处理cookies,或者获得opener不处理重定向。
创建一个opener,实例化一个openerdirector,然后反复调用.add_handle(some_handler_instance)。或者,你可以使用build_opener,这是一个方便的功能,用于创建具有一个单一的函数调用opener对象,.build_opener默认情况下增加了几个程序。但提供了一个快速的方法来添加更多或重写默认的处理程序。
其他类型的处理程序,你可能希望可以处理proxies(代理)、authenticarion(身份验证)和其他常见情况。
install_opener默认情况下可以打开一个opener对象(全局),这意味着调用urlopen将使用已安装的opener对象。Opener对象有一个open方法,使用urlopen函数直接抓取urls,不需要调用install_opener,除非为了方便。
Basic Authentication
说明如何创建和安装处理程序,我们将使用httpbasicauthhandler。对于这个问题的更详细的讨论,包括对基本认证如何工作的解释,看看 Basic Authentication Tutorial.
当需要身份验证时,服务器发送一个头(以及401个错误代码)请求身份验证。
这指定的身份验证scheme(方案)和'realm'(“领域”)。header是这样的:WWW-Authenticate:SCHEME realm=“REALM”。
e.g.
Www-authenticate: Basic realm="cPanel Users"
然后,客户端应以适当的名称和密码重试请求,以在请求中包含作为header的域中的适当name和password。这是“基本的身份验证”为了简化这一过程,我们可以创建一个httpbasicauthhandler实例和opener使用此处理程序。
httpbasicauthhandler使用对象称为一个密码管理器,来处理URL和服务器密码用户名映射,如果你知道这个领域是什么(从服务器发送的身份验证头),你可以使用HTTPPasswordMgr。通常不关心的领域是什么。
在这种情况下,它使用很方便httppasswordmgrwithdefaultrealm。
这允许您指定一个默认的用户名和密码的网址。
我们表明这个提供None作为 add_password方法的域参数。顶级的网址是第一个需要认证的网址。网址“deeper”和URL传递给.add_password()也将匹配。
# 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)
注意:
在上面的例子中,我们只提供给我们的httpbasicauthhandler build_opener。默认情况下,openers有正常情况下的处理程序--ProxyHandler,UnknownHandler,HTTPHandler, HTTPDefaultErrorHandler,HTTPRedirectHandler,FTPHandler,FileHandler,HTTPErrorProcessor.
top_level_url实际上是一个完整的URL(包括“http”计划的组成部分和主机名和可选的端口号)e.g. "http://example.com/" 或者 一个"authority"(i.e.主机名,包括主机名)e.g. "example.com" or "example.com:8080"(后者的例子包括一个端口号)这个 authority
如果存在,一定不能包含“userinfo”部分--例如:joe@password:example.com不正确
proxies
urllib2自动检测代理服务器设置和使用.这是通过proxyhandler是正常的处理程序链的一部分.通常情况下,这是一件好事,但也有一些时候,它可能不会有帮助。一个办法是建立我们自己的proxyhandler,没有代理的定义。这是使用类似的步骤来设置一个基本的身份验证处理程序:
proxy_support = urllib2.ProxyHandler({})
opener = urllib2.build_opener(proxy_support)
urllib2.install_open(opener)
注意:
当前的urllib2不支持通过proxy抓取https,这个可能是个问题.
Sockets and Layers
Python从网络获取资源支持分层,urllib2使用httplib的库,从而使用Socket库。在Python2.3你可以指定多少socket,应该等待超时前的反应。这可能是有用的应用程序,必须获取网页。默认情况下,socket模块没有超时和可以挂起。
目前,socket超时不暴露httplib或者urllib2。然而,你可以在全局范围内为所有的socket设置默认超时使用。
import socket
import urllib2
# timeout in seconds
timeout = 10
socket.setdefault timeout(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)