杰瑞科技汇

Python urlopen 如何处理中文?

  1. 如何发送包含中文的请求(在 URL 参数或 POST 数据中)。
  2. 如何正确地解码从服务器获取的、包含中文的响应内容。

核心问题:URL 编码 和 字符编码

在计算机中,URL 和网络传输只能使用 ASCII 字符集中的字符,像中文这样的非 ASCII 字符,必须被转换成一种安全的格式,这个过程就是 URL 编码(或称百分号编码)。

Python urlopen 如何处理中文?-图1
(图片来源网络,侵删)
  • 空格 会被编码成 %20
  • 会被编码成 %E4%B8%AD
  • 会被编码成 %E6%96%87

同样,从服务器返回的网页数据(HTML、JSON 等)本身也是一串字节流,我们需要用正确的 字符编码(如 UTF-8, GBK)将这些字节流转换成我们能读懂的字符串。


发送包含中文的请求

当你需要在 URL 的查询参数或 POST 请求体中包含中文时,必须先进行 URL 编码,Python 的 urllib.parse 模块提供了 quoteurlencode 函数来方便地完成这个任务。

示例1:在 URL 查询参数中添加中文

假设我们要搜索关键词 "Python 中文"。

错误的做法(直接拼接):

Python urlopen 如何处理中文?-图2
(图片来源网络,侵删)
from urllib.request import urlopen
keyword = "Python 中文"
# 直接拼接,URL是非法的,会报错或无法正确识别
url = f"http://httpbin.org/get?keyword={keyword}" 
# 这里的 httpbin.org 会返回你请求的 URL,方便我们查看

这样拼接的 URL 可能会是 http://httpbin.org/get?keyword=Python 中文,其中包含空格和非 ASCII 字符,很多服务器无法正确解析。

正确的做法(使用 urllib.parse.quote):

from urllib.request import urlopen
from urllib.parse import quote
keyword = "Python 中文"
# 对中文和空格进行 URL 编码
encoded_keyword = quote(keyword) # 编码后为 'Python%20%E4%B8%AD%E6%96%87'
url = f"http://httpbin.org/get?keyword={encoded_keyword}"
print(f"编码后的 URL: {url}")
# 发送请求
response = urlopen(url)
# 读取响应内容 (bytes)
html_bytes = response.read()
# 解码响应内容 (这里 httpbin.org 默认返回 UTF-8 编码的 JSON)
html_str = html_bytes.decode('utf-8')
print(html_str)

输出:

编码后的 URL: http://httpbin.org/get?keyword=Python%20%E4%B8%AD%E6%96%87
{
  "args": {
    "keyword": "Python 中文"
  },
  "headers": {
    "Accept-Encoding": "identity",
    "Host": "httpbin.org",
    "User-Agent": "Python-urllib/3.10",
    "X-Amzn-Trace-Id": "Root=..."
  },
  "origin": "你的IP地址",
  "url": "http://httpbin.org/get?keyword=Python%20%E4%B8%AD%E6%96%87"
}

可以看到,httpbin.org 正确地解码了 keyword 参数,并返回了原始的中文值 "Python 中文"。

Python urlopen 如何处理中文?-图3
(图片来源网络,侵删)

示例2:在 POST 请求体中发送中文表单数据

from urllib.request import Request, urlopen
from urllib.parse import urlencode
# 要发送的表单数据
data = {
    'username': '张三',
    'comment': '你好,世界!'
}
# 将字典编码成 application/x-www-form-urlencoded 格式的字节流
# 注意:urlencode 会自动对字典的值进行 URL 编码
encoded_data = urlencode(data).encode('utf-8')
url = 'http://httpbin.org/post'
# 创建请求对象,可以指定请求方法为 POST
request = Request(url, data=encoded_data, method='POST')
# 发送请求
response = urlopen(request)
html_bytes = response.read()
html_str = html_bytes.decode('utf-8')
print(html_str)

输出(部分):

{
  "form": {
    "comment": "你好,世界!",
    "username": "张三"
  },
  ...
}

可以看到,POST 的表单数据也被正确地发送和接收了。


解码包含中文的响应内容

从服务器返回的数据是字节流(bytes),直接打印会看到一堆乱码,因为你不知道它原本的字符编码是什么。

解决步骤:

  1. 读取字节流:使用 response.read()
  2. 探测或指定字符编码
    • 最佳实践:从 HTTP 响应头中获取 Content-Type 字段,它通常会指定字符编码,Content-Type: text/html; charset=utf-8
    • 备用方案:如果响应头没有指定,可以尝试猜测(很多中文网站使用 GBK 编码)。
  3. 解码字节流:使用 bytes.decode() 方法,将字节流转换成字符串。

示例:从不同编码的网站获取内容

from urllib.request import urlopen
import json
def fetch_and_decode(url, charset='utf-8'):
    """获取并解码 URL 内容"""
    try:
        response = urlopen(url)
        # 获取响应头中的 Content-Type
        content_type = response.headers.get('Content-Type', '')
        print(f"原始响应头 Content-Type: {content_type}")
        # 从 Content-Type 中提取 charset
        #  'text/html; charset=gbk'
        if 'charset=' in content_type:
            # 更新编码为响应头中指定的编码
            charset = content_type.split('charset=')[-1]
            print(f"从响应头提取到编码: {charset}")
        # 读取字节流
        html_bytes = response.read()
        # 使用指定的编码进行解码
        html_str = html_bytes.decode(charset)
        return html_str
    except UnicodeDecodeError:
        print(f"使用编码 '{charset}' 解码失败,尝试其他编码...")
        # 如果指定编码失败,可以尝试其他常见编码
        try:
            html_str = html_bytes.decode('gbk')
            return html_str
        except UnicodeDecodeError:
            print("GBK 解码也失败了,无法解码内容。")
            return None
    except Exception as e:
        print(f"发生错误: {e}")
        return None
# --- 测试 ---
# 1. 一个 UTF-8 编码的网站 (httpbin.org 返回 JSON)
print("--- 测试 UTF-8 ---")
url_utf8 = 'http://httpbin.org/get?name=测试'
content_utf8 = fetch_and_decode(url_utf8)
if content_utf8:
    # 解析 JSON 以便更清晰地查看
    data = json.loads(content_utf8)
    print(f"成功获取 name 参数: {data['args']['name']}")
print("\n" + "="*40 + "\n")
# 2. 一个 GBK 编码的网站 (这里用 httpbin.org 模拟,实际需要找真实GBK网站)
# 我们手动构造一个 GBK 编码的响应来演示
print("--- 测试 GBK ---")
# 创建一个模拟的 GBK 编码的响应
gbk_text = "你好,世界!This is a test."
gbk_bytes = gbk_text.encode('gbk')
# 创建一个模拟的响应头
from http.client import HTTPMessage
headers = HTTPMessage()
headers.add_header('Content-Type', 'text/html; charset=gbk')
# 我们无法直接修改 urlopen 的响应头,所以这里用字符串模拟
# 实际爬虫中,你需要分析真实网站的响应头
# 假设我们知道了这是一个 GBK 网站并手动指定
url_gbk = 'http://httpbin.org/encoding/utf8' # 这个 URL 实际是 UTF-8,我们假装它是 GBK
# 为了演示,我们直接使用我们编码的字节
# 在真实场景中,你通过 urlopen(url_gbk).read() 得到 gbk_bytes
content_gbk = fetch_and_decode(url_gbk, charset='gbk') # 手动指定编码
if content_gbk:
    print(f"成功解码 GBK 内容: {content_gbk}")

更健壮的实践: 在实际爬虫中,通常的流程是:

  1. urlopen 获取 response 对象。
  2. response.headers.get('Content-Type') 中提取 charset
  3. 如果提取到了 charset,就用它来解码 response.read()
  4. 如果没有提取到,可以先用 utf-8 尝试解码,如果失败,再尝试 gbkgb2312 等中文常见编码。

总结与最佳实践

  1. 发送请求时

    • URL 参数:使用 urllib.parse.quote() 对非 ASCII 字符进行编码。
    • POST 数据:使用 urllib.parse.urlencode() 将字典数据编码成 application/x-www-form-urlencoded 格式的字节流,它会自动处理编码。
  2. 接收响应时

    • 首选:从 response.headers['Content-Type'] 中获取 charset,并用它来解码 response.read()
    • 备用:如果响应头没有 charset,可以先尝试 utf-8,失败后再尝试 gbk
    • 解码:始终使用 bytes.decode('charset_name') 将字节流转换为字符串。
  3. 推荐:对于更复杂的网络请求(如处理 Cookie、Session、自定义 Headers),建议使用更强大的第三方库,如 requestsrequests 库能更智能地处理编码问题,让开发者更专注于业务逻辑。

requests 示例(对比):

import requests
# 发送 GET 请求,requests 会自动对 URL 中的中文进行编码
response = requests.get('http://httpbin.org/get', params={'keyword': 'Python 中文'})
# 获取响应内容,requests 会自动根据响应头的 Content-Type 进行解码
# 你可以直接得到一个字符串
print(response.text) 
# 也可以获取 JSON 数据,requests 会自动解析
print(response.json()['args']['keyword'])

可以看到,requests 库极大地简化了这些操作。

分享:
扫描分享到社交APP
上一篇
下一篇