如何解码 quoted-printable
- 点击工具上方的 Quoted-printable → 文本,切换到解码方向。
- 把编码后的文本粘贴到输入框,比如从邮件源码中复制出来的某个正文部分。
- 在输出中查看文本。如果带重音的字母显示不对,就把输出下方的 字符集 改成该部分
Content-Type头中声明的字符集,例如charset=iso-8859-1。 - 点击 复制输出 或 下载。软换行会被拼接起来,每个
=XX都会还原成对应的字节。
如果要编码,保持方向为 文本 → Quoted-printable,输入或粘贴文本即可。输入下方的 字符集 决定非 ASCII 字符会变成哪些字节。
quoted-printable 的工作原理
quoted-printable(RFC 2045 第 6.7 节)尽量让文本保持可读,只转义必须转义的内容:
- 可打印的 ASCII 字符保持原样,
=除外。 - 其他字节都写成
=加两位大写十六进制数字。在 UTF-8 中,每个非 ASCII 字符会变成 2 到 4 个转义序列。 =本身变成=3D。- 行尾的空格或制表符变成
=20或=09。 - 每行最多 76 个字符。更长的行用软换行拆开:在行尾放一个
=,解码时会被删除。
| 文本 | Quoted-printable |
|---|---|
café | caf=C3=A9 |
Grüße aus München | Gr=C3=BC=C3=9Fe aus M=C3=BCnchen |
100% = €5 | 100% =3D =E2=82=AC5 |
<p style="color:red">Hi</p> | <p style=3D"color:red">Hi</p> |
Caf=E9 按 ISO-8859-1 解码 | Café |
最后一行的 =E9 是单个字节:在 ISO-8859-1 和 Windows-1252 中是 é,在 UTF-8 中单独出现则无效,所以结果取决于解码时使用的字符集。
文本模式和二进制模式
编码器默认把输入当作文本处理。换行保持为真正的换行;并且和 Python 的 binascii.b2a_qp 一样,如果第一行以 CRLF 结尾,后面单独的 LF 也会变成 CRLF。这对邮件文本是正确的,但会改变字节。
当字节必须完全一致时,勾选 编码换行符(二进制)。这样换行会写成 =0D 和 =0A,Hello、CRLF、World 会变成 Hello=0D=0AWorld,解码后得到的正好是原始输入。至于图片这类真正的二进制数据,本来就更适合用 Base64。
邮件中用 quoted-printable 还是 Base64
邮件软件会为每个 MIME 部分选择 Content-Transfer-Encoding。quoted-printable 适合以 ASCII 为主的文本,因为它保持可读,体积增加也少。Base64 的额外开销固定在 33% 左右,一旦大部分字节都需要转义,Base64 就更小:
| 文本 | UTF-8 字节数 | Quoted-printable | Base64 |
|---|---|---|---|
Grüße aus München | 20 | 32 个字符 | 28 个字符 |
Привет, мир(俄语) | 20 | 56 个字符 | 28 个字符 |
即便是 17 个字符里只有 3 个非 ASCII 字母的德语文本,用 quoted-printable 也已经比 Base64 大;西里尔文更是 Base64 的两倍。要生成邮件要求的每行 76 字符的 Base64,请使用 MIME Base64 编码器。邮件中的 Base64 讲解了这两种编码在邮件中的样子,包括附件和编码后的邮件头。
读取整封邮件
从原始邮件中复制一个正文部分,适合快速看一眼。对于完整的 .eml 文件,EML 解码器会解析邮件头,按声明的字符集解码 quoted-printable 和 Base64 部分,并列出可下载的附件。quoted-printable 在邮件之外也会出现,比如 vCard 2.1 文件中的 ENCODING=QUOTED-PRINTABLE;把冒号后面的值粘贴到这里即可。
在代码中使用 quoted-printable
import quopri
quopri.encodestring("café".encode("utf-8")) # b'caf=C3=A9'
quopri.decodestring(b"caf=C3=A9 =3D 5=E2=82=AC").decode("utf-8") # 'café = 5€'
Python 的 email 包会自动选择正确的解码方式:msg.get_payload(decode=True) 返回某个部分解码后的字节,无论它用的是哪种传输编码。Base64 部分的用法见 Python 页面。
常见问题
邮件里的 =3D 是什么意思?
它是编码后的等号。在 quoted-printable 中,= 是转义的开头,所以等号本身必须写成 =3D,也就是它的十六进制字节值。HTML 邮件里很常见,比如 style=3D"color:red"。
为什么邮件源码的行尾有个 =?
那是软换行。quoted-printable 每行最多 76 个字符,较长的行会被拆开,每个断点在行尾用 = 标记。解码器会把这些片段重新拼成一行。
行尾的 =20 是什么意思?
编码后的空格。邮件系统可能会删掉行尾空格,所以 quoted-printable 会把行尾的空格或制表符写成 =20 或 =09 来保护它们。
如何在 Python 中解码 quoted-printable?
quopri.decodestring(data) 或 binascii.a2b_qp(data) 返回字节;再调用 .decode("utf-8"),或改用 Content-Type 头中声明的字符集。
能解码 =?UTF-8?Q?...?= 这样的邮件标题吗?
这个页面不行。那是 RFC 2047 的 encoded word,使用一种相近的 Q 编码,用 _ 表示空格。EML 解码器可以解码邮件头中的这类内容,也能解码 quoted-printable 正文。