当我们研究如何借助Python来实现网络连接与操作时,往往可以发现许多参考书籍以及文献均会推荐采用第三方套件requests以完成此项工作。诚然,requests的众多优点使得其成为了当今Python环境中进行HTTP请求的事实上的行业标准。然而,值得注意的是,Requests实际上是由Python自行开发并基于urllib模块实现的。因此,在实际运用这一三方库前,深入理解Python原生的请求发送库urllib及其运作流程和应用技巧无疑是十分必要的。
Python
urllib作为Python的一项标准库,其主要功能在于管理和处理URL地址,同时也负责进行网络通讯。这个库内含多个子模块,各自承担着不同的任务,例如用于打开和读取URL地址、处理URL地址的异常状况、对URL地址进行解析等等。作为一款内置的HTTP请求库,并不需要事先进行任何额外的安装便可立即投入使用。
网络执行流程
urllib中包含有四大模块用以满足不同的需求:
1. request模块:该模块主要用于发起各类请求,从而构成了一个最基础的HTTP请求核心部分;
2. error模块:顾名思义,这个模块专门用于处理在请求过程中共有的异常情况,并为之后可能发生的行为作出预设,如允许重试;
3. parse模块:作为一种工具型模块,提供了许多实用有效的URL处理方法,包括分割、解析等多种操作;
4. robotparser模块:针对网站的robots.txt文件(即网络君子协议)进行分析和解构。
发送请求(基础请求)
urllib库中request模块提供了构建HTTP请求请求的基本方法,并且还具备处理授权验证、重定向、cookies等浏览器功能的功能。以下是该模块的基础使用方式:
from urllib import request
# 使用urlopen访问地址
# 响应结果为HTTPResponse
res = request.urlopen("https://www.baidu.com")
print(res)
# 可以获取到百度页面的内容
#
通过request.urlopen 方法即可获取指定网址的内容,返回的内容被包装到了HTTPResponse 类中。
在使用 urlopen 时还可以指定其他参数:
request.urlopen(url, # 访问地址
data=None, # 参数
[timeout]*, # 超时时间
cafile=None, # CA证书
capath=None, # CA证书路径
cadefault=False, # 已弃用
context=None # 用来设置ssl
)
- data: 可选参数,用来添加访问参数,需要注意的是,只要 data 有值,则请求自动改为 post。同时 data 参数需要做字节流转换。如下使用
from urllib import request, parse
data = {
'country': 'China'
}
# 将data转为字节流
data = parse.urlencode(data).encode('utf-8')
res = request.urlopen('http://httpbin.org/post', data=data)
print(res)
#
- timeout: 可选参数,用于设置请求超时时间,单位为秒,如果请求在指定的时间内没有返回响应信息,则抛出异常。
- context: 可选参数,必须是 ssl.SSLContext 类型,指定 SSL 设置。
- cafile 和 capath 参数分别用来指定 CA 证书和路径,一般用于 HTTPS 链接。
cadefault 已被弃用,默认为 False。
Request 请求对象(复杂请求对象)
对于简单的请求,我们可以直接使用urlopen发起请求。然而,在实际的网络中,一次完整的请求往往需要许多参数,如添加请求头(Headers)、cookies等,这就需要我们借助Request对象来构建请求。
req = request.Request(
url,
data=None,
headers={},
origin_req_host=None,
unverifiable=False,
method=None)
- url: 为必传参数,用于请求的 URL 地址
- data: 为选填字段,必须传入 bytes 类型,如果为字典则需要使用parse.urlencode 进行编码
- headers:为选填参数,类型是一个字典,在构造请求时指定 headers 参数,也可以通过实例 add_header 方法添加。
- origin_req_host:用于指定请求方 host 或 IP 地址。
- unverifiable: 用于表示请求是否无法验证,默认为 False,指的是用户没有足够权限来选择接收这个请求的结果。
- method:用于指定请求类型,如 GET,POST 和 PUT 等。
使用示例:
from urllib import request, parse
# 请求地址
url = "http://httpbin.org/post"
# 构造请求头
headers = {
"User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1 Edg/120.0.0.0",
"Host": "httpbin.org"
}
# 构造请求数据
data = {
"country": "china"
}
# 构造Request对象,并编码data数据
req = request.Request(url=url,
# data数据要转为字节(byte)类型
data=parse.urlencode(data).encode("utf-8"),
headers=headers,
method="POST")
# urlopen接收Request对象
res = request.urlopen(req)
print(res.read().decode('utf-8'))
HTTPResponse 响应对象
请求成功后,Python会将响应结果封装到HTTPResponse对象中。了解此对象可使我们更熟练地从响应结果中获取数据。该对象提供了许多方法和属性,用于获取响应结果的状态和内容(以下代码示例)。
from urllib import request
# 使用urlopen访问地址
# 响应结果为HTTPResponse
res = request.urlopen("https://www.baidu.com")
# 获取http协议版本号,10->1.0, 11-> 1.1
print(res.version)
# 获取响应码
print(res.status)
print(res.getcode())
# 响应描述字符
print(res.reason)
# 获取实际请求页面(用于校验是否重定向)
print(res.geturl())
# 获取所有头信息,返回为元组列表
print(res.getheaders())
# 获取指定头信息
print(res.getheader("host"))
# 获取响应头信息
print(res.info())
# 获取响应结果内容,
print(res.read().decode("utf-8"))
print(res.readline())
print(res.readlines())
需要注意的是: 响应体如果是可以被缓存的内容如“application/gzip”,则可以多次调用来获取,但是如果内容为“text/html”类型,因为响应内容为流式数据,则只能调用一次来获取内容,后续虽然可以再次调用,但无法读取到任何内容,如果确实有需求做多次内容读取,可以使用 BytesIO 等工具在内存中将响应结果数据做缓存,后续即可多次读取。
异常处理
网络请求并非总是可以成功响应,在某些条件下可能会导致请求失败,因此处理异常也是非常有必要的。在 urllib 中使用 error 模块来定义 request 所产的的异常。主要有以下 2 个:
- URLError(基础异常类)
URLError 类是 error 异常模块的基础错误,所有 request 产生的异常都可以通过捕获此类来处理,并通过 reason 属性来获取返回的错误内容。
try:
# 访问一个不存在的网址
request.urlopen("http://www.coasdfla.com")
except error.URLError as e:
print(e.reason)
# [Errno 11001] getaddrinfo failed
- HTTPError
HTTPError 是 URLError 的子类, 用来处理 HTTP 请求的错误,比如 404 网页不存在,500 服务器异常等。HTTPError 有 3 个属性,code:获取 http 状态码,reason:返回错误信息,headers:返回请求头。
try:
# 访问一个不存在的资源
request.urlopen("http://www.baidu.com/i.html")
except error.HTTPError as e:
print(e.reason)
# Not Found
# 404
# Content-Length: 204
# Content-Type: text/html; charset=iso-8859-1
# Date: Tue, 02 Jan 2024 07:47:04 GMT
# Server: Apache
# Set-Cookie: BAIDUID=762B2AF189BFC59272CB9C1CE5DBAF0F:FG=1; Path=/; Domain=baidu.com; Max-Age=31536000
# Set-Cookie: BAIDUID_BFESS=762B2AF189BFC59272CB9C1CE5DBAF0F:FG=1; Path=/; Domain=baidu.com; Max-Age=31536000; Secure; SameSite=None
# Connection: close
在实际的开发中,应遵循一个原则:请求异常的捕获应先小后大的原则,依次处理。