Skip to main content

urllib

1. urllib 是什么?

urllib 是 Python 的标准库,用于处理 URL(统一资源定位符)相关的操作,包括发送 HTTP 请求、解析 URL、处理响应等。它是 Python 内置的,无需额外安装。

1.1 为什么需要 urllib?

在 Web 开发中,经常需要:

  • 从网站获取数据(如 API 调用)
  • 下载文件
  • 解析和处理 URL
  • 发送 HTTP 请求

urllib 的优势

  1. 无需安装 :Python 内置,开箱即用
  2. 学习价值高 :帮助理解 HTTP 协议原理
  3. 功能完整 :满足基本的网络请求需求
  4. 可控性强 :可以精细控制请求过程

简单理解

  • 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#fragment

4.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 错误(如网络错误、超时)
  • HTTPErrorURLError 的子类,所以先捕获 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 的区别?

特性urllibrequests
安装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])

注意 :不验证证书存在安全风险,仅用于测试环境。

使用 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():解析 URL
    • urlencode():编码查询参数
    • quote():编码 URL 中的特殊字符
  • urllib.error :异常处理

    • HTTPError:HTTP 错误
    • URLError:URL 错误

8.2 使用 urllib 的好处

  1. 无需安装 :Python 内置,开箱即用
  2. 学习价值高 :帮助理解 HTTP 协议原理
  3. 功能完整 :满足基本的网络请求需求
  4. 可控性强 :可以精细控制请求过程