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

3

我正在创建一张图片,希望可以在电子邮件中嵌入。但我无法弄清如何将图像创建为二进制并传递到MIMEImage中。以下是我的代码,当我尝试读取图像对象时出现错误 - 错误是“AttributeError:'NoneType'对象没有'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/电子邮件部分中图片的添加和关联方式感到困惑。需要任何帮助!

更新: 这段代码实际上是可以工作的——我只是在我的PC上打错了几个字母。


忘记了 - 我正在使用Python 3.4 - user3151858
我正在导入以下模块: - user3151858
从PIL导入Image,ImageDraw 导入smtplib 从电子邮件.mime.text导入MIMEText 从电子邮件.mime.image导入MIMEImage 从电子邮件.mime.multipart导入MIMEMultipart - user3151858
如果您有要添加到问题中的信息,请编辑它,而不是发布评论。 - MattDMo
图片作为附件到达了吗? - Mark Ransom
不,没有附件到达。看起来图像在过程中“丢失”,即它已经被创建了,因为我没有收到任何错误,但它没有被附加。 - user3151858
1个回答

5
这里有几个概念错误,一个是在使用PIL时,另一个是关于图像格式应该如何才能被嵌入到电子邮件中。
关于PIL: ImageDraw类是就地操作的,而不是像Image类调用一样,在每次操作后通常返回一个新图像。在您的代码中,这意味着对image_base.text的调用实际上正在更改位于image变量中的对象的像素数据。此调用实际上返回None,并且上面的代码应该在以下行上引发错误,例如“AttributeError:None对象没有属性'read'”。
除此之外(也就是说,您应该从image变量中获取数据以将其附加到电子邮件中),出现了第二个问题:由于明显的原因,PIL将图像存储为未压缩的原始像素数据格式。当在电子邮件中附加图像时,我们通常希望将图像整洁地打包在文件中-根据目的,PNG或JPG格式通常更好-让我们保持.PNG。因此,您必须使用PIL创建文件数据,然后附加文件数据(即包括头文件、元数据和以压缩形式表示的实际像素数据的PNG文件的数据)。否则,您将在电子邮件中放置一堆(未压缩的)像素数据,接收方将无法将其重新组合成图像(即使他将数据视为像素,原始像素数据也不包含图像形状-)
您有两个选择:要么在内存中生成文件字节,要么将它们写入实际的磁盘文件中,并重新读取该文件以进行附加。第二种形式更容易跟踪。第一个既更有效率又是“正确的做法”-因此让我们保持它:
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的部分 - 如果你使用得正确,现在应该可以工作了)
提示:如果您正确地使用代码中处理电子邮件和MIME的部分,则应该可以正常工作。

谢谢您的回复!为什么我需要使用.seek(0) - 您能否请说明一下?我也不确定文件保存在哪里 - 是在内存中吗?还有,包含可以附加到电子邮件的图片的对象是什么? - user3151858
2
BytesIO对象在Python中模拟文件,但仅存在于内存中。在PIL将等效于图像文件的信息记录到BytesIO对象后,“seek(0)”将其倒回,以便随后的“read”读取字节,就像它们在文件系统上的.png文件中一样。并且与您的原始代码一样,该字节字符串位于“imgObj”变量中,您将其附加到电子邮件中。 - jsbueno

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