如何将使用 PIL/pillow 创建的嵌入图像作为电子邮件发送 (Python 3)

How to send embedded image created using PIL/pillow as email (Python 3)

我正在创建要嵌入到电子邮件中的图像。我无法弄清楚如何将图像创建为二进制文件并传递给 MIMEImage。下面是我的代码,当我尝试读取图像对象时出现错误 - 错误是 "AttributeError: 'NoneType' object has no attribute 'read'".

image=Image.new("RGBA",(300,400),(255,255,255))
image_base=ImageDraw.Draw(image)
emailed_password_pic=image_base.text((150,200),emailed_password,(0,0,0))
imgObj=emailed_password_pic.read()
msg=MIMEMultipart()
html="""<p>Please finish registration <br/><img src="cid:image.jpg"></p>"""
img_file='image.jpg'
msgText = MIMEText(html,'html')
msgImg=MIMEImage(imgObj)
msgImg.add_header('Content-ID',img_file)
msg.attach(msgImg)
msg.attach(msgText)

如果您查看第 4 行 - 我正在尝试读取图像以便将其传递到 MIMEImage。显然,图像需要以二进制形式读取。但是,我不知道如何将它转换为二进制文件以便 .read() 可以处理它。

跟进 我根据 jsbueno 的建议编辑了代码 - 非常感谢!!!:

  emailed_password=os.urandom(16)
  image=Image.new("RGBA",(300,400),(255,255,255))
  image_base=ImageDraw.Draw(image)
  emailed_password_pic=image_base.text((150,200),emailed_password,(0,0,0))
  stream_bytes=BytesIO()
  image.save(stream_bytes,format='png')
  stream_bytes.seek(0)
  #in_memory_file=stream_bytes.getvalue()
  #imgObj=in_memory_file.read()
  imgObj=stream_bytes.read()
  msg=MIMEMultipart()
  sender='xxx@abc.com'
  receiver='jjjj@gmail.com'
  subject_header='Please use code provided in this e-mail to confirm your subscription.'
  msg["To"]=receiver
  msg["From"]=sender
  msg["Subject"]=subject_header
  html="""<p>Please finish registration by loging into your account and typing in code from this e-mail.<br/><img src="cid:image.png"></p>"""
  img_file='image.png'
  msgText=MIMEText(html,'html')
  msgImg=MIMEImage(imgObj) #Is mistake here?
  msgImg.add_header('Content-ID',img_file)
  msg.attach(msgImg)
  msg.attach(msgText)
  smtpObj=smtplib.SMTP('smtp.mandrillapp.com', 587)
  smtpObj.login(userName,userPassword)
  smtpObj.sendmail(sender,receiver,msg.as_string())

我现在没有收到错误消息,但电子邮件中没有图像。我对图像在 html/email 部分的附加方式和相关方式感到困惑。感谢您的帮助!

更新: 此代码确实有效 - 我只是在我的 PC 上的代码中出现了一些小错误。

这里存在一些概念性错误,无论是在使用 PIL 方面还是在图像应该采用何种格式才能纳入 e-mail。

在 PIL 中:ImageDraw class 就地操作,不像 Image class 调用,后者通常在每次操作后 return 一个新图像。在您的代码中,这意味着对 image_base.text 的调用实际上是在更改位于 image 变量中的 object 的像素数据。这个调用实际上是 returns None 并且上面的代码应该在下面的行中引发类似 "AttributeError: None object does not have attribute 'read'" 的错误。

过去(也就是说,您应该从 image 变量中获取数据以将其附加到 e-mail)是第二个问题:PIL,出于显而易见的原因,将图像放在内存中未压缩的原始像素数据格式。在 e-mails 中附加图像时,我们通常希望将图像整齐地打包在文件中 - PNG 或 JPG 格式通常更好,具体取决于意图 - 让我们继续使用 .PNG。因此,您必须使用 PIL 创建文件数据,并附加文件数据(即构成 PNG 文件的数据,包括 headers、元数据和压缩形式的实际像素数据)。否则你会在你的 e-mail 中放入一堆(未压缩的)像素数据,接收方将无法 assemble 返回到图像中(即使他会将数据视为像素,原始像素数据不包含图像形状 so-)

您有两个选择:要么在内存中生成 file-bytes,要么将它们写入磁盘中的实际文件,然后 re-read 该文件用于附加。第二种形式更容易理解。第一个既更有效又 "the right thing to do" - 所以让我们保留它:

from io import BytesIO
# In Python 2.x:
# from StringIO import StringIO.StringIO as BytesIO

image=Image.new("RGBA",(300,400),(255,255,255))
image_base=ImageDraw.Draw(image)
# this actually modifies "image"
emailed_password_pic=image_base.text((150,200),emailed_password,(0,0,0))
stream = BytesIO()
image.save(stream, format="png")
stream.seek(0)
imgObj=stream.read()
...

(注意:我没有检查您的代码中处理邮件和 mime 的部分 - 如果您正确使用它,它现在应该可以工作)