简介:
urllib2是python的一个获取url(Uniform Resource Locators,统一资源定址器)的模块。它用urlopen函数的形式提供了一个非常简洁的接口。这使得用各种各样的协议获取url成为可能。它同时 也提供了一个稍微复杂的接口来处理常见的状况-如基本的认证,cookies,代理,等等。这些都是由叫做opener和handler的对象来处理的。
urlib2支持获取url的多种url 协议(以url中“:”前的字符串区别,如ftp是ftp形式的url 协议),用它们关联的网络协议(如HTTP,FTP)。这个教程著重于最普遍的情况--HTTP。
最简单的情况下urlopen用起来非常简单。但随着你打开HTTP ur时遇到错误或无意义的事情,你需要对HTTP的一些理解。对HTTP最权威最容易让人理解的参考是RFC 2616。这是一个技术文档,而且不太容易读懂。这篇HOWTO意在用足够关于HTTP的细节阐明urllib2,使你明白。它的意图不在替换urllib2 docs,而是对它们的一个补充。
1 Proxy 的设置
urllib2 默认会使用环境变量 http_proxy 来设置 HTTP Proxy。如果想在程序中明确控制 Proxy,而不受环境变量的影响,可以使用下面的方式
import urllib2 enable_proxy = True proxy_handler = urllib2.ProxyHandler({"http" : 'http://some-proxy.com:8080'}) null_proxy_handler = urllib2.ProxyHandler({}) if enable_proxy: opener = urllib2.build_opener(proxy_handler)else: opener = urllib2.build_opener(null_proxy_handler) urllib2.install_opener(opener) |
这里要注意的一个细节,使用 urllib2.install_opener() 会设置 urllib2 的全局 opener。这样后面的使用会很方便,但不能做更细粒度的控制,比如想在程序中使用两个不同的 Proxy 设置等。比较好的做法是不使用 install_opener 去更改全局的设置,而只是直接调用 opener 的 open 方法代替全局的 urlopen 方法。
2 Timeout 设置
在老版本中,urllib2 的 API 并没有暴露 Timeout 的设置,要设置 Timeout 值,只能更改 Socket 的全局 Timeout 值。
import urllib2import socket socket.setdefaulttimeout(10) # 10 秒钟后超时urllib2.socket.setdefaulttimeout(10) # 另一种方式 |
在新的 Python 2.6 版本中,超时可以通过 urllib2.urlopen() 的 timeout 参数直接设置。
import urllib2 response = urllib2.urlopen('http://www.google.com', timeout=10) |
3 在 HTTP Request 中加入特定的 Header
要加入 Header,需要使用 Request 对象:
import urllib2 request = urllib2.Request(uri) request.add_header('User-Agent', 'fake-client') response = urllib2.urlopen(request) |
对有些 header 要特别留意,Server 端会针对这些 header 做检查
- User-Agent 有些 Server 或 Proxy 会检查该值,用来判断是否是浏览器发起的 Request
- Content-Type 在使用 REST 接口时,Server 会检查该值,用来确定 HTTP Body 中的内容该怎样解析。
常见的取值有:
-
- application/xml :在 XML RPC,如 RESTful/SOAP 调用时使用
- application/json :在 JSON RPC 调用时使用
-
application/x-www-form-urlencoded :浏览器提交 Web 表单时使用
- ……
-
在使用 RPC 调用 Server 提供的 RESTful 或 SOAP 服务时, Content-Type 设置错误会导致 Server 拒绝服务。
4 Redirect
urllib2 默认情况下会针对 3xx HTTP 返回码自动进行 Redirect 动作,无需人工配置。要检测是否发生了 Redirect 动作,只要检查一下 Response 的 URL 和 Request 的 URL 是否一致就可以了。
import urllib2 response = urllib2.urlopen('http://www.google.cn') redirected = response.geturl() == 'http://www.google.cn' |
如果不想自动 Redirect,除了使用更低层次的 httplib 库之外,还可以使用自定义的 HTTPRedirectHandler 类。
import urllib2 class RedirectHandler(urllib2.HTTPRedirectHandler): def http_error_301(self, req, fp, code, msg, headers): pass def http_error_302(self, req, fp, code, msg, headers): pass opener = urllib2.build_opener(RedirectHandler) opener.open('http://www.google.cn') |
5 Cookie
urllib2 对 Cookie 的处理也是自动的。如果需要得到某个 Cookie 项的值,可以这么做:
import urllib2import cookielib cookie = cookielib.CookieJar() opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookie)) response = opener.open('http://www.google.com')for item in cookie: if item.name == 'some_cookie_item_name': print item.value |
6 使用 HTTP 的 PUT 和 DELETE 方法
urllib2 只支持 HTTP 的 GET 和 POST 方法,如果要使用 HTTP PUT 和 DELETE,只能使用比较低层的 httplib 库。虽然如此,我们还是能通过下面的方式,使 urllib2 能够发出 HTTP PUT 或 DELETE 的包:
import urllib2 request = urllib2.Request(uri, data=data) request.get_method = lambda: 'PUT' # or 'DELETE' response = urllib2.urlopen(request) |
这种做法虽然属于 Hack 的方式,但实际使用起来也没什么问题。
7 得到 HTTP 的返回码
对于 200 OK 来说,只要使用 urlopen 返回的 response 对象的 getcode() 方法就可以得到 HTTP 的返回码。但对其它返回码来说,urlopen 会抛出异常。这时候,就要检查异常对象的 code 属性了:
import urllib2try: response = urllib2.urlopen('http://restrict.web.com')except urllib2.HTTPError, e: print e.code |
8 Debug Log
使用 urllib2 时,可以通过下面的方法把 Debug Log 打开,这样收发包的内容就会在屏幕上打印出来,方便我们调试,在一定程度上可以省去抓包的工作。
import urllib2 httpHandler = urllib2.HTTPHandler(debuglevel=1) httpsHandler = urllib2.HTTPSHandler(debuglevel=1) opener = urllib2.build_opener(httpHandler, httpsHandler) urllib2.install_opener(opener) response = urllib2.urlopen('http://www.google.com') |
获取url:
以下是获取url最简单的方式:
importurllib2
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对象:
importurllib2
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()
注意有时需要其他的Encoding(例如,对于一个来自表单的文件上传(file upload)--详细内容见HTML Specification, Form Submission )。
如果你不传送数据参数,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)
注意一个完整的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.
>>> try: urllib2.urlopen(req)
>>> except URLError, e:
>>> print e.reason
>>>
(4, 'getaddrinfo failed')
HTTPError:
每个来自服务器的HTTP response包含一个“status code”(状态代码)。有时状态代码暗示了服务器不能处理这个请求。默认的句柄将会为你处理一些response(如,如果返回的是一个要求你从一个不同的url获取文件的“重定向”,urlib2将会为你处理)。对于那些它不能处理的,urlopen将会抛出一个HTTPerror。
典型的错误包含404(页面没有找到),403(请求禁止)和401(需要许可)。
所有的HTTP错误代码参见RFC2616的第十部分。
HTTP错误代码将会有一个code(integer)属性,这个code属性的integer值和服务器返回的错误代码是一致的。
错误代码:
因为默认的句柄处理重定向(300序列中的代码)和在100-299之间表示成功的代码,你通常只需要了解在400-599序列中的错误代码。
BaseHTTPServer.BaseHTTPRequestHandler.responses是一个有用的response字典,其中的代码显示了所有RFC2616使用的response代码。
为了方便,代码被复制到了这里:
# 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错误代码和一个错误页。你可以使用返回的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
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<?xml-stylesheet href="./css/ht2html.css"
type="text/css"?>
<html><head><title>Error 404: File Not Found</title>
...... etc