Python3使用urllib访问网页

介绍

改教程翻译自python官网的一篇文档。

urllib.request是一个用于访问URL(统一资源定位符)的Python模块。它以urlopen函数的形式提供了一个非常简单的接口,可以访问使用多种不同协议的URL。它也提供了一个稍微复杂一些的接口,用来处理常用的情况——如基本的认证,cookies,代理等等。这些服务由叫做handlers和openers的对象提供。

urllib.request支持访问多种“URL模式”(模式由URL中“:”前面的字符串确定——比如“ftp”就是“ftp://python.org/”的URL模式),使用的是它们对应的网络协议(如FTP,HTTP)。这个教程集中于最常用的的类型,HTTP。

对于直截了当的情况,urlopen很容易使用。但是,一旦你在打开HTTP URL的时候遇到错误或者一些不平常的情况,你就需要对超文本转换协议的一些理解。关于HTTP最全面最权威的参考是RFC 2616。这是一个技术文档,不太容易阅读。这篇文章的目标就是说明如何使用urllib,包含足够的HTTP协议细节帮助你理解。这篇文章不是要代替urllib.request的文档,而是对它的补充。

访问URL

使用urllib.request最简单的方式如下:

import urllib.request
with urllib.request.urlopen('http://python.org/') as response:
    html = response.read()

如果你想利用url下载一个资源并把它存储在一个临时文件中,你可以利用urlretrieve()函数:

import urllib.request
local_filename,headers = urllib.request.urlretrieve('http://pythpn.org/')
html = open(local_filename)

下图是演示效果:Python3使用urllib访问网页_第1张图片

uellib的很多用法就是这么简单(注意除了‘http:’类型的url,我们也可以使用以‘ftp:’、‘file:’等开头的url)。然而,这个教程的目的是解释HTTP中一些更复杂的情形。

HTTP是基于请求和响应的——客户端发起请求,服务端返回响应。urllib.request用Request类来表示你做出的HTTP请求。它的最简形式就是只指定你需要访问的url。Request对象调用urlopen方法会为这个请求返回一个响应对象。这个响应是一个类文件对象,意味着你可以对其调用.read()方法:

import urllib.request

req = urllib.request.Request('http://www.voidspace.org.uk')
with urllib.request.urlopen(req) as response:
   the_page = response.read()

注意urllib.request使用相同的接口来处理所有类型的url。比如说,你也可以这样发起一个FTP请求:

req = urllib.request.urlopen('ftp://example.com/')

在HTTP的情况下,Request对象还允许你做两件事:第一,你可以传递要发送到服务端的数据;第二,你可以发送关于数据或请求自身的额外信息(元数据)给服务器——这个信息会作为HTTP的“请求头”进行发送。让我们分别看一下。

数据

有时你想使用HTTP发送数据到一个url(通常这个url会指向一个CGI(通用网关接口,Common Gateway Interface)脚本或其它网络应用),这通常使用一个POST请求来完成。这也就是当你提交一份填写好的HTML表单的时候,你的浏览器所做的事情。不是所有的POST请求都来自表单:你可以使用POST方式把任意的数据发送到你自己的应用。在常见的HTML表单情况中,数据需要用标准方式进行编码,然后作为data参数传递给Request对象。编码是使用一个urllib.parse库中的函数完成的。

import urllib.parse
import urllib.request

url = 'http://www.someserver.com/cgi-bin/register.cgi'
values = {'name' : 'Michael Foord',
          'location' : 'Northampton',
          'language' : 'Python' }

data = urllib.parse.urlencode(values)
data = data.encode('ascii') # data should be bytes
req = urllib.request.Request(url, data)
with urllib.request.urlopen(req) as response:
   the_page = response.read()

如果不传递data参数,那urllib就会使用GET请求方式。GET方式和POST方式的其中一个区别在于POST请求经常有副作用:它们会以某种方式改变系统的状态(比如,在网上下订单,会有一英担的午餐肉罐头送到你家门口)。尽管HTTP标准明确说POST方式总是会造成副作用,而GET方式从来不会,但是并没有保证措施。数据也可以用GET方式传递,只要把它编码在url中。

做法如下:

>>> import urllib.request
>>> import urllib.parse
>>> data = {}
>>> data['name'] = 'Somebody Here'
>>> data['location'] = 'Northampton'
>>> data['language'] = 'Python'
>>> url_values = urllib.parse.urlencode(data)
>>> print(url_values)  # The order may differ from below.  
name=Somebody+Here&language=Python&location=Northampton
>>> url = 'http://www.example.com/example.cgi'
>>> full_url = url + '?' + url_values
>>> data = urllib.request.urlopen(full_url)

注意full_url 是通过在url后面加一个?,然后再加上编码后的数据进行创建的。

头信息

我们在这里讨论一个特殊的HTTP头信息,来说明如何在你的HTTP请求中添加头信息。

一些网站不喜欢被程序访问,或者会给不同的浏览器发送不同的版本。默认情况下,urllib会把自身标记为Python-urllib/x.y(其中x和y表示Python的版本号,如Python-urllib/2.5),这可能会迷惑网站,或者干脆不起作用。浏览器标识自己的方式就是通过User-agent头信息。当你创建一个Request对象时,你可以传递一个头信息的字典。下面的例子发起的是跟上面一样的请求,但是把自己标识为一个IE浏览器的版本。

import urllib.parse
import urllib.request

url = 'http://www.someserver.com/cgi-bin/register.cgi'
user_agent = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64)'
values = {'name':'Michael Foord',
          'location':'Northampton',
          'language':'Python' }
headers = {'User-Agent':user_agent}

data = urllib.parse.urlencode(values)
data = data.encode('ascii')
req = urllib.request.Request(url, data, headers)
with urllib.request.urlopen(req) as response:
    the_page = response.read()

这个响应也有两个有用的方法。看info和geturl 这一节。

处理异常

urlopen在不能处理某个响应的时候会抛出URLError(虽然一般使用Python API时,ValueError、TypeError等内建异常也可能被抛出)。

HTTPErrorURLError的子类,在遇到HTTP URL的特殊情况时被抛出。

异常类出自urllib.error模块。

URLError

一般来说,URLError被抛出是因为没有网络连接(没有到指定服务器的路径),或者是指定服务器不存在。在这种情况下,抛出的异常将会包含一个‘reason’属性,这是包含一个错误码和一段错误信息的元组。例如

>>> req = urllib.request.Request('http://www.pretend_server.org')
>>> try: urllib.request.urlopen(req)
... except urllib.error.URLError as e:
...     print(e.reason)      
...
(4, 'getaddrinfo failed')

HTTPError

每一个来自服务器的HTTP响应都包含一个数字的“状态码”。有时状态码表明服务器不能执行请求。默认的处理程序会为你处理其中的部分响应(比如,如果响应是“重定向”,要求客户端从一个不同的URL中获取资料,那么urllib将会为你处理这个)。对于那些不能处理的响应,urlopen将会抛出一个HTTPError。典型的错误包括‘404’(页面未找到),‘403’(请求禁止),和‘401’(请求认证)。

查看RFC 2616的第10节,作为对所有HTTP错误码的参考。

抛出的HTTPError实例有一个整型的‘code’属性,对应于服务器发送的错误。

错误码

因为默认的处理程序会处理重定向问题(范围在300的错误码),而且范围100-299之间的状态码表示成功,所以你通常只会看到范围在400-599之间的错误码。

http.server.BaseHTTPRequestHandler.responses 是一个关于响应码的字典,展示了RFC 2616使用的所有状态码。为了方便,把字典展示如下:

# 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’属性外,也可以使用由urllib.response模块返回的read、geturl和info方法。演示如下:

req = urllib.request.Request('http://www.python.org/fish.html')
try:
    urllib.request.urlopen(req)
except urllib.error.HTTPError as e:
    print (e.code)
    print (e.info())
    print (e.geturl())
    print (e.read())

上面程序的效果如下,Python3使用urllib访问网页_第2张图片

简单起见,上图中后面的页面内容没有全部截图。

包装一下

如果你希望程序对HTTPErrorURLError有所准备,有两种基本方法可以使用,我更喜欢第二个。

第一种

from urllib.request import Request, urlopen
from urllib.error import URLError, HTTPError
req = Request(someurl)
try:
    response = urlopen(req)
except HTTPError as e:
    print('The server couldn\'t fulfill the request.')
    print('Error code: ', e.code)
except URLError as e:
    print('We failed to reach a server.')
    print('Reason: ', e.reason)
else:
    # everything is fine

注意except HTTPError必须放在第一个,否则except URLError也将捕获一个HTTPError。

第二种

from urllib.request import Request, urlopen
from urllib.error import URLError
req = Request(someurl)
try:
    response = urlopen(req)
except URLError as 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

info和geturl

urlopen(或HTTPError实例)返回的响应有两个实用的方法info()geturl(),是在模块urllib.response中定义的。

geturl—这个方法返回的是所获取页面的真正URL路径。这个是有用的,因为urlopen(或者使用的opener对象)可能经过了重定向。因此所得页面的URL可能不是请求的URL。

info—这个方法返回一个类似字典的对象,描述所得到的页面,尤其是服务器发回的头信息。它实际上是一个http.client.HTTPMessage实例。

典型的头信息包括‘Content-length’、‘Content-type’等等。你可以查看关于HTTP 头信息的快速指南,这里有关于HTTP头信息的含义和使用的简短说明。

Openers和Handlers

当你访问URL时,你使用的就是一个opener(这是urllib.reuest.OpenerDirector的一个实例)。一般来说我们使用的是默认的opener—通过urlopen—但是你可以创建自定义的opener。opener会使用handler。所有的“重活”都是由handler完成的。每一个handler知道如何去处理某种类型的URL(http,ftp等等),或者是如何处理访问URL的某一方面,如HTTP重定向或HTTP cookies。

如果你想要用特定的handler来访问URL,你可以创建一个opener。比如,得到一个处理cookies的opener或者是一个不处理重定向的opener。

要创建一个opener,可以实例化一个OpenerDirector,然后重复地调用.add_handler(some_handler_instance)

或者,你可以使用build_opener方法,这是一个很方便的函数,可以通过一次函数调用创建opener对象。build_opener默认添加了一些handler,但是提供了一个简单的方法添加或者覆写默认的handler。

你可能需要其它类型的handler,比如可以处理代理,认证,以及其它常见但是特殊的情形。

install_opener可以用来把一个opener对象设定为(全局)默认opener。这意味着调用urlopen的时候会使用你安装的opener。

opener对象有一个open方法,可以直接调用来获取URL资源,方式与urlopen相同:除非为了方便,否则不需要调用install_opener

基本认证

为了说明创建和安装一个handler,我们使用HTTPBasicAuthHandler为例。关于这个话题的更多细节—包括关于基本认证如何工作的说明—请参看基本认证教程。

当需要认证(或授权)时,服务器会发送一个头信息(还有一个401错误码)请求认证。它指定了认证模式和一个“realm(领域)”。这个头信息的形式看起来是:WWW-Authenticate:SCHEME realm="REALM"

例如,WWW-Authenticate: Basic realm="cPanel Users"

如何客户端应该重新发起请求,并把对应realm的用户名与密码加入请求的头信息中。这就是“基础认证”。为了简化这个过程,我们创建一个HTTPBasicAuthHandler实例,再有一个opener来使用这个handler。

HTTPBasicAuthHandler使用一个叫做密码管理器的对象处理url和用户名密码域的映射。如果你知道领域是什么(根据服务器发送的认证头信息得知),那么你可以使用HTTPPasswordMgr。人们往往不在乎领域是什么。在那种情况下,最方便的就是使用HTTPPasswordMgrWithDefaultRealm。它允许你为一个url指定一个默认的用户名和密码。如果你没有为某个领域提供可选的组合,anemia就会使用这个。我们通过把None作为add_password方法的realm参数来表示它。

顶层url就是需要认证的第一个url,比你传递给add_password()方法的url更“深层”的url也将匹配。

# create a password manager
password_mgr = urllib.request.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 = urllib.request.HTTPBasicAuthHandler(password_mgr)

# create "opener" (OpenerDirector instance)
opener = urllib.request.build_opener(handler)

# use the opener to fetch a URL
opener.open(a_url)

# Install the opener.
# Now all calls to urllib.request.urlopen use our opener.
urllib.request.install_opener(opener)

注意:在上面的例子中,我们 build_opener时只使用了我们的HTTPBasicAuthHandler。默认情况下,opener包含处理常见情形的handler— ProxyHandler ( 如果设置了代理,比如http_proxy变量 ), UnknownHandlerHTTPHandler,HTTPDefaultErrorHandlerHTTPRedirectHandlerFTPHandlerFileHandlerDataHandlerHTTPErrorProcessor.

事实上,顶层url要么是一个完整的url(包括“http:”模式和主机名以及可选的端口号),例如“http://example.com/” ,或者是一个“授权机构”(即主机名,可以再加一个端口号)如“example.com” 或“example.com:8080” 。如果使用“授权机构”的话,一定不能包含“userinfo”元素—比如“joe:[email protected]”就是不正确的。

代理

urllib会自动地检测并使用你的代理设置。这是通过ProxyHandler完成的,它是在检测到一个代理设置时的正常处理程序链的一部分。通常这是个好事,但是有些时候它可能没有用。还有一个方法就是设置我们自己的ProxyHandler,不定义代理。做法与设置一个Basic Authentication的步骤相同:

>>> proxy_support = urllib.request.ProxyHandler({})
>>> opener = urllib.request.build_opener(proxy_support)
>>> urllib.request.install_opener(opener)

Note:现在urllib.request不支持通过代理访问一个https网址。不过,可以通过这篇教程实现。

如果设置一个变量REQUEST_METHOD,那么HTTP_PROXY就会被忽略,具体可以查看getproxies()的文档

套接字层

Python对于获取网络数据的支持是有层次的。urllib使用的是http.client库,而他又转而使用socket库。

在Python 2.3中你可以设定套接字的超时等待时长。这在必须获取网页的应用中是很有用的。默认情况下,套接字模块没有超时设置,并且可能会一直等待。目前,套接字超时不在http.client或者urllib.request层可见了。但是,你可以为所有的套接字设定一个全局的等待时长:

import socket
import urllib.request

# timeout in seconds
timeout = 10
socket.setdefaulttimeout(timeout)

# this call to urllib.request.urlopen now uses the default timeout
# we have set in the socket module
req = urllib.request.Request('http://www.voidspace.org.uk')
response = urllib.request.urlopen(req)

转载于:https://www.cnblogs.com/GuoYaxiang/p/6232831.html

你可能感兴趣的:(python,网络)