Python发送带有“quoted-printable”传输编码和“utf-8”内容编码的电子邮件。

8

Python的email.mime倾向于使用编码方式base647bitus-ascii. 我想使用quoted-printableutf-8,因为这对人类更易于阅读和调试。

目前,我的邮件看起来像:

--===============6135350048414329636==
MIME-Version: 1.0
Content-Type: text/plain
Content-Transfer-Encoding: base64

IyEvYmluL2Jhc2gKCmZvciBpIGluIHs4Mjg4Li44N

或者

--===============0756888342500148236==
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit

happy face =E2=98=BA

我希望原始电子邮件采用可引用的Unicode编码,这样人们就更容易阅读了。

--===============5610730199728027971==
MIME-Version: 1.0
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset="utf-8"

happy face ☺

1
可能是Encode MIMEText as quoted printables的重复问题。 - tripleee
1
请参见https://dev59.com/2mDVa4cB1Zd3GeqPgL_b。 - tripleee
1
对于Python 3.6+,请参见现在的https://stackoverflow.com/questions/66039715/python3-email-message-to-disable-base64-and-remove-mime-version/66041936#66041936 - tripleee
3个回答

13

简短回答

设置content-transfer-encoding

创建将附加到MIMEMultipart对象的MIMEText对象时,首先将content-transfer-encoding设置为值quoted-printable,然后执行set_payload。操作顺序很重要。

from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

# first create MIMEText, then set content-transfer-encoding, then set payload
mt = MIMEText(None, _subtype='plain')
mt.replace_header('content-transfer-encoding', 'quoted-printable')
mt.set_payload(u'happy face ☺', 'utf-8')

# create the parent email object and the MIMEMultipart extension to it
email = MIMEMultipart('mixed')
inline = MIMEMultipart('alternative')

# assemble the objects
inline.attach(mt)
email.attach(inline)

设置电子邮件的字符集和各种编码方式

cs = charset.Charset('utf-8')
cs.header_encoding = charset.QP
cs.body_encoding = charset.QP
email.set_charset(cs)

结果

这将创建一封原始邮件,其内容对人类可读(除了 base64 编码的文件附件)。

>>> print(email)
--===============5610730199728027971==
MIME-Version: 1.0
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset="utf-8"

happy face ☺

--===============5610730199728027971==--

--===============0985725891393820576==
Content-Type: text/x-sh
MIME-Version: 1.0
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="test.sh"

Zm9vYmFyc2RmYXNkZmtqaGFzZGZrbGhhc2ZrbGpoYXNma2xqaGFzZmtsaGZkYXNmCg==

--===============0985725891393820576==--

长篇回答

以下是更长的脚本,以提供有关先前代码片段的更多背景。

此脚本将发送以UTF-8编码的text/plain部分。 为了好玩,它还将附加一个文件。 这将产生可读的原始电子邮件(除了文件附件)。

from __future__ import print_function

from email import charset    
from email.encoders import encode_base64
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import mimetypes

# create the parent email object
email = MIMEMultipart('mixed')
# set email charset and email encodings
cs_ = charset.Charset('utf-8')
cs_.header_encoding = charset.QP
cs_.body_encoding = charset.QP
email.set_charset(cs_)

# create the 'text/plain' MIMEText
# first create MIMEText, then set content-transfer-encoding, then set payload
mt = MIMEText(None, _subtype='plain')
mt.replace_header('content-transfer-encoding', 'quoted-printable')
mt.set_payload(u'happy face ☺', 'utf-8')

# assemble the parts
inline = MIMEMultipart('alternative')
inline.attach(mt)
email.attach(inline)

# for fun, attach a file to the email
my_file = '/tmp/test.sh'
mimetype, encoding = mimetypes.guess_type(my_file)
mimetype = mimetype or 'application/octet-stream'
mimetype = mimetype.split('/', 1)
attachment = MIMEBase(mimetype[0], mimetype[1])
attachment.set_payload(open(my_file, 'rb').read())
encode_base64(attachment)
attachment.add_header('Content-Disposition', 'attachment', filename=os.path.basename(my_file))
email.attach(attachment)

结果

这将创建一封原始的电子邮件,可以被人类阅读(除了base64编码的文件附件)。

>>> print(email)
--===============5610730199728027971==
MIME-Version: 1.0
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset="utf-8"

happy face ☺

--===============5610730199728027971==--

--===============0985725891393820576==
Content-Type: text/x-sh
MIME-Version: 1.0
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="test.sh"

Zm9vYmFyc2RmYXNkZmtqaGFzZGZrbGhhc2ZrbGpoYXNma2xqaGFzZmtsaGZkYXNmCg==

--===============0985725891393820576==--

(额外奖励)发送电子邮件

使用smtplib,可以将电子邮件发送。

import smtplib

# set email address headers
email['From'] = 'me@email.com'
email['To'] = 'you@email.com'
email['Subject'] = 'hello'

# send the email
smtp_srv = smtplib.SMTP('localhost')
smtp_srv.set_debuglevel(True)
print(mesg_html, end='\n\n')
print(email.as_string(), end='\n\n')
smtp_srv.sendmail('me@email.com', 'you@email.com', email.as_string())
smtp_srv.quit()

2
这个例子似乎根本没有引用可打印编码。正确的QP应该看起来像 happy face =E2=98=BA -- 或者,使用Python默认QP实现中过于热衷于编码空格的恶劣方式,是 happy=20face=20=E2=98=BA - tripleee
你可以使用mt.set_payload(u'happy face ☺'.encode('quoted-printable'), 'utf-8')来获取后者。 - tripleee
1
另外,你应该使用 _subtype='plain' 而不是 text/plaintext 已经被隐含了,并不是子类型的一部分)。 - tripleee
1
“这个例子似乎根本没有引用可打印编码。”谢谢@tripleee,我会在下周内进行审核和更正。我已经更正了“_subtype”。 - JamesThomasMoon
这并不是必要的。可以从其中一个链接的建议重复代码中获取代码。 - tripleee

3
在试图修改现有消息的正文(email.Message对象)并将其编码设置为“quoted-printable”时,我发现这个问题比我预期的要花费更多的精力。
import email
#... 'part' is the Message object
content = part.get_payload(decode=True)
#... Modify content
part['Content-Transfer-Encoding'] = '8bit'
part.set_payload(content, 'UTF-8')
del part['Content-Transfer-Encoding']
email.encoders.encode_quopri(part)

现在,为什么我要设置然后删除Content-Transfer-Encoding头?如果没有头部存在,set_payload调用将设置Content-Transfer-Encoding头并对数据进行编码(Base64)。否则,set_payload调用将假定调用者已经对数据进行了编码,并且不会更改它(通过编码)。因此,实际上并不重要我将Content-Transfer-Encoding头设置为什么值,只是不能留空。
但是为什么我需要删除头部呢?email.encoders.encode_quopri调用只会添加一个标题,因此该消息将导致多个Content-Transfer-Encoding头。
因此,仅使用set_payload然后使用encode_quopri处理没有Content-Transfer-Encoding头的消息将导致Base64字符串的Quoted-printable表示形式,并且对于具有现有Content-Transfer-Encoding头的消息将导致具有重复头的消息。使用encode_quopri然后set_payload可能会导致重复的头,但不会对消息进行编码。因此,需要添加/删除操作以避免使用quopri模块

1
最简单的解决方法就是:
from email.mime.text import MIMEText
from email import charset

cs = charset.Charset('utf-8')
cs.body_encoding = charset.QP
message = MIMEText(your_body_here, 'plain', cs)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接