1. urllib 是什么? #
urllib 是 Python 的标准库,用于处理 URL(统一资源定位符)相关的操作,包括发送 HTTP 请求、解析 URL、处理响应等。它是 Python 内置的,无需额外安装。
1.1 为什么需要 urllib? #
在 Web 开发中,经常需要:
- 从网站获取数据(如 API 调用)
- 下载文件
- 解析和处理 URL
- 发送 HTTP 请求
urllib 的优势:
- 无需安装:Python 内置,开箱即用
- 学习价值高:帮助理解 HTTP 协议原理
- 功能完整:满足基本的网络请求需求
- 可控性强:可以精细控制请求过程
简单理解:
- urllib:Python 自带的网络请求工具
- requests:第三方库,更简单易用(需要安装)
1.2 urllib 模块结构 #
urllib 包含多个子模块,每个模块负责不同的功能:
# 导入 urllib 的各个子模块
import urllib.request # 发送 HTTP 请求(最常用)
import urllib.parse # 解析和构建 URL
import urllib.error # 异常处理
import urllib.response # 响应处理(通常不需要直接导入)主要模块说明:
urllib.request:发送 HTTP 请求,获取响应urllib.parse:解析 URL,处理查询参数urllib.error:处理网络请求中的异常urllib.response:响应对象(通常通过urlopen返回)
2. 前置知识 #
在学习 urllib 之前,你需要了解以下基础知识。
2.1 HTTP 基础知识 #
HTTP(HyperText Transfer Protocol) 是 Web 上最常用的协议,用于在客户端和服务器之间传输数据。
HTTP 请求方法:
- GET:获取数据(如查看网页、获取 API 数据)
- POST:提交数据(如登录、创建资源)
- PUT:更新数据
- DELETE:删除数据
HTTP 状态码:
200:成功404:资源不存在500:服务器错误
2.2 URL 结构 #
URL(Uniform Resource Locator) 是网页地址,由多个部分组成:
https://www.example.com:8080/path/to/page?name=value#fragment
│ │ │ │ │ │
│ │ │ │ │ └─ 锚点(页面内跳转)
│ │ │ │ └─ 查询参数
│ │ │ └─ 路径
│ │ └─ 端口号
│ └─ 域名
└─ 协议(http/https)简单理解:
- 协议:
http://或https://(安全) - 域名:网站地址,如
www.example.com - 路径:具体页面路径,如
/users/profile - 查询参数:
?key=value&key2=value2,用于传递数据
2.3 Python 基础 #
你需要了解:
- 基本的 Python 语法
- 异常处理(
try/except) - 文件操作(读取、写入)
- 字符串编码(
utf-8)
3. urllib.request:发送 HTTP 请求 #
urllib.request 是最常用的模块,用于发送 HTTP 请求并获取响应。
3.1 基本 GET 请求 #
GET 请求用于获取数据,是最简单的请求方式。
# 导入 urllib.request 模块
import urllib.request
# 发送 GET 请求
# urlopen() 函数用于打开 URL 并返回响应对象
response = urllib.request.urlopen('https://www.example.com')
# 读取响应内容(返回字节数据)
content = response.read()
# 将字节数据解码为字符串(使用 utf-8 编码)
html = content.decode('utf-8')
# 打印响应内容
print(html)
# 获取响应状态码(200 表示成功)
print(f"状态码:{response.status}")
# 获取所有响应头(字典格式)
headers = response.getheaders()
print(f"响应头:{headers}")
# 获取特定响应头(如 Content-Type)
content_type = response.getheader('Content-Type')
print(f"内容类型:{content_type}")
# 关闭响应(使用 with 语句会自动关闭)
response.close()说明:
urlopen()发送 GET 请求并返回响应对象response.read()读取响应内容(字节数据)decode('utf-8')将字节数据转换为字符串response.status获取 HTTP 状态码response.getheader()获取特定响应头
3.2 使用 with 语句(推荐) #
使用 with 语句可以自动关闭响应,更安全。
# 导入 urllib.request 模块
import urllib.request
# 使用 with 语句自动管理资源
# 请求完成后会自动关闭连接
with urllib.request.urlopen('https://www.example.com') as response:
# 读取响应内容
content = response.read()
# 解码为字符串
html = content.decode('utf-8')
# 打印前 200 个字符
print(html[:200])
# 获取状态码
print(f"状态码:{response.status}")3.3 带查询参数的 GET 请求 #
在实际应用中,经常需要在 URL 中添加查询参数(如搜索关键词、分页参数等)。
# 导入 urllib.request 和 urllib.parse 模块
import urllib.request
import urllib.parse
# 定义查询参数(字典格式)
params = {
'q': 'Python教程', # 搜索关键词
'page': 1, # 页码
'limit': 10 # 每页数量
}
# 使用 urlencode() 将字典编码为 URL 查询字符串
# 会自动处理中文字符编码
query_string = urllib.parse.urlencode(params)
# 构建完整的 URL
# httpbin.org 是一个用于测试 HTTP 请求的网站
url = f'https://httpbin.org/get?{query_string}'
# 发送 GET 请求
with urllib.request.urlopen(url) as response:
# 读取响应内容
data = response.read()
# 解码为字符串
result = data.decode('utf-8')
# 打印响应(JSON 格式)
print(result)说明:
urlencode()将字典转换为 URL 查询字符串- 会自动处理中文字符的编码(如
Python教程会被编码为Python%E6%95%99%E7%A8%8B) httpbin.org是一个测试网站,会返回请求的详细信息
3.4 POST 请求:发送表单数据 #
POST 请求用于提交数据,如登录、注册等。
# 导入必要的模块
import urllib.request
import urllib.parse
# 定义表单数据(字典格式)
form_data = {
'username': 'admin',
'password': '123456'
}
# 将表单数据编码为 URL 格式
# urlencode() 返回字符串,需要编码为字节
encoded_data = urllib.parse.urlencode(form_data).encode('utf-8')
# 创建请求对象
# Request() 用于创建更复杂的请求(可以设置方法、数据、请求头等)
req = urllib.request.Request(
'https://httpbin.org/post', # 请求的 URL
data=encoded_data, # POST 数据(必须是字节)
method='POST' # 请求方法
)
# 发送请求
with urllib.request.urlopen(req) as response:
# 读取响应
result = response.read().decode('utf-8')
print(result)说明:
- POST 请求需要提供
data参数(必须是字节数据) urlencode().encode('utf-8')将字典转换为字节数据Request()用于创建更复杂的请求对象method='POST'指定请求方法
3.5 POST 请求:发送 JSON 数据 #
很多 API 使用 JSON 格式传输数据。
# 导入必要的模块
import urllib.request
import urllib.parse
import json
# 定义 JSON 数据(字典格式)
json_data = {
'name': '张三',
'age': 25
}
# 将字典转换为 JSON 字符串,然后编码为字节
# json.dumps() 将 Python 对象转换为 JSON 字符串
# encode('utf-8') 将字符串编码为字节
json_bytes = json.dumps(json_data).encode('utf-8')
# 创建请求对象
req = urllib.request.Request(
'https://httpbin.org/post', # 请求的 URL
data=json_bytes, # JSON 数据(字节格式)
headers={
'Content-Type': 'application/json' # 设置请求头,告诉服务器这是 JSON 数据
},
method='POST' # 请求方法
)
# 发送请求
with urllib.request.urlopen(req) as response:
# 读取响应
result = response.read().decode('utf-8')
print(result)说明:
json.dumps()将 Python 字典转换为 JSON 字符串Content-Type: application/json告诉服务器这是 JSON 数据- JSON 数据也需要编码为字节
3.6 设置请求头 #
请求头(Headers)用于传递额外的信息,如用户代理、认证信息等。
# 导入 urllib.request 模块
import urllib.request
# 定义自定义请求头(字典格式)
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
# User-Agent 告诉服务器你使用的浏览器/客户端
'Accept': 'application/json',
# Accept 告诉服务器你希望接收的数据类型
'Authorization': 'Bearer token123'
# Authorization 用于身份认证(Bearer token 是常见的认证方式)
}
# 创建请求对象,设置请求头
req = urllib.request.Request(
'https://httpbin.org/headers', # 请求的 URL
headers=headers # 设置请求头
)
# 发送请求
with urllib.request.urlopen(req) as response:
# 读取响应
result = response.read().decode('utf-8')
print(result)说明:
User-Agent标识客户端类型(有些网站会检查这个)Accept指定希望接收的内容类型Authorization用于身份认证(Bearer token 是常见方式)
3.7 处理超时 #
网络请求可能会因为网络问题而超时,需要设置超时时间。
# 导入 urllib.request 和 urllib.error 模块
import urllib.request
import urllib.error
# 设置超时时间(秒)
# timeout=5 表示如果 5 秒内没有响应,就抛出异常
try:
with urllib.request.urlopen('https://www.example.com', timeout=5) as response:
content = response.read().decode('utf-8')
print("请求成功")
print(content[:100]) # 打印前 100 个字符
except urllib.error.URLError as e:
# 捕获 URL 错误(包括超时)
print(f"请求失败:{e.reason}")说明:
timeout=5设置超时时间为 5 秒- 如果超时,会抛出
URLError异常 - 使用
try/except处理异常
4. urllib.parse:解析和构建 URL #
urllib.parse 用于解析 URL、处理查询参数、编码/解码等。
4.1 解析 URL #
urlparse() 可以将 URL 分解为各个组成部分。
# 导入 urllib.parse 模块
from urllib.parse import urlparse
# 解析 URL
# urlparse() 将 URL 分解为多个组成部分
result = urlparse('https://www.example.com:8080/path/to/page?name=value#fragment')
# 打印各个组成部分
print(f"协议(scheme):{result.scheme}") # https
print(f"网络位置(netloc):{result.netloc}") # www.example.com:8080
print(f"路径(path):{result.path}") # /path/to/page
print(f"查询参数(query):{result.query}") # name=value
print(f"锚点(fragment):{result.fragment}") # fragment
print(f"用户名(username):{result.username}") # None(如果没有)
print(f"密码(password):{result.password}") # None(如果没有)
print(f"主机名(hostname):{result.hostname}") # www.example.com
print(f"端口号(port):{result.port}") # 8080说明:
urlparse()返回一个ParseResult对象- 可以访问 URL 的各个组成部分
- 常用于提取 URL 中的信息
4.2 构建 URL #
urlunparse() 可以将各个部分组合成完整的 URL。
# 导入 urllib.parse 模块
from urllib.parse import urlunparse
# 构建 URL
# urlunparse() 接受一个包含 6 个元素的元组
# 格式:(scheme, netloc, path, params, query, fragment)
url = urlunparse((
'https', # 协议
'www.example.com', # 网络位置(域名)
'/path', # 路径
'', # 参数(通常为空)
'name=value', # 查询参数
'fragment' # 锚点
))
# 打印构建的 URL
print(url) # https://www.example.com/path?name=value#fragment4.3 处理查询参数 #
urlencode() 用于将字典编码为查询字符串,parse_qs() 用于解析查询字符串。
# 导入 urllib.parse 模块
from urllib.parse import urlencode, parse_qs, parse_qsl
# 编码查询参数(字典 → 查询字符串)
params = {
'q': 'Python教程', # 搜索关键词
'page': 1, # 页码
'sort': 'recent' # 排序方式
}
# urlencode() 将字典转换为 URL 查询字符串
# 会自动处理中文字符编码
query_string = urlencode(params)
print(f"编码后的查询字符串:{query_string}")
# 输出:q=Python%E6%95%99%E7%A8%8B&page=1&sort=recent
# 解析查询字符串(查询字符串 → 字典)
# parse_qs() 返回字典,值是列表(因为一个参数可能有多个值)
query_str = 'name=张三&age=25&hobbies=编程&hobbies=读书'
parsed_dict = parse_qs(query_str)
print(f"解析为字典:{parsed_dict}")
# 输出:{'name': ['张三'], 'age': ['25'], 'hobbies': ['编程', '读书']}
# parse_qsl() 返回列表,每个元素是 (键, 值) 的元组
parsed_list = parse_qsl(query_str)
print(f"解析为列表:{parsed_list}")
# 输出:[('name', '张三'), ('age', '25'), ('hobbies', '编程'), ('hobbies', '读书')]说明:
urlencode()将字典编码为查询字符串parse_qs()返回字典(值是列表,因为一个参数可能有多个值)parse_qsl()返回列表(每个元素是键值对元组)
4.4 URL 编码和解码 #
URL 中不能直接使用某些字符(如中文、空格),需要编码。
# 导入 urllib.parse 模块
from urllib.parse import quote, unquote, quote_plus, unquote_plus
# 编码 URL(将特殊字符转换为 %XX 格式)
# quote() 用于编码 URL 中的特殊字符
text = 'Python 教程'
encoded = quote(text)
print(f"编码后:{encoded}")
# 输出:Python%20%E6%95%99%E7%A8%8B
# %20 是空格的编码,%E6%95%99%E7%A8%8B 是"教程"的编码
# 构建完整的 URL
url = f'https://www.example.com/search?q={encoded}'
print(f"完整 URL:{url}")
# 解码 URL(将 %XX 格式转换回原始字符)
decoded = unquote(encoded)
print(f"解码后:{decoded}")
# 输出:Python 教程
# quote_plus() 会将空格转换为 +(而不是 %20)
url2 = f'https://www.example.com/search?q={quote_plus(text)}'
print(f"使用 quote_plus:{url2}")
# 输出:https://www.example.com/search?q=Python+%E6%95%99%E7%A8%8B
# unquote_plus() 会将 + 转换回空格
decoded2 = unquote_plus('Python+%E6%95%99%E7%A8%8B')
print(f"解码 plus 编码:{decoded2}")
# 输出:Python 教程说明:
quote()编码 URL(空格变为%20)quote_plus()编码 URL(空格变为+)unquote()和unquote_plus()用于解码
5. urllib.error:异常处理 #
网络请求可能会失败,需要处理各种异常。
# 导入必要的模块
import urllib.request
import urllib.error
# 尝试发送请求
try:
# 发送请求(这个 URL 会返回 404 错误)
with urllib.request.urlopen('https://httpbin.org/status/404') as response:
content = response.read()
print("请求成功")
except urllib.error.HTTPError as e:
# 捕获 HTTP 错误(如 404、500 等)
print(f"HTTP 错误:{e.code} - {e.reason}")
# e.code 是状态码(如 404)
# e.reason 是错误原因(如 Not Found)
print(f"响应头:{e.headers}")
# e.headers 是响应头(字典格式)
except urllib.error.URLError as e:
# 捕获 URL 错误(如网络错误、超时等)
print(f"URL 错误:{e.reason}")
# e.reason 是错误原因
else:
# 如果没有异常,执行这里
print("请求成功")说明:
HTTPError:HTTP 错误(如 404、500)URLError:URL 错误(如网络错误、超时)HTTPError是URLError的子类,所以先捕获HTTPError
6. 实际应用示例 #
6.1 下载文件 #
使用 urlretrieve() 可以方便地下载文件。
# 导入必要的模块
import urllib.request
import os
# 定义下载文件的函数
def download_file(url, save_path):
"""下载文件到指定路径"""
try:
# 创建保存目录(如果不存在)
# os.path.dirname() 获取目录路径
# os.makedirs() 创建目录(exist_ok=True 表示如果已存在不报错)
os.makedirs(os.path.dirname(save_path), exist_ok=True)
# 下载文件
# urlretrieve() 直接下载文件到本地
urllib.request.urlretrieve(url, save_path)
# 打印成功信息
print(f'文件下载成功:{save_path}')
except Exception as e:
# 捕获所有异常
print(f'下载失败:{e}')
# 主程序入口
if __name__ == "__main__":
# 下载文件示例
# 注意:这个 URL 可能不存在,仅作为示例
download_file(
'https://www.example.com/image.jpg',
'./downloads/image.jpg'
)说明:
urlretrieve()直接下载文件到本地os.makedirs()创建目录- 使用
try/except处理异常
6.2 简单的 API 客户端 #
创建一个简单的 API 客户端类,封装常用的请求操作。
# 导入必要的模块
import urllib.request
import urllib.parse
import urllib.error
import json
# 定义 API 客户端类
class APIClient:
"""简单的 API 客户端"""
def __init__(self, base_url: str, api_key: str = None):
# 初始化方法
# base_url: API 的基础 URL(如 https://api.example.com)
# api_key: API 密钥(可选)
# 移除 base_url 末尾的斜杠(如果有)
self.base_url = base_url.rstrip('/')
# 保存 API 密钥
self.api_key = api_key
# 设置默认请求头
self.headers = {
'User-Agent': 'APIClient/1.0', # 用户代理
'Accept': 'application/json' # 接受 JSON 格式
}
# 如果有 API 密钥,添加到请求头
if api_key:
self.headers['Authorization'] = f'Bearer {api_key}'
def get(self, endpoint: str, params: dict = None):
"""发送 GET 请求"""
# endpoint: API 端点(如 /users)
# params: 查询参数(可选)
# 构建完整的 URL
# lstrip('/') 移除 endpoint 开头的斜杠(如果有)
url = f'{self.base_url}/{endpoint.lstrip("/")}'
# 如果有查询参数,添加到 URL
if params:
query_string = urllib.parse.urlencode(params)
url = f'{url}?{query_string}'
# 创建请求对象
req = urllib.request.Request(url, headers=self.headers)
# 发送请求
try:
with urllib.request.urlopen(req) as response:
# 读取响应并解析为 JSON
data = response.read().decode('utf-8')
return json.loads(data)
except urllib.error.HTTPError as e:
# 处理 HTTP 错误
print(f"HTTP 错误:{e.code} - {e.reason}")
return None
except urllib.error.URLError as e:
# 处理 URL 错误
print(f"URL 错误:{e.reason}")
return None
def post(self, endpoint: str, data: dict):
"""发送 POST 请求"""
# endpoint: API 端点
# data: 要发送的数据(字典格式)
# 构建完整的 URL
url = f'{self.base_url}/{endpoint.lstrip("/")}'
# 将数据转换为 JSON 字符串,然后编码为字节
json_data = json.dumps(data).encode('utf-8')
# 设置 Content-Type 请求头
self.headers['Content-Type'] = 'application/json'
# 创建请求对象
req = urllib.request.Request(
url,
data=json_data,
headers=self.headers,
method='POST'
)
# 发送请求
try:
with urllib.request.urlopen(req) as response:
# 读取响应并解析为 JSON
result = response.read().decode('utf-8')
return json.loads(result)
except urllib.error.HTTPError as e:
# 处理 HTTP 错误
print(f"HTTP 错误:{e.code} - {e.reason}")
return None
except urllib.error.URLError as e:
# 处理 URL 错误
print(f"URL 错误:{e.reason}")
return None
# 主程序入口
if __name__ == "__main__":
# 创建 API 客户端实例
# jsonplaceholder.typicode.com 是一个用于测试的 API 服务
client = APIClient('https://jsonplaceholder.typicode.com')
# 发送 GET 请求
# 获取用户 ID 为 1 的所有文章
posts = client.get('/posts', {'userId': 1})
if posts:
print(f'获取到 {len(posts)} 篇文章')
if posts:
print(f'第一篇文章标题:{posts[0]["title"]}')
# 发送 POST 请求
# 创建新文章
new_post = client.post('/posts', {
'title': '测试标题',
'body': '测试内容',
'userId': 1
})
if new_post:
print(f'创建新文章,ID: {new_post["id"]}')说明:
APIClient类封装了常用的 API 请求操作get()方法发送 GET 请求post()方法发送 POST 请求- 使用
try/except处理异常
6.3 带基本认证的请求 #
有些 API 需要基本认证(Basic Authentication)。
# 导入必要的模块
import urllib.request
import urllib.error
import base64
# 定义带基本认证的请求函数
def basic_auth_request(url, username, password):
"""发送带基本认证的请求"""
# url: 请求的 URL
# username: 用户名
# password: 密码
# 创建认证字符串(格式:username:password)
auth_str = f'{username}:{password}'
# 将字符串编码为字节
auth_bytes = auth_str.encode('utf-8')
# 使用 base64 编码
# base64.b64encode() 将字节编码为 base64 字符串
auth_base64 = base64.b64encode(auth_bytes).decode('utf-8')
# 设置请求头
# Basic 认证格式:Authorization: Basic base64(username:password)
headers = {
'Authorization': f'Basic {auth_base64}',
'User-Agent': 'MyApp/1.0'
}
# 创建请求对象
req = urllib.request.Request(url, headers=headers)
# 发送请求
try:
with urllib.request.urlopen(req) as response:
# 读取响应
return response.read().decode('utf-8')
except urllib.error.HTTPError as e:
# 处理 HTTP 错误
return f'错误:{e.code} - {e.reason}'
# 主程序入口
if __name__ == "__main__":
# 发送带基本认证的请求
# httpbin.org 是一个测试网站,这个端点需要基本认证
result = basic_auth_request(
'https://httpbin.org/basic-auth/user/passwd',
'user', # 用户名
'passwd' # 密码
)
print(result)说明:
- 基本认证需要将
username:password进行 base64 编码 - 格式:
Authorization: Basic base64(username:password) base64.b64encode()用于 base64 编码
7. 常见问题 #
7.1 urllib 和 requests 的区别? #
| 特性 | urllib | requests |
|---|---|---|
| 安装 | Python 内置 | 需要单独安装 |
| 易用性 | 较复杂 | 非常简单 |
| 功能 | 基础功能 | 功能丰富 |
| 文档 | 官方文档 | 优秀文档 |
建议:
- 学习 HTTP 原理:使用 urllib
- 快速开发:使用 requests
- 依赖最小化:使用 urllib(无需安装)
- 生产环境:推荐 requests(更简单、功能更丰富)
7.2 如何处理 SSL 证书错误? #
如果遇到 SSL 证书错误,可以创建不验证证书的上下文(仅用于测试,生产环境不推荐)。
# 导入必要的模块
import urllib.request
import ssl
# 创建不验证证书的 SSL 上下文(仅用于测试)
# 生产环境应该使用有效的证书
ssl_context = ssl.create_default_context()
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE
# 使用自定义的 SSL 上下文发送请求
with urllib.request.urlopen(
'https://www.example.com',
context=ssl_context
) as response:
content = response.read().decode('utf-8')
print(content[:100])注意:不验证证书存在安全风险,仅用于测试环境。
7.3 如何设置 Cookie? #
使用 http.cookiejar 可以处理 Cookie。
# 导入必要的模块
import urllib.request
import http.cookiejar
# 创建 CookieJar(用于存储 Cookie)
cookie_jar = http.cookiejar.CookieJar()
# 创建 Cookie 处理器
cookie_handler = urllib.request.HTTPCookieProcessor(cookie_jar)
# 创建 opener(用于发送请求)
opener = urllib.request.build_opener(cookie_handler)
# 使用 opener 发送请求(会自动处理 Cookie)
response = opener.open('https://httpbin.org/cookies/set?name=value')
# 读取响应
print(response.read().decode('utf-8'))
# 查看 Cookie
for cookie in cookie_jar:
print(f'{cookie.name} = {cookie.value}')8. 总结 #
8.1 核心概念回顾 #
urllib.request:发送 HTTP 请求
urlopen():发送简单请求Request():创建复杂请求urlretrieve():下载文件
urllib.parse:解析和构建 URL
urlparse():解析 URLurlencode():编码查询参数quote():编码 URL 中的特殊字符
urllib.error:异常处理
HTTPError:HTTP 错误URLError:URL 错误
8.2 使用 urllib 的好处 #
- 无需安装:Python 内置,开箱即用
- 学习价值高:帮助理解 HTTP 协议原理
- 功能完整:满足基本的网络请求需求
- 可控性强:可以精细控制请求过程