“Python/docs/3.9/howto/urllib2”的版本间差异
(autoload) |
小 (Page commit) |
||
第1行: | 第1行: | ||
+ | {{DISPLAYTITLE:HOWTO 使用 urllib 包获取 Internet 资源 — Python 文档}} | ||
<div id="howto-fetch-internet-resources-using-the-urllib-package" class="section"> | <div id="howto-fetch-internet-resources-using-the-urllib-package" class="section"> | ||
<span id="urllib-howto"></span> | <span id="urllib-howto"></span> | ||
− | = HOWTO | + | = HOWTO 使用 urllib 包获取 Internet 资源 = |
− | ; | + | ; 作者 |
− | : [http://www.voidspace.org.uk/python/index.shtml | + | : [http://www.voidspace.org.uk/python/index.shtml 迈克尔·福特] |
<div class="admonition note"> | <div class="admonition note"> | ||
− | + | 笔记 | |
− | + | 本 HOWTO 的早期修订版有法语翻译,可从 [http://www.voidspace.org.uk/python/articles/urllib2_francais.shtml urllib2 - Le Manuel manquant] 获得。 | |
− | HOWTO | ||
第18行: | 第18行: | ||
<div id="introduction" class="section"> | <div id="introduction" class="section"> | ||
− | == | + | == 简介 == |
− | + | 相关文章 | |
− | + | 您可能还会发现以下有关使用 Python 获取 Web 资源的文章很有用: | |
− | |||
− | |||
− | |||
<ul> | <ul> | ||
− | <li><p>[http://www.voidspace.org.uk/python/articles/authentication.shtml | + | <li><p>[http://www.voidspace.org.uk/python/articles/authentication.shtml 基本认证]</p> |
<blockquote><div> | <blockquote><div> | ||
− | <p> | + | <p>关于 ''基本身份验证'' 的教程,以及 Python 中的示例。</p> |
</div></blockquote></li></ul> | </div></blockquote></li></ul> | ||
+ | '''urllib.request''' 是一个用于获取 URL(统一资源定位器)的 Python 模块。 它以 ''urlopen'' 函数的形式提供了一个非常简单的界面。 这能够使用各种不同的协议获取 URL。 它还提供了一个稍微复杂一些的界面来处理常见情况——比如基本身份验证、cookies、代理等等。 这些由称为处理程序和开启程序的对象提供。 | ||
− | </ | + | urllib.request 支持获取许多“URL 方案”的 URL(由 URL 中 <code>":"</code> 之前的字符串标识 - 例如 <code>"ftp"</code> 是 <code>"ftp://python.org/"</code> 的 URL 方案)使用它们的相关的网络协议(例如 FTP、HTTP)。 本教程重点介绍最常见的情况,HTTP。 |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | 对于简单的情况 ''urlopen'' 非常容易使用。 但是,一旦您在打开 HTTP URL 时遇到错误或非平凡情况,您就需要对超文本传输协议有所了解。 HTTP 最全面、最权威的参考文献是 <span id="index-0" class="target"></span>[https://tools.ietf.org/html/rfc2616.html RFC 2616]。 这是一份技术文档,并非旨在易于阅读。 本 HOWTO 旨在说明如何使用 ''urllib'',其中包含有关 HTTP 的足够详细信息以帮助您完成。 它不是要替换 [[../../library/urllib.request#module-urllib|urllib.request]] 文档,而是对它们的补充。 | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
第61行: | 第42行: | ||
<div id="fetching-urls" class="section"> | <div id="fetching-urls" class="section"> | ||
− | == | + | == 获取网址 == |
− | + | 最简单的使用 urllib.request 的方法如下: | |
<div class="highlight-python3 notranslate"> | <div class="highlight-python3 notranslate"> | ||
第69行: | 第50行: | ||
<div class="highlight"> | <div class="highlight"> | ||
− | < | + | <syntaxhighlight lang="python3">import urllib.request |
with urllib.request.urlopen('http://python.org/') as response: | with urllib.request.urlopen('http://python.org/') as response: | ||
− | html = response.read()</ | + | html = response.read()</syntaxhighlight> |
</div> | </div> | ||
</div> | </div> | ||
− | + | 如果您希望通过 URL 检索资源并将其存储在临时位置,您可以通过 [[../../library/shutil#shutil|shutil.copyfileobj()]] 和 [[../../library/tempfile#tempfile|tempfile.NamedTemporaryFile()]] 函数来实现: | |
− | |||
− | [[../../library/tempfile#tempfile| | ||
<div class="highlight-python3 notranslate"> | <div class="highlight-python3 notranslate"> | ||
第84行: | 第63行: | ||
<div class="highlight"> | <div class="highlight"> | ||
− | < | + | <syntaxhighlight lang="python3">import shutil |
import tempfile | import tempfile | ||
import urllib.request | import urllib.request | ||
第93行: | 第72行: | ||
with open(tmp_file.name) as html: | with open(tmp_file.name) as html: | ||
− | pass</ | + | pass</syntaxhighlight> |
</div> | </div> | ||
</div> | </div> | ||
− | + | urllib 的许多用途就是这么简单(请注意,我们可以使用以 'ftp:'、'file:' 等开头的 URL,而不是 'http:' URL)。 但是,本教程的目的是解释更复杂的情况,重点是 HTTP。 | |
− | |||
− | |||
− | |||
− | HTTP | + | HTTP 基于请求和响应 - 客户端发出请求,服务器发送响应。 urllib.request 用 <code>Request</code> 对象反映了这一点,该对象代表您正在发出的 HTTP 请求。 以最简单的形式创建一个 Request 对象,该对象指定要获取的 URL。 使用此请求对象调用 <code>urlopen</code> 会返回所请求 URL 的响应对象。 这个响应是一个类似文件的对象,这意味着你可以在响应上调用 <code>.read()</code>: |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
<div class="highlight-python3 notranslate"> | <div class="highlight-python3 notranslate"> | ||
第115行: | 第85行: | ||
<div class="highlight"> | <div class="highlight"> | ||
− | < | + | <syntaxhighlight lang="python3">import urllib.request |
req = urllib.request.Request('http://www.voidspace.org.uk') | req = urllib.request.Request('http://www.voidspace.org.uk') | ||
with urllib.request.urlopen(req) as response: | with urllib.request.urlopen(req) as response: | ||
− | the_page = response.read()</ | + | the_page = response.read()</syntaxhighlight> |
</div> | </div> | ||
</div> | </div> | ||
− | + | 请注意, urllib.request 使用相同的 Request 接口来处理所有 URL 方案。 例如,您可以像这样发出 FTP 请求: | |
− | |||
<div class="highlight-python3 notranslate"> | <div class="highlight-python3 notranslate"> | ||
第131行: | 第100行: | ||
<div class="highlight"> | <div class="highlight"> | ||
− | < | + | <syntaxhighlight lang="python3">req = urllib.request.Request('ftp://example.com/')</syntaxhighlight> |
</div> | </div> | ||
</div> | </div> | ||
− | + | 在 HTTP 的情况下,Request 对象允许您做两件额外的事情:首先,您可以传递要发送到服务器的数据。 其次,您可以将额外信息(“元数据”)''关于'' 数据或关于请求本身的信息传递给服务器——这些信息作为 HTTP“标头”发送。 让我们依次看看这些。 | |
− | |||
− | |||
− | |||
− | |||
<div id="data" class="section"> | <div id="data" class="section"> | ||
− | === | + | === 数据 === |
− | + | 有时您想将数据发送到 URL(通常 URL 将引用 CGI(通用网关接口)脚本或其他 Web 应用程序)。 对于 HTTP,这通常使用所谓的 '''POST''' 请求来完成。 当您提交在 Web 上填写的 HTML 表单时,浏览器通常会执行此操作。 并非所有 POST 都必须来自表单:您可以使用 POST 将任意数据传输到您自己的应用程序。 在 HTML 表单的常见情况下,数据需要以标准方式进行编码,然后作为 <code>data</code> 参数传递给 Request 对象。 编码是使用 [[../../library/urllib.parse#module-urllib|urllib.parse]] 库中的函数完成的。 | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
<div class="highlight-python3 notranslate"> | <div class="highlight-python3 notranslate"> | ||
第160行: | 第117行: | ||
<div class="highlight"> | <div class="highlight"> | ||
− | < | + | <syntaxhighlight lang="python3">import urllib.parse |
import urllib.request | import urllib.request | ||
第172行: | 第129行: | ||
req = urllib.request.Request(url, data) | req = urllib.request.Request(url, data) | ||
with urllib.request.urlopen(req) as response: | with urllib.request.urlopen(req) as response: | ||
− | the_page = response.read()</ | + | the_page = response.read()</syntaxhighlight> |
</div> | </div> | ||
</div> | </div> | ||
− | + | 请注意,有时需要其他编码(例如 用于从 HTML 表单上传文件 - 有关更多详细信息,请参阅 [https://www.w3.org/TR/REC-html40/interact/forms.html#h-17.13 HTML 规范,表单提交] )。 | |
− | |||
− | |||
− | + | 如果不传递 <code>data</code> 参数,urllib 将使用 '''GET''' 请求。 GET 和 POST 请求的不同之处在于 POST 请求通常具有“副作用”:它们以某种方式改变系统的状态(例如,通过向网站下订单以交付一百磅罐装垃圾邮件到你家门口)。 尽管 HTTP 标准明确指出 POST 旨在 ''总是'' 引起副作用,而 GET 请求 ''从不'' 引起副作用,但没有什么能阻止 GET 请求产生副作用,也没有 POST 请求没有副作用。 数据也可以通过在 URL 本身中编码来在 HTTP GET 请求中传递。 | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | POST | ||
− | GET | ||
− | + | 这是按如下方式完成的: | |
<div class="highlight-python3 notranslate"> | <div class="highlight-python3 notranslate"> | ||
第197行: | 第144行: | ||
<div class="highlight"> | <div class="highlight"> | ||
− | < | + | <syntaxhighlight lang="python3">>>> 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& | + | 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)</syntaxhighlight> | |
</div> | </div> | ||
</div> | </div> | ||
− | + | 请注意,完整的 URL 是通过向 URL 添加 <code>?</code> 后跟编码值来创建的。 | |
− | |||
第220行: | 第166行: | ||
<div id="headers" class="section"> | <div id="headers" class="section"> | ||
− | === | + | === 标题 === |
− | + | 我们将在此处讨论一个特定的 HTTP 标头,以说明如何向 HTTP 请求添加标头。 | |
− | |||
− | + | 一些网站[[#id8|1]]不喜欢被程序浏览,或者向不同浏览器发送不同版本[[#id9|2]]。 默认情况下,urllib 将自身标识为 <code>Python-urllib/x.y</code>(其中 <code>x</code> 和 <code>y</code> 是 Python 版本的主要和次要版本号,例如 <code>Python-urllib/2.5</code>),这可能会混淆站点,或者根本不起作用。 浏览器识别自己的方式是通过 <code>User-Agent</code> 标头 [[#id10|3]]。 当你创建一个 Request 对象时,你可以传入一个标头字典。 以下示例发出与上述相同的请求,但将自身标识为 Internet Explorer [[#id11|4]] 的版本。 | |
− | |||
− | <code>Python-urllib/x.y</code> | ||
− | |||
− | |||
− | |||
− | <code>User-Agent</code> | ||
− | |||
− | |||
− | Explorer [[#id11|4]] | ||
<div class="highlight-python3 notranslate"> | <div class="highlight-python3 notranslate"> | ||
第240行: | 第176行: | ||
<div class="highlight"> | <div class="highlight"> | ||
− | < | + | <syntaxhighlight lang="python3">import urllib.parse |
import urllib.request | import urllib.request | ||
第254行: | 第190行: | ||
req = urllib.request.Request(url, data, headers) | req = urllib.request.Request(url, data, headers) | ||
with urllib.request.urlopen(req) as response: | with urllib.request.urlopen(req) as response: | ||
− | the_page = response.read()</ | + | the_page = response.read()</syntaxhighlight> |
</div> | </div> | ||
</div> | </div> | ||
− | + | 响应也有两个有用的方法。 请参阅关于 [[#info-and-geturl|info 和 geturl]] 的部分,在我们了解出现问题时会发生什么之后。 | |
− | |||
第268行: | 第203行: | ||
<div id="handling-exceptions" class="section"> | <div id="handling-exceptions" class="section"> | ||
− | == | + | == 处理异常 == |
− | ''urlopen'' | + | ''urlopen'' 在无法处理响应时引发 <code>URLError</code>(尽管与 Python API 一样,内置异常如 [[../../library/exceptions#ValueError|ValueError]]、[[../../library/exceptions#TypeError|TypeError]] 等. 也可以提高)。 |
− | |||
− | [[../../library/exceptions#TypeError| | ||
− | <code>HTTPError</code> | + | <code>HTTPError</code> 是 <code>URLError</code> 的子类,在 HTTP URL 的特定情况下提出。 |
− | HTTP | ||
− | + | 异常类是从 [[../../library/urllib.error#module-urllib|urllib.error]] 模块导出的。 | |
<div id="urlerror" class="section"> | <div id="urlerror" class="section"> | ||
− | === | + | === 网址错误 === |
− | + | 通常,由于没有网络连接(没有到指定服务器的路由)或指定的服务器不存在,会引发 URLError。 在这种情况下,引发的异常将具有“原因”属性,它是一个包含错误代码和文本错误消息的元组。 | |
− | |||
− | |||
− | |||
− | + | 例如 | |
<div class="highlight-python3 notranslate"> | <div class="highlight-python3 notranslate"> | ||
第294行: | 第223行: | ||
<div class="highlight"> | <div class="highlight"> | ||
− | < | + | <syntaxhighlight lang="python3">>>> req = urllib.request.Request('http://www.pretend_server.org') |
− | + | >>> try: urllib.request.urlopen(req) | |
... except urllib.error.URLError as e: | ... except urllib.error.URLError as e: | ||
... print(e.reason) | ... print(e.reason) | ||
... | ... | ||
− | (4, 'getaddrinfo failed')</ | + | (4, 'getaddrinfo failed')</syntaxhighlight> |
</div> | </div> | ||
第308行: | 第237行: | ||
<div id="httperror" class="section"> | <div id="httperror" class="section"> | ||
− | === | + | === HTTP错误 === |
− | + | 来自服务器的每个 HTTP 响应都包含一个数字“状态代码”。 有时状态码表示服务器无法满足请求。 默认处理程序将为您处理其中一些响应(例如,如果响应是请求客户端从不同 URL 获取文档的“重定向”,则 urllib 将为您处理)。 对于无法处理的那些,urlopen 将引发 <code>HTTPError</code>。 典型的错误包括“404”(未找到页面)、“403”(请求被禁止)和“401”(需要身份验证)。 | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | 有关所有 HTTP 错误代码的参考,请参阅 <span id="index-1" class="target"></span>[https://tools.ietf.org/html/rfc2616.html RFC 2616] 的第 10 节。 | |
− | + | 引发的 <code>HTTPError</code> 实例将具有一个整数 'code' 属性,它对应于服务器发送的错误。 | |
− | |||
<div id="error-codes" class="section"> | <div id="error-codes" class="section"> | ||
− | ==== | + | ==== 错误代码 ==== |
− | + | 因为默认处理程序处理重定向(300 范围内的代码),并且 100-299 范围内的代码表示成功,所以您通常只会看到 400-599 范围内的错误代码。 | |
− | |||
− | |||
− | [[../../library/http.server#http.server.BaseHTTPRequestHandler| | + | [[../../library/http.server#http.server.BaseHTTPRequestHandler|http.server.BaseHTTPRequestHandler.responses]] 是一个有用的响应代码字典,其中显示了 <span id="index-2" class="target"></span>[https://tools.ietf.org/html/rfc2616.html RFC 2616] 使用的所有响应代码。 为方便起见,此处转载词典 |
− | |||
− | |||
<div class="highlight-python3 notranslate"> | <div class="highlight-python3 notranslate"> | ||
第339行: | 第257行: | ||
<div class="highlight"> | <div class="highlight"> | ||
− | < | + | <syntaxhighlight lang="python3"># Table mapping response codes to messages; entries have the |
# form {code: (shortmessage, longmessage)}. | # form {code: (shortmessage, longmessage)}. | ||
responses = { | responses = { | ||
第405行: | 第323行: | ||
'The gateway server did not receive a timely response'), | 'The gateway server did not receive a timely response'), | ||
505: ('HTTP Version Not Supported', 'Cannot fulfill request.'), | 505: ('HTTP Version Not Supported', 'Cannot fulfill request.'), | ||
− | }</ | + | }</syntaxhighlight> |
</div> | </div> | ||
</div> | </div> | ||
− | + | 当出现错误时,服务器通过返回 HTTP 错误代码 ''和'' 错误页面进行响应。 您可以使用 <code>HTTPError</code> 实例作为返回页面上的响应。 这意味着除了 code 属性外,它还具有 <code>urllib.response</code> 模块返回的 read、geturl 和 info 方法: | |
− | '' | ||
− | |||
− | |||
<div class="highlight-python3 notranslate"> | <div class="highlight-python3 notranslate"> | ||
第419行: | 第334行: | ||
<div class="highlight"> | <div class="highlight"> | ||
− | < | + | <syntaxhighlight lang="python3">>>> req = urllib.request.Request('http://www.python.org/fish.html') |
− | + | >>> try: | |
... urllib.request.urlopen(req) | ... urllib.request.urlopen(req) | ||
... except urllib.error.HTTPError as e: | ... except urllib.error.HTTPError as e: | ||
第427行: | 第342行: | ||
... | ... | ||
404 | 404 | ||
− | b' | + | b'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" |
− | + | "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n\n\n<html | |
... | ... | ||
− | + | <title>Page Not Found</title>\n | |
− | ...</ | + | ...</syntaxhighlight> |
</div> | </div> | ||
第442行: | 第357行: | ||
<div id="wrapping-it-up" class="section"> | <div id="wrapping-it-up" class="section"> | ||
− | === | + | === 包起来 === |
− | + | 因此,如果您想为 <code>HTTPError</code> ''或'' <code>URLError</code> 做好准备,有两种基本方法。 我更喜欢第二种方法。 | |
− | |||
<div id="number-1" class="section"> | <div id="number-1" class="section"> | ||
− | ==== | + | ==== 1号 ==== |
<div class="highlight-python3 notranslate"> | <div class="highlight-python3 notranslate"> | ||
第455行: | 第369行: | ||
<div class="highlight"> | <div class="highlight"> | ||
− | < | + | <syntaxhighlight lang="python3">from urllib.request import Request, urlopen |
from urllib.error import URLError, HTTPError | from urllib.error import URLError, HTTPError | ||
req = Request(someurl) | req = Request(someurl) | ||
第467行: | 第381行: | ||
print('Reason: ', e.reason) | print('Reason: ', e.reason) | ||
else: | else: | ||
− | # everything is fine</ | + | # everything is fine</syntaxhighlight> |
</div> | </div> | ||
第474行: | 第388行: | ||
<div class="admonition note"> | <div class="admonition note"> | ||
− | + | 笔记 | |
− | + | <code>except HTTPError</code> ''必须''在前,否则<code>except URLError</code>将''也''赶上<code>HTTPError</code>。 | |
− | |||
第485行: | 第398行: | ||
<div id="number-2" class="section"> | <div id="number-2" class="section"> | ||
− | ==== | + | ==== 2号 ==== |
<div class="highlight-python3 notranslate"> | <div class="highlight-python3 notranslate"> | ||
第491行: | 第404行: | ||
<div class="highlight"> | <div class="highlight"> | ||
− | < | + | <syntaxhighlight lang="python3">from urllib.request import Request, urlopen |
from urllib.error import URLError | from urllib.error import URLError | ||
req = Request(someurl) | req = Request(someurl) | ||
第504行: | 第417行: | ||
print('Error code: ', e.code) | print('Error code: ', e.code) | ||
else: | else: | ||
− | # everything is fine</ | + | # everything is fine</syntaxhighlight> |
</div> | </div> | ||
第517行: | 第430行: | ||
<div id="info-and-geturl" class="section"> | <div id="info-and-geturl" class="section"> | ||
− | == | + | == 信息和获取网址 == |
− | + | urlopen(或 <code>HTTPError</code> 实例)返回的响应有两个有用的方法 <code>info()</code> 和 <code>geturl()</code> 并在模块 [[../../library/urllib.request#module-urllib|urllib.response]] 中定义.. | |
− | |||
− | [[../../library/urllib.request#module-urllib| | ||
− | '''geturl''' - | + | '''geturl''' - 这将返回所获取页面的真实 URL。 这很有用,因为 <code>urlopen</code>(或使用的 opener 对象)可能跟随重定向。 获取的页面的 URL 可能与请求的 URL 不同。 |
− | |||
− | |||
− | '''info''' - | + | '''info''' - 这将返回一个类似字典的对象,描述所获取的页面,特别是服务器发送的标头。 它目前是一个 <code>http.client.HTTPMessage</code> 实例。 |
− | |||
− | <code>http.client.HTTPMessage</code> | ||
− | + | 典型的标头包括“Content-length”、“Content-type”等。 请参阅 [http://jkorpela.fi/http.html HTTP 标头快速参考] 以获取有用的 HTTP 标头列表及其含义和用途的简要说明。 | |
− | [http://jkorpela.fi/http.html | ||
− | |||
− | |||
第540行: | 第444行: | ||
<div id="openers-and-handlers" class="section"> | <div id="openers-and-handlers" class="section"> | ||
− | == | + | == 开瓶器和处理程序 == |
− | + | 当你获取一个 URL 时,你使用了一个 opener(一个可能令人困惑的名称 [[../../library/urllib.request#urllib.request|urllib.request.OpenerDirector]] 的实例)。 通常我们一直使用默认的开启器 - 通过 <code>urlopen</code> - 但您可以创建自定义开启器。 开启者使用处理程序。 所有的“繁重工作”都由搬运工完成。 每个处理程序都知道如何打开特定 URL 方案(http、ftp 等)的 URL,或者如何处理 URL 打开的一个方面,例如 HTTP 重定向或 HTTP cookie。 | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | 如果您想获取安装了特定处理程序的 URL,您将需要创建 opener,例如获取处理 cookie 的 opener,或获取不处理重定向的 opener。 | |
− | |||
− | |||
− | + | 要创建开启器,请实例化 <code>OpenerDirector</code>,然后重复调用 <code>.add_handler(some_handler_instance)</code>。 | |
− | <code>.add_handler(some_handler_instance)</code> | ||
− | + | 或者,您可以使用 <code>build_opener</code>,这是一个使用单个函数调用创建 opener 对象的便捷函数。 <code>build_opener</code> 默认添加多个处理程序,但提供了一种快速添加和/或覆盖默认处理程序的方法。 | |
− | |||
− | |||
− | |||
− | + | 您可能想要的其他类型的处理程序可以处理代理、身份验证和其他常见但稍微特殊的情况。 | |
− | |||
− | <code>install_opener</code> | + | <code>install_opener</code> 可用于使 <code>opener</code> 对象成为(全局)默认开启器。 这意味着对 <code>urlopen</code> 的调用将使用您安装的开启程序。 |
− | |||
− | |||
− | Opener | + | Opener 对象有一个 <code>open</code> 方法,可以像 <code>urlopen</code> 函数一样直接调用它来获取 url:不需要调用 <code>install_opener</code>,除非是为了方便. |
− | |||
− | <code>install_opener</code> | ||
第577行: | 第464行: | ||
<div id="id5" class="section"> | <div id="id5" class="section"> | ||
− | == | + | == 基本认证 == |
− | + | 为了说明创建和安装处理程序,我们将使用 <code>HTTPBasicAuthHandler</code>。 有关此主题的更详细讨论 - 包括对基本身份验证如何工作的解释 - 请参阅 [http://www.voidspace.org.uk/python/articles/authentication.shtml 基本身份验证教程] 。 | |
− | <code>HTTPBasicAuthHandler</code> | ||
− | |||
− | |||
− | + | 当需要身份验证时,服务器会发送一个请求身份验证的标头(以及 401 错误代码)。 这指定了身份验证方案和“领域”。 标题看起来像:<code>WWW-Authenticate: SCHEME realm="REALM"</code>。 | |
− | |||
− | |||
− | + | 例如 | |
<div class="highlight-none notranslate"> | <div class="highlight-none notranslate"> | ||
第594行: | 第476行: | ||
<div class="highlight"> | <div class="highlight"> | ||
− | <pre>WWW-Authenticate: Basic realm="cPanel Users"</pre> | + | <pre class="none">WWW-Authenticate: Basic realm="cPanel Users"</pre> |
</div> | </div> | ||
</div> | </div> | ||
− | + | 然后,客户端应使用作为请求标头包含的领域的适当名称和密码重试请求。 这是“基本身份验证”。 为了简化这个过程,我们可以创建一个 <code>HTTPBasicAuthHandler</code> 的实例和一个开启器来使用这个处理程序。 | |
− | |||
− | |||
− | <code>HTTPBasicAuthHandler</code> | ||
− | + | <code>HTTPBasicAuthHandler</code> 使用一个称为密码管理器的对象来处理 URL 和领域到密码和用户名的映射。 如果您知道领域是什么(来自服务器发送的身份验证标头),那么您可以使用 <code>HTTPPasswordMgr</code>。 通常人们并不关心领域是什么。 在这种情况下,使用 <code>HTTPPasswordMgrWithDefaultRealm</code> 会很方便。 这允许您为 URL 指定默认用户名和密码。 这将在您没有为特定领域提供替代组合的情况下提供。 我们通过提供 <code>None</code> 作为 <code>add_password</code> 方法的领域参数来表明这一点。 | |
− | |||
− | |||
− | <code>HTTPPasswordMgr</code> | ||
− | |||
− | |||
− | |||
− | |||
− | <code>add_password</code> | ||
− | + | 顶级 URL 是第一个需要身份验证的 URL。 比传递给 .add_password() 的 URL“更深”的 URL 也将匹配。 | |
− | |||
<div class="highlight-python3 notranslate"> | <div class="highlight-python3 notranslate"> | ||
第621行: | 第491行: | ||
<div class="highlight"> | <div class="highlight"> | ||
− | < | + | <syntaxhighlight lang="python3"># create a password manager |
password_mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm() | password_mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm() | ||
# Add the username and password. | # Add the username and password. | ||
# If we knew the realm, we could use it instead of None. | # If we knew the realm, we could use it instead of None. | ||
− | top_level_url = | + | top_level_url = "http://example.com/foo/" |
password_mgr.add_password(None, top_level_url, username, password) | password_mgr.add_password(None, top_level_url, username, password) | ||
handler = urllib.request.HTTPBasicAuthHandler(password_mgr) | handler = urllib.request.HTTPBasicAuthHandler(password_mgr) | ||
− | # create | + | # create "opener" (OpenerDirector instance) |
opener = urllib.request.build_opener(handler) | opener = urllib.request.build_opener(handler) | ||
第639行: | 第509行: | ||
# Install the opener. | # Install the opener. | ||
# Now all calls to urllib.request.urlopen use our opener. | # Now all calls to urllib.request.urlopen use our opener. | ||
− | urllib.request.install_opener(opener)</ | + | urllib.request.install_opener(opener)</syntaxhighlight> |
</div> | </div> | ||
第646行: | 第516行: | ||
<div class="admonition note"> | <div class="admonition note"> | ||
− | + | 笔记 | |
− | + | 在上面的示例中,我们仅将 <code>HTTPBasicAuthHandler</code> 提供给 <code>build_opener</code>。 默认情况下,开启程序具有正常情况下的处理程序 - <code>ProxyHandler</code>(如果设置了代理设置,例如 <span id="index-3" class="target"></span><code>http_proxy</code> 环境变量)、<code>UnknownHandler</code>、[ X162X]、<code>HTTPDefaultErrorHandler</code>、<code>HTTPRedirectHandler</code>、<code>FTPHandler</code>、<code>FileHandler</code>、<code>DataHandler</code>、<code>HTTPErrorProcessor</code>. | |
− | <code>build_opener</code> | ||
− | |||
− | |||
− | <code>HTTPDefaultErrorHandler</code> | ||
− | <code>FileHandler</code> | ||
</div> | </div> | ||
− | <code>top_level_url</code> | + | <code>top_level_url</code> 实际上是 ''或者'' 一个完整的 URL(包括“http:”方案组件和主机名以及可选的端口号)例如 <code>"http://example.com/"</code> ''或'' 一个“权威”(即 主机名,可选地包括端口号)例如 <code>"example.com"</code> 或 <code>"example.com:8080"</code>(后一个示例包括端口号)。 权限(如果存在)不得包含“userinfo”组件 - 例如 <code>"joe:password@example.com"</code> 不正确。 |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
第669行: | 第528行: | ||
<div id="proxies" class="section"> | <div id="proxies" class="section"> | ||
− | == | + | == 代理 == |
− | '''urllib''' | + | '''urllib''' 将自动检测您的代理设置并使用这些设置。 这是通过 <code>ProxyHandler</code>,当检测到代理设置时,它是正常处理程序链的一部分。 通常这是一件好事,但有时它可能没有帮助 [[#id12|5]]。 一种方法是设置我们自己的 <code>ProxyHandler</code>,没有定义代理。 这是使用设置 [http://www.voidspace.org.uk/python/articles/authentication.shtml 基本身份验证] 处理程序的类似步骤完成的: |
− | |||
− | |||
− | |||
− | <code>ProxyHandler</code> | ||
− | |||
<div class="highlight-python3 notranslate"> | <div class="highlight-python3 notranslate"> | ||
第682行: | 第536行: | ||
<div class="highlight"> | <div class="highlight"> | ||
− | < | + | <syntaxhighlight lang="python3">>>> proxy_support = urllib.request.ProxyHandler({}) |
− | + | >>> opener = urllib.request.build_opener(proxy_support) | |
− | + | >>> urllib.request.install_opener(opener)</syntaxhighlight> | |
</div> | </div> | ||
第691行: | 第545行: | ||
<div class="admonition note"> | <div class="admonition note"> | ||
− | + | 笔记 | |
− | + | 目前 <code>urllib.request</code> ''不'' 支持通过代理获取 <code>https</code> 位置。 但是,这可以通过扩展 urllib.request 来启用,如配方 [[#id13|6]] 中所示。 | |
− | |||
− | |||
第701行: | 第553行: | ||
<div class="admonition note"> | <div class="admonition note"> | ||
− | + | 笔记 | |
− | <code> | + | 如果设置了变量 <code>REQUEST_METHOD</code>,则 <code>HTTP_PROXY</code> 将被忽略; 请参阅有关 [[../../library/urllib.request#urllib.request|getproxies()]] 的文档。 |
− | |||
第712行: | 第563行: | ||
<div id="sockets-and-layers" class="section"> | <div id="sockets-and-layers" class="section"> | ||
− | == | + | == 套接字和层 == |
− | + | 从 Web 获取资源的 Python 支持是分层的。 urllib 使用 [[../../library/http.client#module-http|http.client]] 库,后者又使用套接字库。 | |
− | |||
− | + | 从 Python 2.3 开始,您可以指定套接字在超时之前应等待响应的时间。 这在必须获取网页的应用程序中很有用。 默认情况下,套接字模块具有 ''无超时'' 并且可以挂起。 目前,套接字超时未在 http.client 或 urllib.request 级别公开。 但是,您可以使用以下命令为所有套接字全局设置默认超时 | |
− | |||
− | |||
− | |||
− | |||
<div class="highlight-python3 notranslate"> | <div class="highlight-python3 notranslate"> | ||
第727行: | 第573行: | ||
<div class="highlight"> | <div class="highlight"> | ||
− | < | + | <syntaxhighlight lang="python3">import socket |
import urllib.request | import urllib.request | ||
第737行: | 第583行: | ||
# we have set in the socket module | # we have set in the socket module | ||
req = urllib.request.Request('http://www.voidspace.org.uk') | req = urllib.request.Request('http://www.voidspace.org.uk') | ||
− | response = urllib.request.urlopen(req)</ | + | response = urllib.request.urlopen(req)</syntaxhighlight> |
</div> | </div> | ||
第744行: | 第590行: | ||
</div> | </div> | ||
+ | |||
+ | ----- | ||
+ | |||
<div id="footnotes" class="section"> | <div id="footnotes" class="section"> | ||
− | == | + | == 脚注 == |
− | + | 本文档由 John Lee 审阅和修订。 | |
; <span class="brackets">[[#id1|1]]</span> | ; <span class="brackets">[[#id1|1]]</span> | ||
− | : | + | : 以谷歌为例。 |
; <span class="brackets">[[#id2|2]]</span> | ; <span class="brackets">[[#id2|2]]</span> | ||
− | : | + | : 浏览器嗅探对于网站设计来说是一种非常糟糕的做法 - 使用网络标准构建网站更为明智。 不幸的是,许多网站仍然向不同的浏览器发送不同的版本。 |
; <span class="brackets">[[#id3|3]]</span> | ; <span class="brackets">[[#id3|3]]</span> | ||
− | : | + | : MSIE 6 的用户代理是 '''Mozilla/4.0(兼容;MSIE 6.0;Windows NT 5.1;SV1;.NET CLR 1.1.4322)''' |
; <span class="brackets">[[#id4|4]]</span> | ; <span class="brackets">[[#id4|4]]</span> | ||
− | : | + | : 更多 HTTP 请求头的详细信息,请参见 [http://jkorpela.fi/http.html HTTP 头快速参考] 。 |
; <span class="brackets">[[#id6|5]]</span> | ; <span class="brackets">[[#id6|5]]</span> | ||
− | : | + | : 就我而言,我必须使用代理才能在工作中访问互联网。 如果您尝试通过此代理获取 ''localhost'' URL,它会阻止它们。 IE 设置为使用 urllib 接收的代理。 为了使用本地主机服务器测试脚本,我必须阻止 urllib 使用代理。 |
; <span class="brackets">[[#id7|6]]</span> | ; <span class="brackets">[[#id7|6]]</span> | ||
− | : urllib | + | : SSL 代理的 urllib 开启器(CONNECT 方法):[https://code.activestate.com/recipes/456195/ ASPN Cookbook Recipe]。 |
</div> | </div> | ||
+ | |||
+ | </div> | ||
+ | <div class="clearer"> | ||
+ | |||
+ | |||
</div> | </div> | ||
− | [[Category:Python 3.9 | + | [[Category:Python 3.9 文档]] |
2021年10月31日 (日) 04:51的最新版本
HOWTO 使用 urllib 包获取 Internet 资源
- 作者
- 迈克尔·福特
简介
相关文章
您可能还会发现以下有关使用 Python 获取 Web 资源的文章很有用:
-
关于 基本身份验证 的教程,以及 Python 中的示例。
urllib.request 是一个用于获取 URL(统一资源定位器)的 Python 模块。 它以 urlopen 函数的形式提供了一个非常简单的界面。 这能够使用各种不同的协议获取 URL。 它还提供了一个稍微复杂一些的界面来处理常见情况——比如基本身份验证、cookies、代理等等。 这些由称为处理程序和开启程序的对象提供。
urllib.request 支持获取许多“URL 方案”的 URL(由 URL 中 ":"
之前的字符串标识 - 例如 "ftp"
是 "ftp://python.org/%22
的 URL 方案)使用它们的相关的网络协议(例如 FTP、HTTP)。 本教程重点介绍最常见的情况,HTTP。
对于简单的情况 urlopen 非常容易使用。 但是,一旦您在打开 HTTP URL 时遇到错误或非平凡情况,您就需要对超文本传输协议有所了解。 HTTP 最全面、最权威的参考文献是 RFC 2616。 这是一份技术文档,并非旨在易于阅读。 本 HOWTO 旨在说明如何使用 urllib,其中包含有关 HTTP 的足够详细信息以帮助您完成。 它不是要替换 urllib.request 文档,而是对它们的补充。
获取网址
最简单的使用 urllib.request 的方法如下:
如果您希望通过 URL 检索资源并将其存储在临时位置,您可以通过 shutil.copyfileobj() 和 tempfile.NamedTemporaryFile() 函数来实现:
urllib 的许多用途就是这么简单(请注意,我们可以使用以 'ftp:'、'file:' 等开头的 URL,而不是 'http:' URL)。 但是,本教程的目的是解释更复杂的情况,重点是 HTTP。
HTTP 基于请求和响应 - 客户端发出请求,服务器发送响应。 urllib.request 用 Request
对象反映了这一点,该对象代表您正在发出的 HTTP 请求。 以最简单的形式创建一个 Request 对象,该对象指定要获取的 URL。 使用此请求对象调用 urlopen
会返回所请求 URL 的响应对象。 这个响应是一个类似文件的对象,这意味着你可以在响应上调用 .read()
:
请注意, urllib.request 使用相同的 Request 接口来处理所有 URL 方案。 例如,您可以像这样发出 FTP 请求:
在 HTTP 的情况下,Request 对象允许您做两件额外的事情:首先,您可以传递要发送到服务器的数据。 其次,您可以将额外信息(“元数据”)关于 数据或关于请求本身的信息传递给服务器——这些信息作为 HTTP“标头”发送。 让我们依次看看这些。
数据
有时您想将数据发送到 URL(通常 URL 将引用 CGI(通用网关接口)脚本或其他 Web 应用程序)。 对于 HTTP,这通常使用所谓的 POST 请求来完成。 当您提交在 Web 上填写的 HTML 表单时,浏览器通常会执行此操作。 并非所有 POST 都必须来自表单:您可以使用 POST 将任意数据传输到您自己的应用程序。 在 HTML 表单的常见情况下,数据需要以标准方式进行编码,然后作为 data
参数传递给 Request 对象。 编码是使用 urllib.parse 库中的函数完成的。
请注意,有时需要其他编码(例如 用于从 HTML 表单上传文件 - 有关更多详细信息,请参阅 HTML 规范,表单提交 )。
如果不传递 data
参数,urllib 将使用 GET 请求。 GET 和 POST 请求的不同之处在于 POST 请求通常具有“副作用”:它们以某种方式改变系统的状态(例如,通过向网站下订单以交付一百磅罐装垃圾邮件到你家门口)。 尽管 HTTP 标准明确指出 POST 旨在 总是 引起副作用,而 GET 请求 从不 引起副作用,但没有什么能阻止 GET 请求产生副作用,也没有 POST 请求没有副作用。 数据也可以通过在 URL 本身中编码来在 HTTP GET 请求中传递。
这是按如下方式完成的:
请注意,完整的 URL 是通过向 URL 添加 ?
后跟编码值来创建的。
标题
我们将在此处讨论一个特定的 HTTP 标头,以说明如何向 HTTP 请求添加标头。
一些网站1不喜欢被程序浏览,或者向不同浏览器发送不同版本2。 默认情况下,urllib 将自身标识为 Python-urllib/x.y
(其中 x
和 y
是 Python 版本的主要和次要版本号,例如 Python-urllib/2.5
),这可能会混淆站点,或者根本不起作用。 浏览器识别自己的方式是通过 User-Agent
标头 3。 当你创建一个 Request 对象时,你可以传入一个标头字典。 以下示例发出与上述相同的请求,但将自身标识为 Internet Explorer 4 的版本。
响应也有两个有用的方法。 请参阅关于 info 和 geturl 的部分,在我们了解出现问题时会发生什么之后。
处理异常
urlopen 在无法处理响应时引发 URLError
(尽管与 Python API 一样,内置异常如 ValueError、TypeError 等. 也可以提高)。
HTTPError
是 URLError
的子类,在 HTTP URL 的特定情况下提出。
异常类是从 urllib.error 模块导出的。
网址错误
通常,由于没有网络连接(没有到指定服务器的路由)或指定的服务器不存在,会引发 URLError。 在这种情况下,引发的异常将具有“原因”属性,它是一个包含错误代码和文本错误消息的元组。
例如
HTTP错误
来自服务器的每个 HTTP 响应都包含一个数字“状态代码”。 有时状态码表示服务器无法满足请求。 默认处理程序将为您处理其中一些响应(例如,如果响应是请求客户端从不同 URL 获取文档的“重定向”,则 urllib 将为您处理)。 对于无法处理的那些,urlopen 将引发 HTTPError
。 典型的错误包括“404”(未找到页面)、“403”(请求被禁止)和“401”(需要身份验证)。
有关所有 HTTP 错误代码的参考,请参阅 RFC 2616 的第 10 节。
引发的 HTTPError
实例将具有一个整数 'code' 属性,它对应于服务器发送的错误。
错误代码
因为默认处理程序处理重定向(300 范围内的代码),并且 100-299 范围内的代码表示成功,所以您通常只会看到 400-599 范围内的错误代码。
http.server.BaseHTTPRequestHandler.responses 是一个有用的响应代码字典,其中显示了 RFC 2616 使用的所有响应代码。 为方便起见,此处转载词典
当出现错误时,服务器通过返回 HTTP 错误代码 和 错误页面进行响应。 您可以使用 HTTPError
实例作为返回页面上的响应。 这意味着除了 code 属性外,它还具有 urllib.response
模块返回的 read、geturl 和 info 方法:
包起来
因此,如果您想为 HTTPError
或 URLError
做好准备,有两种基本方法。 我更喜欢第二种方法。
1号
笔记
except HTTPError
必须在前,否则except URLError
将也赶上HTTPError
。
2号
信息和获取网址
urlopen(或 HTTPError
实例)返回的响应有两个有用的方法 info()
和 geturl()
并在模块 urllib.response 中定义..
geturl - 这将返回所获取页面的真实 URL。 这很有用,因为 urlopen
(或使用的 opener 对象)可能跟随重定向。 获取的页面的 URL 可能与请求的 URL 不同。
info - 这将返回一个类似字典的对象,描述所获取的页面,特别是服务器发送的标头。 它目前是一个 http.client.HTTPMessage
实例。
典型的标头包括“Content-length”、“Content-type”等。 请参阅 HTTP 标头快速参考 以获取有用的 HTTP 标头列表及其含义和用途的简要说明。
开瓶器和处理程序
当你获取一个 URL 时,你使用了一个 opener(一个可能令人困惑的名称 urllib.request.OpenerDirector 的实例)。 通常我们一直使用默认的开启器 - 通过 urlopen
- 但您可以创建自定义开启器。 开启者使用处理程序。 所有的“繁重工作”都由搬运工完成。 每个处理程序都知道如何打开特定 URL 方案(http、ftp 等)的 URL,或者如何处理 URL 打开的一个方面,例如 HTTP 重定向或 HTTP cookie。
如果您想获取安装了特定处理程序的 URL,您将需要创建 opener,例如获取处理 cookie 的 opener,或获取不处理重定向的 opener。
要创建开启器,请实例化 OpenerDirector
,然后重复调用 .add_handler(some_handler_instance)
。
或者,您可以使用 build_opener
,这是一个使用单个函数调用创建 opener 对象的便捷函数。 build_opener
默认添加多个处理程序,但提供了一种快速添加和/或覆盖默认处理程序的方法。
您可能想要的其他类型的处理程序可以处理代理、身份验证和其他常见但稍微特殊的情况。
install_opener
可用于使 opener
对象成为(全局)默认开启器。 这意味着对 urlopen
的调用将使用您安装的开启程序。
Opener 对象有一个 open
方法,可以像 urlopen
函数一样直接调用它来获取 url:不需要调用 install_opener
,除非是为了方便.
基本认证
为了说明创建和安装处理程序,我们将使用 HTTPBasicAuthHandler
。 有关此主题的更详细讨论 - 包括对基本身份验证如何工作的解释 - 请参阅 基本身份验证教程 。
当需要身份验证时,服务器会发送一个请求身份验证的标头(以及 401 错误代码)。 这指定了身份验证方案和“领域”。 标题看起来像:WWW-Authenticate: SCHEME realm="REALM"
。
例如
WWW-Authenticate: Basic realm="cPanel Users"
然后,客户端应使用作为请求标头包含的领域的适当名称和密码重试请求。 这是“基本身份验证”。 为了简化这个过程,我们可以创建一个 HTTPBasicAuthHandler
的实例和一个开启器来使用这个处理程序。
HTTPBasicAuthHandler
使用一个称为密码管理器的对象来处理 URL 和领域到密码和用户名的映射。 如果您知道领域是什么(来自服务器发送的身份验证标头),那么您可以使用 HTTPPasswordMgr
。 通常人们并不关心领域是什么。 在这种情况下,使用 HTTPPasswordMgrWithDefaultRealm
会很方便。 这允许您为 URL 指定默认用户名和密码。 这将在您没有为特定领域提供替代组合的情况下提供。 我们通过提供 None
作为 add_password
方法的领域参数来表明这一点。
顶级 URL 是第一个需要身份验证的 URL。 比传递给 .add_password() 的 URL“更深”的 URL 也将匹配。
笔记
在上面的示例中,我们仅将 HTTPBasicAuthHandler
提供给 build_opener
。 默认情况下,开启程序具有正常情况下的处理程序 - ProxyHandler
(如果设置了代理设置,例如 http_proxy
环境变量)、UnknownHandler
、[ X162X]、HTTPDefaultErrorHandler
、HTTPRedirectHandler
、FTPHandler
、FileHandler
、DataHandler
、HTTPErrorProcessor
.
top_level_url
实际上是 或者 一个完整的 URL(包括“http:”方案组件和主机名以及可选的端口号)例如 "http://example.com/%22
或 一个“权威”(即 主机名,可选地包括端口号)例如 "example.com"
或 "example.com:8080"
(后一个示例包括端口号)。 权限(如果存在)不得包含“userinfo”组件 - 例如 "joe:password@example.com"
不正确。
代理
urllib 将自动检测您的代理设置并使用这些设置。 这是通过 ProxyHandler
,当检测到代理设置时,它是正常处理程序链的一部分。 通常这是一件好事,但有时它可能没有帮助 5。 一种方法是设置我们自己的 ProxyHandler
,没有定义代理。 这是使用设置 基本身份验证 处理程序的类似步骤完成的:
套接字和层
从 Web 获取资源的 Python 支持是分层的。 urllib 使用 http.client 库,后者又使用套接字库。
从 Python 2.3 开始,您可以指定套接字在超时之前应等待响应的时间。 这在必须获取网页的应用程序中很有用。 默认情况下,套接字模块具有 无超时 并且可以挂起。 目前,套接字超时未在 http.client 或 urllib.request 级别公开。 但是,您可以使用以下命令为所有套接字全局设置默认超时
脚注
本文档由 John Lee 审阅和修订。
- 1
- 以谷歌为例。
- 2
- 浏览器嗅探对于网站设计来说是一种非常糟糕的做法 - 使用网络标准构建网站更为明智。 不幸的是,许多网站仍然向不同的浏览器发送不同的版本。
- 3
- MSIE 6 的用户代理是 Mozilla/4.0(兼容;MSIE 6.0;Windows NT 5.1;SV1;.NET CLR 1.1.4322)
- 4
- 更多 HTTP 请求头的详细信息,请参见 HTTP 头快速参考 。
- 5
- 就我而言,我必须使用代理才能在工作中访问互联网。 如果您尝试通过此代理获取 localhost URL,它会阻止它们。 IE 设置为使用 urllib 接收的代理。 为了使用本地主机服务器测试脚本,我必须阻止 urllib 使用代理。
- 6
- SSL 代理的 urllib 开启器(CONNECT 方法):ASPN Cookbook Recipe。