源代码: Lib / urllib / request.py
未完待续…
此模块定义了有助于在现实环境中打开URL(主要是HTTP)的函数和类 - 基本和摘要式身份验证,重定向,cookie等。
也可以看看
建议将Requests包用于更高级别的HTTP客户端接口。
打开一个URL,url可以是字符串或 Request对象。
data必须是指定要发送到服务器的其他数据的对象,如果不需要此类数据可以指定为None
。详情请见Request 。
urllib.request
模块使用HTTP/1.1
并且在其HTTP请求中包含请求头Connection:close
。
可选的timeout参数指定阻塞操作(如连接尝试)的超时时间(以秒为单位)。如果未指定,将使用全局默认超时设置。这实际上仅适用于HTTP,HTTPS,FTP
连接。
如果指定了context,则它必须是ssl.SSLContext的实例,用来描述各种SSL选项。有关详细信息,请参阅HTTPSConnection。
可选的cafile和capath参数为HTTPS请求指定一组可信CA证书。 cafile应指向包含一组 CA证书的单个文件,而capath应指向哈希证书文件的目录。更多信息可以在ssl.SSLContext.load_verify_locations()中找到。
cadefault参数被忽略。
此函数始终返回一个对象,该对象可用作 上下文管理器 并具有如下的方法
>>> from urllib import request
>>> r = request.urlopen("http://www.baidu.com")
>>> r.geturl()
'http://www.baidu.com'
>>> r.info()
<http.client.HTTPMessage object at 0x01C19C50>
>>> r.getcode()
200
>>> r.info()._headers
[('Bdpagetype', '1'), ('Bdqid', '0xd0d3ce2a00001272'), ('Cache-Control', 'private'), ('Content-Type', 'text/html'), ('Cxy_all', 'baidu+8c65a65315fd91f
1324cd1a322701b91'), ('Date', 'Tue, 18 Dec 2018 13:06:43 GMT'), ('Expires', 'Tue, 18 Dec 2018 13:06:05 GMT'), ('P3p', 'CP=" OTI DSP COR IVA OUR IND CO
M "'), ('Server', 'BWS/1.1'), ('Set-Cookie', 'BAIDUID=064B59D7B588D0DD317AE0B14B544F69:FG=1; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647;
path=/; domain=.baidu.com'), ('Set-Cookie', 'BIDUPSID=064B59D7B588D0DD317AE0B14B544F69; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=
/; domain=.baidu.com'), ('Set-Cookie', 'PSTM=1545138403; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com'), ('Set-C
ookie', 'delPer=0; path=/; domain=.baidu.com'), ('Set-Cookie', 'BDSVRTM=0; path=/'), ('Set-Cookie', 'BD_HOME=0; path=/'), ('Set-Cookie', 'H_PS_PSSID=1
431_21082_28131_27751_27244_22158; path=/; domain=.baidu.com'), ('Vary', 'Accept-Encoding'), ('X-Ua-Compatible', 'IE=Edge,chrome=1'), ('Connection', '
close'), ('Transfer-Encoding', 'chunked')]
对于HTTP和HTTPS的URL,此函数返回经过稍微修改的 http.client.HTTPResponse 对象。除了上面的三个新方法之外,msg属性包含与reason属性相同的信息 - 服务器返回的原因 - 而不是 HTTPResponse文档中指定的响应头。
>>> type(r)
<class 'http.client.HTTPResponse'>
>>> r.reason
'OK'
>>> r.msg
'OK'
对于FTP,文件和数据URL,请求显示地由历史遗留的URLopener和FancyURLopener类处理 ,此函数返回一个urllib.response.addinfourl对象。
协议错误会引发引发URLError。
>>> r = request.urlopen("httpf://www.baidu.com")
Traceback (most recent call last):
File "" , line 1, in <module>
...
raise URLError('unknown url type: %s' % type)
urllib.error.URLError: <urlopen error unknown url type: httpf>
请注意,如果没有handler来处理请求,则可能会返回None
(尽管默认安装的全局 OpenerDirector
使用 UnknownHandler
确保永远不会发生这种情况)。
此外,如果检测到代理设置(例如,当一个*_proxy
环境变量如http_proxy
已设置),默认安装的 ProxyHandler确保通过代理处理请求。
Python 2.6及更早版本的遗留函数urllib.urlopen
已经停止使用; urllib.request.urlopen()对应旧的 urllib2.urlopen。通过传递字典参数给urllib.urlopen
来完成代理的处理,可以通过使用ProxyHandler对象来获得这些参数 。
在版本3.2中更改:添加了cafile和capath。
版本3.2中已更改:如果可能,现在支持HTTPS虚拟主机(即,如果 ssl.HAS_SNI为true)。
版本3.2中的新功能:data可以是可迭代的对象。
在版本3.3中更改:添加了cadefault。
版本3.4.3中已更改:已添加context。
从版本3.6 开始不推荐使用:不推荐使用cafile,capath和cadefault以支持上下文context。请改用ssl.SSLContext.load_cert_chain(),或者让ssl.create_default_context()为您自动选择系统的可信CA证书。
返回一个OpenerDirector实例,它按照handler的给定顺序工作。handler可以是BaseHandler的实例或者是BaseHandler的子类(在这种情况下,必须可以在没有任何参数的情况下调用构造函数)。以下类的实例将可用于handler:ProxyHandler
(如果检测到代理设置),UnknownHandler,HTTPHandler, HTTPDefaultErrorHandler,HTTPRedirectHandler, FTPHandler,FileHandler,HTTPErrorProcessor
。
如果Python安装具有SSL支持(即,如果可以导入模块ssl),HTTPSHandler也将添加到可用的handler中。
一个BaseHandler子类,还可以改变其handler_order 属性,修改其在处理程序列表中的位置。
译者实例:
为什么不使用urlopen(),而使用opener,因为前者有些高级特性无法使用,比如代理等,此时就要自定义opener来使用这些高级特性。
from urllib import request
old_url = 'http://www.baidu.com/'
# 普通的opener和handler,和request.urlopen()一样
http_handler = request.HTTPHandler()
opener = request.build_opener(http_handler)
r = opener.open(old_url)
print(r.reason,type(r)
# 输出结果
OK <class 'urllib.request.OpenerDirector'>
# 创建了一个ProxyHandler()
def bh_proxy():
old_url = 'http://www.ip111.cn/'
http_proxy_handler = request.ProxyHandler({"http":"122.227.139.170:3128"})
opener = request.build_opener(http_proxy_handler)
r = opener.open(old_url)
print(r.reason,type(r))
import re
content = r.read().decode(encoding="utf-8",errors="replace")
#print(content)
print(re.search(r'(\d{1,3}\.){3}\d{1,3}',content))
bh_proxy()
# 输出结果
OK <class 'urllib.request.OpenerDirector'>
<re.Match object; span=(1788, 1803), match='122.227.139.170'>
将OpenerDirector实例安装为默认的全局opener。只有当你想让urlopen使用那个opener时才需要用到此函数; 否则,只需要调用OpenerDirector.open()来替代 urlopen()。代码不检查是否真的是一个OpenerDirector,任何具有适当接口的类都可以使用。
译者注:
让我们在上面例子中的bh_proxy()增加一段代码:
def bh_proxy():
# ...
# 第二部分
print("-".center(50,'-'))
r = request.urlopen(old_url)
content = r.read().decode(encoding="utf-8", errors="replace")
# print(content)
print(re.search(r'(\d{1,3}\.){3}\d{1,3}', content))
# 输出结果
OK <class 'urllib.request.OpenerDirector'>
<re.Match object; span=(1788, 1803), match='122.227.139.170'>
--------------------------------------------------
<re.Match object; span=(1788, 1802), match='111.222.333.444'>
可以看到,如果urlopen()没有走代理,如果想让它一直走代理,就可以使用install_opener函数,继续对上面的例子修改。
def bh_proxy():
# ...
http_proxy_handler = request.ProxyHandler({"http":"122.227.139.170:3128"})
opener = request.build_opener(http_proxy_handler)
request.install_opener(opener)
r = opener.open(old_url)
# ...
# 输出结果
OK <class 'urllib.request.OpenerDirector'>
<re.Match object; span=(1788, 1803), match='122.227.139.170'>
--------------------------------------------------
<re.Match object; span=(1788, 1803), match='122.227.139.170'>
将路径名(path)从路径的本地语法转换为URL路径组件中使用的形式。这不会产生完整的URL。返回值已使用quote()函数进行url编码。
将路径组件路径从百分比编码的URL 转换为路径的本地语法。这不接受完整的URL。此函数用于 unquote()解码路径。
def ulib_req_of():
pu = request.pathname2url('/pag1/a=3&关键字=kwd')
print(pu)
print(request.url2pathname(pu))
ulib_req_of()
# 输出结果(本地windwos测试)
/pag1/a%3D3%26%E5%85%B3%E9%94%AE%E5%AD%97%3Dkwd
\pag1\a=3&关键字=kwd
此帮助函数返回代理服务器URL映射的方案(scheme)字典。它首先扫描操作系统环境变量中的
变量(不区分大小写),当它找不到时,从Mac OSX系统配置和Windows系统注册表中查找代理信息。如果小写和大写环境变量都存在(并且不一致),则首选小写。
注意
如果设置了环境变量REQUEST_METHOD
(通常表示您的脚本在CGI环境中运行),则将忽略环境变量HTTP_PROXY
(大写_PROXY)。这是因为客户端可以使用“Proxy":HTTP请求头
执行注入(非法操作)。如果需要在CGI环境中使用HTTP代理,请显式使用ProxyHandler
,或确保变量名称为小写(或至少为_proxy后缀)。
import os,re
os.environ['my_proxy'] = '122.227.139.170:3128'
print(request.getproxies())
# 输出结果
{'my': '122.227.139.170:3128'}
此类是对URL请求的抽象。
url应该是包含有效URL的字符串。
data
必须是指定要发送到服务器的其他数据的对象,或者如果不需要此类数据,指定为None
。目前,HTTP请求是唯一使用data的请求。支持的对象类型包括字节,类文件对象和可迭代对象。如果没有提供 Content-Length
或Transfer-Encoding
请求头,HTTPHandler将根据data的类型来设置这些请求头。 Content-Length
用于发送字节对象, Transfer-Encoding: chunked
用于发送文件和其他迭代对象(如RFC 7230 和下面所描述的那样)。
对于HTTP POST
请求方法,data应该是具有标准application / x-www-form-urlencoded
格式的缓冲区。urllib.parse.urlencode()函数接收映射或2元组的序列,并以ASCII格式返回字符串。在用作data参数之前,应将其编码为字节。
headers
应该是一个字典,并且将被视为在每个键值对上使用add_header()函数。这通常用于“欺骗” User-Agent
头,浏览器使用该标头值来标识自身 - 某些HTTP服务器仅允许来自常见浏览器而非脚本的请求。例如,Mozilla Firefox可能将自己标识为"Mozilla/5.0 (X11; U; Linux i686) Gecko/20071127 Firefox/2.0.0.11"
,而urllib 默认的用户代理字符串是 "Python-urllib/2.6"
(在Python 2.6上)。
如果存在data 参数,则应包含适当的Content-Type
头。如果未提供此头且数据不是None
,则将添加默认值Content-Type: application/x-www-form-urlencoded
。
最后两个参数仅对正确处理第三方HTTP cookie感兴趣:
origin_req_host
应该是事务的原始请求主机,如下RFC 2965所定义。它默认为 http.cookiejar.request_host(self)
。这是用户启动的原始请求的主机名或IP地址。例如,如果请求是针对HTML文档中的图像,则该请求应该是包含图像的页面请求的请求主机。
unverifiable
应表明该请求是否无法核实,如RFC 2965。它默认为False
。无法核实的请求是用户无法批准的URL。例如,如果请求是针对HTML文档中的图像,并且用户没有选择批准自动获取图像,则应该设置为True。
method应该是一个字符串,表示将使用的HTTP请求方法(例如'HEAD'
)。如果提供,则其值存储在 method属性中并由get_method()使用。默认值是'GET'
(如果data是None)或’POST'
(如果data不是None)。子类可以通过在类本身中设置method属性来指示不同的默认方法 。
def ulib_req_reqcls():
url = 'http://127.0.0.1:8004/findg/'
mrequest = request.Request(url,method='GET',headers={"SELF_DEFINE":"GOOD123","User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36"})
#print(mrequest.get_header("User-Agent"))
resp = request.urlopen(mrequest)
print(resp)
ulib_req_reqcls()
# 输出结果
<http.client.HTTPResponse object at 0x00000124A8A072B0>
注意
如果数据对象无法多次传递其内容(例如,只能生成一次内容的文件或可迭代文件),则请求将无法按预期工作,并且会重试HTTP重定向或身份验证请求。data在请求头被发送到HTTP服务器后马上发送。库不支持多次继续期望(原文There is no support for a 100-continue expectation in the library
)。
在版本3.3中更改:Request.method参数被添加到Request类。
在版本3.4中更改:默认的Request.method可以在类中指示。
在版本3.6中更改:如果未提供Content-Length
且data既不是None
也不是字节对象也不引发错误。回过头来使用分块传输编码( chunked transfer encoding)。
以下方法描述了Request公共接口,因此可以在子类中重写所有方法。它还定义了几个公共属性,客户端可以使用这些属性来检查已解析的请求。
传递给构造函数的原始URL。
版本3.4中已更改。
Request.full_url具有setter,getter和deleter的属性。获取 full_url返回包含原始请求URL片段(如果存在)。
>>> r = request.Request('http://www.baidu.com')
>>> r.full_url
'http://www.baidu.com'
>>> r.full_url = 'http://www.ip111.cn'
URI类型。
>>> r.type
'http'
>>> r.full_url = 'ftp://www.ip111.cn'
>>> r.type
'ftp'
URI 权限,通常是主机,但也可能包含由冒号分隔的端口。
>>> r.full_url = 'http://www.ip111.cn'
>>> r.host
'www.ip111.cn'
>>> r.full_url = 'http://123.456.789.111:3243'
>>> r.host
'123.456.789.111:3243'
请求的原始主机,没有端口。
>>> r = request.Request("http://123.456.789.111:3243")
>>> r.origin_req_host
'123.456.789.111'
URI路径。如果Request使用代理,则selector将是传递给代理的完整URL。
>>> r = request.Request("http://123.456.789.111:3243/dir/stu/?a=3&b=4")
>>> r.selector
'/dir/stu/?a=3&b=4'
请求的实体主体,如果未指定则为None
。
版本3.4中更改:如果之前已设置或计算过data,则更改现在的data值将删除“Content-Length”
头。
>>> r.data is None
True
boolean,表示请求是否无法验证(如 RFC 2965 中描述)。
要使用的HTTP请求方法。默认情况下,它的值为None
,这意味着get_method()将对要使用的方法进行正常选取。可以通过修改在Request子类中的get_method()方法设置默认值,或者通过将method 参数值传递给Request构造函数来设置其值(从而覆盖默认选择)。
版本3.3中的新功能。
版本3.4中已更改:现在可以在子类中设置默认值; 以前它只能通过构造函数参数设置。
>>> r.method
Traceback (most recent call last):
File "" , line 1, in <module>
AttributeError: 'Request' object has no attribute 'method'
>>> r = request.Request("http://123.456.789.111:3243/dir/stu/?a=3&b=4",method='GET')
>>> r.method
'GET'
返回表示HTTP请求方法的字符串。如果 Request.method不是None
,返回其值,否则如果data是None
,返回 'GET'
或者如果data不是None
,返回'POST'
。这仅对HTTP请求有意义。
在版本3.3中更改: get_method现在查找method的值。
>>> r.get_method()
'GET'
在请求中添加另一个头。除了HTTP handler之外,所有handler都会忽略请求头,并将它们添加到请求头列表中并发送到服务器。请注意,不能有多个具有相同名称的请求头,后面的会覆盖前面添加的,以防key发生冲突。目前,这不会损坏HTTP功能。
>>> r = request.Request("http://123.456.789.111:3243/dir/",method='GET')
>>> r.add_header("User-Agent","python3.7")
>>> r.header_items()
[('User-agent', 'python3.7')]
添加一个不会重定向请求的头
返回实例是否具有指定请求头(检查常规和未重定向的头)
>>> r.header_items()
[('User-agent', 'python3.7')]
>>> r.has_header('User-agent')
True
>>> r.has_header('User-agentss')
False
从请求实例中删除某个请求头(包括常规和未重定向的头)
版本3.4中的新功能。
返回构造函数中给出的URL
版本3.4中已更改
返回 full_url
>>> r = request.Request("http://123.456.789.111:3243/dir/",method='GET')
>>> r.get_full_url()
'http://123.456.789.111:3243/dir/'
>>>
通过连接到代理服务器来准备请求。host和type将取代这些实例,并且实例的选择将是在构造函数中给出的原始URL。
def ulib_req_req_proxy():
old_url = 'http://www.ip111.cn/'
r = request.Request(old_url,method='GET')
r.set_proxy('122.227.139.170:3128','http')
resp =request.urlopen(r)
import re
content = resp.read().decode(encoding="utf-8", errors="replace")
print(re.search(r'(\d{1,3}\.){3}\d{1,3}', content))
ulib_req_req_proxy()
#输出结果
<re.Match object; span=(1788, 1803), match='122.227.139.170'>
返回给定请求头的值。如果请求头不存在,则返回默认值。
返回Request请求头的元组列表(header_name,header_value)
。
版本3.4中已更改:已删除自3.3以来已弃用的请求方法add_data,has_data,get_data,get_type,get_host,get_selector,get_origin_req_host和is_unverifiable。
OpenerDirector类打开URL ,处理BaseHandler链。它管理handler的链,并从错误中恢复。
OpenerDirector 实例有以下方法:
handler应该是一个BaseHandler实例。搜索以下方法,并将其添加到可能的链中(请注意,HTTP错误是一种特殊情况)。
打开给定的url(可以是请求对象或字符串),可选地传递给定的data。返回值和异常与urlopen()(urlopen就是在全局OpenerDirector上调用open()方法)相同。可选的timeout参数指定阻塞操作(如连接尝试)的超时(以秒为单位)(如果未指定,将使用全局默认超时设置)。超时功能实际上仅适用于HTTP,HTTPS和FTP连接)。
处理给定协议的错误。这将使用给定的参数(特定于协议)调用给定协议的已注册错误处理程序。HTTP协议是一种特殊情况,它使用HTTP响应代码来确定特定的错误处理程序; 请参阅handler类中的http_error_*()
方法。
返回值和爆出的异常同urlopen()。
OpenerDirector对象分三个阶段打开一个URL:
在每个阶段中调用这些方法的顺序是通过handler实例的先后顺序来确定的。
1 . 每个具有名字像 protocol_request()
的handler都调用该方法来预处理请求。
2 .每个具有名字像 protocol_open()
的handler都调用该方法来处理请求。当handler返回非None 值(如响应)或引发异常(通常是 URLError
)时,此阶段结束。允许异常传递。
实际上,上述算法首先尝试default_open()。如果所有这些方法都返回None
,则对名称像protocol_open()的方法重复该算法。如果返回所有这些方法None
,则对名称像unknown_open()的方法重复该算法。
请注意,这些方法的实现可能涉及父类 OpenerDirector实例open()和 error()方法的调用。
3 . 每个具有名字像 protocol_response()
的handler都调用该方法来对响应进行后处理。
译者注:
上面这三个步骤,如果不明白可以看下我从源码中提权的部分,
protocol 是个各种类型的协议
class OpenerDirector:
#...省略
def open(self, fullurl, data=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
# ...
# pre-process request 阶段1
meth_name = protocol+"_request"
for processor in self.process_request.get(protocol, []):
meth = getattr(processor, meth_name)
req = meth(req)
# 阶段2
response = self._open(req, data)
# post-process response 阶段3
meth_name = protocol+"_response"
for processor in self.process_response.get(protocol, []):
meth = getattr(processor, meth_name)
response = meth(req, response)
return response
BaseHandler对象提供了一些直接有用的方法,以及其他旨在由派生类使用的方法。这些是直接用途:
添加director(某个OpenerDirector实例)作为parent。
>>> h = request.BaseHandler()
>>> opener = request.build_opener(h)
>>> h.add_parent(opener)
>>> h.parent
<urllib.request.OpenerDirector object at 0x000002A533713DA0>
删除任何parent。
以下属性和方法只能由派生自BaseHandler的类使用 。
注意
已采用该约定,子类定义的 protocol_request()或protocol_response()方法 为*Processor
; 其它的为*Handler
。
一个可用的OpenerDirector,可用于使用不同的协议打开url,或处理错误。
这种方法在BaseHandler中没有定义,但子类应该定义它,如果他们想捕获所有的URL。
如果实现了此方法,则parent OpenerDirector将调用此方法。它应该为OpenerDirector的open()函数返回一个类文件对象 或None
。错误的类型不一定都是URLError(例如,MemoryError不等同于 URLError),所以要清楚引发的是什么错误。
在任何特定于协议的打开方法之前将调用此方法。
译者实例:
我自己定义了一个BaseHandler子类,实现了default_open方法,为每次请求增加一个HTTP请求头name
,
def ulib_req_bh_cls():
class MyHandler(request.BaseHandler):
def __init__(self,headers):
self.headers = headers
def default_open(self,req):
print(req.header_items())
for k,v in self.headers.items():
req.add_header(k,v)
return None
h = MyHandler({"name":"leng"})
o = request.build_opener(h)
resp = o.open('http://127.0.0.1:8000/findg/')
ulib_req_bh_cls()
# 输出结果
[('Host', '127.0.0.1:8000'), ('User-agent', 'Python-urllib/3.7')]
在服务端打印请求头可以看到,name
变成了HTTP_NAME
...
'HTTP_HOST': '127.0.0.1:8000',
'HTTP_USER_AGENT': 'Python-urllib/3.7',
'HTTP_NAME': 'leng',
'HTTP_CONNECTION': 'close'
...
这种方法在BaseHandler中没有定义,但子类应该定义它,如果他们想处理给定协议的URL。
如果已定义此方法,将由 parent OpenerDirector调用。返回值应与default_open()中的相同。
这种方法在BaseHandler中没有定义,但子类应该定义它,如果他们想捕获URL(这些URL没有一个注册的handler能将其打开)。
如果已定义此方法,将由 parent OpenerDirector调用。返回值应与default_open()中的相同。
这种方法在BaseHandler中没有定义,但子类应该定义它,如果他们打算处理其他handler无法处理的HTTP错误。它将通过OpenerDirector自动调用并获取错误 ,通常不应在其他情况下调用。
req将是一个Request对象,fp将是一个带有HTTP错误体的文件类对象,code是错误的三位数代码,msg 将是用户可见的code解释,hdrs是一个映射对象带有错误的标题。
返回值和异常同 urlopen()。
nnn应该是一个三位数的HTTP错误代码。BaseHandler类中此方法也未定义,当发生代码为nnn的HTTP错误时,如果子类实现了此方法,将会被调用。
子类应重写此方法以处理特定的HTTP错误。
返回值和异常同 http_error_default()。
这种方法在BaseHandler中没有定义,但子类应该定义它,如果他们想预先处理给定协议的请求。
req是一个Request对象。返回值应该是一个 Request对象。
这种方法在BaseHandler中没有定义,但子类应该定义它,如果他们想进行后处理指定协议的响应。
req是一个Request对象。response是一个实现与urlopen()返回值相同的接口的对象。返回值应该实现为与urlopen()返回值相同的接口 。