Python脚本邮件附件中文名乱码怎么办

wen 实用脚本 1

本文目录导读:

Python脚本邮件附件中文名乱码怎么办

  1. 目录导读
  2. 问题现象与根源分析
  3. 常见错误代码示例
  4. 正确解决方案(一):使用Header对象编码
  5. 正确解决方案(二):设置MIMEBase的Content-Disposition
  6. 终极兼容方案:RFC 2231/5987 编码
  7. 完整可运行代码示例
  8. QA常见问题解答
  9. 实战技巧与避坑指南

Python脚本发送邮件附件中文名乱码?一文解决所有编码陷阱

目录导读

  1. 问题现象与根源分析
  2. 常见错误代码示例
  3. 正确解决方案(一):使用Header对象编码
  4. 正确解决方案(二):设置MIMEBase的Content-Disposition
  5. 终极兼容方案:RFC 2231/5987 编码
  6. 完整可运行代码示例
  7. QA常见问题解答
  8. 实战技巧与避坑指南

问题现象与根源分析

很多Python开发者在用smtplibemail.mime模块发送带中文名称的附件时,收件人看到的是类似=?UTF-8?B?5rWL6K+V5paH5qyW?=或者直接显示为乱码。
根本原因:邮件协议(MIME)要求附件文件名必须明确指定字符编码(尤其是非ASCII字符),否则邮件客户端无法正确解析,Python标准库email模块在默认情况下不会自动对附件名进行编码,需要开发者手动处理。

关键字

  • email.header.Header
  • Content-Disposition 参数
  • base64 或 quoted-printable 编码

常见错误代码示例

先看一个“反面教材”,你很可能正在用这种写法:

from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email import encoders
msg = MIMEMultipart()
with open('报告.pdf','rb') as f:
    part = MIMEBase('application', 'octet-stream')
    part.set_payload(f.read())
    encoders.encode_base64(part)
    part.add_header('Content-Disposition', 'attachment', filename='中文报告.pdf')
msg.attach(part)

输出效果

  • 网易、QQ邮箱: 显示为乱码或乱码+文件名截断
  • Gmail: 可能显示为压缩包名称乱码

原因分析:add_header 参数中的filename='中文报告.pdf'是原始字节(Python3中是str类型),没有经过编码转换,邮件传输时会被当成ASCII处理,导致乱码。


正确解决方案(一):使用Header对象编码

官方推荐用email.header.Header对文件名进行编码,再把编码后的字符串传给filename

from email.header import Header
# 将文件名进行Base64编码
encoded_name = Header('中文报告.pdf', 'utf-8').encode()
part.add_header('Content-Disposition', 'attachment', filename=encoded_name)

关键点

  • Header('原始字符串', '字符编码') 会自动生成类似 =?UTF-8?B?5Lit5paH5oKf5ZGK?= 的编码字符串。
  • 此方案兼容Outlook、Thunderbird等主流客户端。
  • 注意:Header默认使用base64编码,长度可控。

但有一个遗留问题:某些老旧邮件系统(如Exchange 2007)遇到这种编码的附件名可能显示异常。


正确解决方案(二):设置MIMEBase的Content-Disposition

除了用Header,也可以直接设置Content-Disposition字段的filename*参数(注意带星号),这种写法是RFC 5987定义的现代标准,效果更好。

from email.mime.base import MIMEBase
from email import encoders
with open('用户手册.pdf', 'rb') as f:
    part = MIMEBase('application', 'octet-stream')
    part.set_payload(f.read())
    encoders.encode_base64(part)
    # 用 filename* 替代 filename
    part.add_header('Content-Disposition', 'attachment',
                   filename*=('utf-8', '', '用户手册.pdf'))
msg.attach(part)

参数解释

  • filename*= 后必须是一个三元组(charset, language, value)
  • language 可以留空字符串
  • filename*=('utf-8', '', '用户手册.pdf')

此方案对Gmail、Apple Mail支持极佳,但Outlook(尤其是旧版)可能忽略filename*而优先使用filename,因此更稳妥的做法是同时提供两个版本。


终极兼容方案:RFC 2231/5987 编码

同时设置 filenamefilename*,让客户端按优先级选择。
代码示例:

part.add_header('Content-Disposition', 'attachment',
               filename=Header('中文报告.pdf', 'utf-8').encode(),
               filename*=('utf-8', '', '中文报告.pdf'))

顺序

  1. 现代客户端(如Gmail、Outlook 2013+)会识别 filename* 并使用UTF-8。
  2. 老旧客户端优先选择简单的 filename,其值为Base64编码,也能正确解码。
  3. 如果只设置filename*,部分手机邮件客户端可能显示空白。

完整可运行代码示例

以下是一个发送带中文附件邮件的完整脚本,融合了全部最佳实践:

import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email.mime.text import MIMEText
from email.header import Header
from email import encoders
def send_mail_with_attachment(sender, receiver, subject, body, filepath, filename):
    # 创建邮件主体
    msg = MIMEMultipart()
    msg['From'] = sender
    msg['To'] = receiver
    msg['Subject'] = Header(subject, 'utf-8')
    # 添加正文
    msg.attach(MIMEText(body, 'plain', 'utf-8'))
    # 添加附件(带中文名)
    with open(filepath, 'rb') as f:
        part = MIMEBase('application', 'octet-stream')
        part.set_payload(f.read())
        encoders.encode_base64(part)
        # 同时设置两种编码方式
        part.add_header('Content-Disposition', 'attachment',
                       filename=Header(filename, 'utf-8').encode(),
                       filename*=('utf-8', '', filename))
        msg.attach(part)
    # 发送邮件
    with smtplib.SMTP('smtp.qq.com', 587) as server:
        server.starttls()
        server.login('your@qq.com', '授权码')
        server.send_message(msg)
# 调用示例
send_mail_with_attachment(
    sender='your@qq.com',
    receiver='friend@gmail.com',
    subject='重要:中文报告',
    body='请查收附件。',
    filepath='./output.pdf',
    filename='中文报告.pdf'
)

QA常见问题解答

*Q1:为什么我设置了`filename,对方还是看到乱码?** A:很可能是邮件服务器或客户端不支持RFC 5987,建议同时设置filename(用Header编码),如果对方用Outlook 2010以下版本,可能需要将文件名限制在ASCII范围内,或用Base64编码的filename`。

Q2:用Header编码后,文件名变成了很长的=?UTF-8?B?...?=,正常吗?
A:完全正常,这是MIME编码的标准形式,所有邮件客户端都会自动还原,如果不想看到这种长字符串,可以改为使用filename*方式。

Q3:发送多份附件时,文件名乱码是否独立?
A:是的,每个MIME对象都要单独编码,千万别在循环外只编码一次。

Q4:除了.pdf.xls.docx等也会乱码吗?
A:只要文件名包含非ASCII字符(中文、日文、重音符号等),就一定会乱码,与文件类型无关。

Q5:有没有更简单的第三方库?
A:yagmail库可以自动处理编码,但底层原理仍是上述方案,如果想完全掌控,坚持用标准库。


实战技巧与避坑指南

  • 编码前先检查文件名:如果文件名本身是未知编码(例如从Windows系统传入的GBK),要用 filename.encode('utf-8').decode('utf-8') 先统一为Unicode。
  • 文件名长度:某些邮件系统对filename*中的字符串长度有限制(比如255字节),超长内容可能被截断,如果文件名过长,建议截取或省略。
  • 测试邮件客户端:至少用Gmail、Outlook Web、网易邮箱大师、Apple Mail验证一遍。
  • 不要使用part.set_param()add_header才是标准的添加头字段方法,set_param会被误用。
  • 如果你的脚本部署在Linux服务器:确保服务器默认语言设置是UTF-8,可以在Python脚本开头强制设置:
    import locale
    locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')


Python发送带中文附件邮件时,务必对Content-Disposition中的文件名进行MIME编码,最推荐的做法是:*同时使用 Header 编码的 filename + RFC 5987 标准的 `filename`**,这样既能兼容老系统,也能完美支持现代客户端,记住这条黄金法则,你的邮件再也不会出现“%E4%B8%AD%E6%96%87”这样的乱码。

转发给被中文附件折磨的同事吧!

抱歉,评论功能暂时关闭!