URL 本身、POST 请求的表单数据、以及从服务器接收到的响应内容,都可能需要不同的编码处理。
下面我将分三个主要部分来解释:
- URL 编码 (处理 URL 中的特殊字符)
- POST 数据编码 (构建请求体)
- 解码 (处理服务器返回的数据)
准备工作:Python 2 环境和 urllib2
确保你有一个 Python 2 的环境。urllib2 是 Python 2 的标准库,无需额外安装。
# Python 2 import urllib2 import urllib # 注意:urllib 和 urllib2 经常一起使用
URL 编码 (处理 URL 中的特殊字符)
URL 只能包含 ASCII 字符集,像空格、中文、&、 这样的特殊字符都不能直接出现在 URL 中,我们需要将它们转换成一种安全的格式,这个过程叫做 URL 编码 或 百分号编码。
- 空格 (
`) ->%20` - 中文字符 "中" ->
%E4%B8%AD &->%26
何时需要编码?
当你手动构建一个包含非 ASCII 字符或特殊字符的 URL 时。
如何编码?
使用 urllib 模块中的 quote() 或 urlencode() 函数。
示例 1:使用 urllib.quote() 编码单个字符串
quote() 用于编码 URL 的路径部分或单个参数值。
search_keyword = "Python 编程"
# 对关键词进行编码
encoded_keyword = urllib.quote(search_keyword)
print "编码后的关键词:", encoded_keyword
# 输出: 编码后的关键词: Python%20%E7%BC%96%E7%A8%8B
# 构建完整的 URL
base_url = "http://www.example.com/search?q="
full_url = base_url + encoded_keyword
print "完整的 URL:", full_url
# 输出: 完整的 URL: http://www.example.com/search?q=Python%20%E7%BC%96%E7%A8%8B
# 使用 urllib2 发送请求
try:
response = urllib2.urlopen(full_url)
html = response.read()
print "成功获取网页内容,长度:", len(html)
except urllib2.URLError as e:
print "请求失败:", e
示例 2:使用 urllib.urlencode() 编码字典 (用于查询参数或 POST 数据)
urlencode() 是更方便的函数,它接受一个字典,并将其转换为 key1=value1&key2=value2 的格式,同时自动对键和值进行编码。
# 准备查询参数
params = {
'q': 'Python 编程',
'source': 'web',
'lang': 'zh-CN'
}
# 使用 urlencode 将字典编码为查询字符串
query_string = urllib.urlencode(params)
print "编码后的查询字符串:", query_string
# 输出: 编码后的查询字符串: q=Python+%E7%BC%96%E7%A8%8B&source=web&lang=zh-CN
# 构建完整的 URL
base_url = "http://www.example.com/search?"
full_url = base_url + query_string
print "完整的 URL:", full_url
# 输出: 完整的 URL: http://www.example.com/search?q=Python+%E7%BC%96%E7%A8%8B&source=web&lang=zh-CN
# 使用 urllib2 发送请求
try:
response = urllib2.urlopen(full_url)
html = response.read()
print "成功获取网页内容,长度:", len(html)
except urllib2.URLError as e:
print "请求失败:", e
POST 数据编码 (构建请求体)
当使用 POST 方法提交表单数据时,数据会放在 HTTP 请求的 body 中,这个 body 的格式和编码方式需要通过 Content-Type 请求头指定。
最常见的两种类型是:
application/x-www-form-urlencoded: 和 URL 查询字符串格式一样 (key1=value1&key2=value2)。multipart/form-data: 用于上传文件,格式更复杂。
如何编码?
同样使用 urllib.urlencode()。
示例:发送 POST 请求
import urllib
import urllib2
# 准备要提交的表单数据
post_data = {
'username': '我的用户名',
'password': '123456'
}
# 将数据编码为标准的 application/x-www-form-urlencoded 格式
encoded_data = urllib.urlencode(post_data)
print "编码后的 POST 数据:", encoded_data
# 输出: 编码后的 POST 数据: username=%E6%88%91%E7%9A%84%E7%94%A8%E6%88%B7%E5%90%8D&password=123456
# 创建请求对象
# 注意:URL 是目标地址,DATA 是要发送的数据
url = 'http://www.example.com/login'
request = urllib2.Request(url, data=encoded_data)
# 添加 Content-Type 请求头,告诉服务器我们发送的是什么格式的数据
# 这一步非常重要!
request.add_header('Content-Type', 'application/x-www-form-urlencoded')
# 发送请求
try:
response = urllib2.urlopen(request)
html = response.read()
print "POST 请求成功,响应内容长度:", len(html)
except urllib2.URLError as e:
print "POST 请求失败:", e
解码 (处理服务器返回的数据)
这是最容易被忽略也最容易出错的地方,当你用 response.read() 读取内容时,得到的是一个字节串 (bytes),而不是字符串 (string),这个字节串是什么编码(UTF-8, GBK),取决于服务器如何设置 Content-Type 响应头。
如何解码?
- 检查响应头:从
response.headers中查看Content-Type。 - 使用
decode()方法:将字节串解码成 Unicode 字符串。
示例:处理不同编码的响应
import urllib2
# 假设这个 URL 返回 GBK 编码的中文网页
url_gbk = 'http://www.example.com/gbk_page'
# 假设这个 URL 返回 UTF-8 编码的中文网页
url_utf8 = 'http://www.example.com/utf8_page'
def fetch_and_decode(url):
try:
response = urllib2.urlopen(url)
# 1. 从响应头中获取编码
# headers.get('Content-Type') 可能返回 'text/html; charset=GBK'
content_type = response.headers.get('Content-Type')
print "响应头 Content-Type:", content_type
# 提取编码信息,默认为 'utf-8'
# 简单处理,实际情况可能更复杂
charset = 'utf-8'
if 'charset=' in content_type:
charset = content_type.split('charset=')[-1].strip()
print "检测到的编码:", charset
# 2. 读取原始字节串
raw_bytes = response.read()
# 3. 使用检测到的编码进行解码
# 注意:如果编码猜错了,这里会抛出 UnicodeDecodeError
decoded_html = raw_bytes.decode(charset)
print "解码成功,内容前50个字符:"
print decoded_html[:50]
return decoded_html
except urllib2.URLError as e:
print "请求失败:", e
except UnicodeDecodeError as e:
print "解码失败!编码可能不正确,错误信息:", e
print "原始字节串前50个字节:", raw_bytes[:50]
except Exception as e:
print "发生未知错误:", e
# 测试 GBK 编码页面
print "--- 尝试获取 GBK 编码页面 ---"
fetch_and_decode(url_gbk)
# 测试 UTF-8 编码页面
print "\n--- 尝试获取 UTF-8 编码页面 ---"
fetch_and_decode(url_utf8)
如果服务器没有指定编码怎么办?
这是一个很常见的问题。Content-Type 中没有 charset,你只能猜测,常见的策略是:
- 优先尝试
utf-8:因为它是目前最通用的编码。 - 尝试
gbk/gb2312:如果访问的是中国大陆的网站,这是一个很好的猜测。 - 使用更高级的库,如
chardet,它可以自动检测文本的编码。
使用 chardet 自动检测编码:
首先需要安装 chardet:
pip install chardet
import urllib2
import chardet
url = 'http://www.example.com/unknown_encoding_page'
try:
response = urllib2.urlopen(url)
raw_bytes = response.read()
# 使用 chardet 检测编码
result = chardet.detect(raw_bytes)
detected_encoding = result['encoding']
confidence = result['confidence']
print "chardet 检测结果: 编码 = %s, 置信度 = %.2f" % (detected_encoding, confidence)
if confidence > 0.7: # 置信度较高时再进行解码
decoded_html = raw_bytes.decode(detected_encoding)
print "解码成功,内容前50个字符:", decoded_html[:50]
else:
print "置信度太低,无法确定编码。"
except Exception as e:
print "发生错误:", e
总结与最佳实践
- 构建 URL:始终使用
urllib.quote()或urllib.urlencode()对 URL 中的非 ASCII 字符和特殊字符进行编码。 - 发送 POST 请求:使用
urllib.urlencode()编码表单数据,并务必设置Content-Type: application/x-www-form-urlencoded请求头。 - 处理响应:
- 永远不要假设响应的编码。
- 首先从
response.headers['Content-Type']中查找charset。 - 如果找不到,可以尝试
utf-8或gbk,或者使用chardet库进行自动检测。 - 获取到字节串 (
response.read()) 后,立即使用正确的编码调用.decode()方法,将其转换为 Unicode 字符串进行后续处理。
遵循这些原则,可以避免绝大多数在 Python 2 中使用 urllib2 时遇到的编码问题。
重要提示:
urllib2是 Python 2 的模块,在 Python 3 中,它被重构并合并到了urllib包中(urllib.request,urllib.parse,urllib.error),Python 3 对字符串和字节的处理更加清晰,编码问题虽然依然存在,但处理方式有所不同,如果你正在开始一个新项目,强烈建议使用 Python 3。
