python3之发送和读取邮件

一、发送邮件

  • 发送邮件使用SMTP协议【Simple Mail Transfer Protocol简单的邮件传输协议】,SMTP协议是SMTP客户端与SMTP服务器之间的通信协议。
  • python中发送邮件使用的模块有smtplib和email:
    使用smtplib模块进行发送邮件;
    使用email模块来添加发送的邮件内容。

1. smtplib模块

导入模块:import smtplib

1.1. 创建SMTP对象

smtplib.SMTPsmtplib.SMTP_SSL:均可以用来创建SMTP对象;
smtplib.SMTP_SSL:使用安全加密的SSL协议连接到SMTP服务器;
smtplib.SMTP:没有进行安全加密。
故若待测试邮箱不允许使用非SSL和非TLS频道通信时,则无法使用smtp.SMTP方式来创建客户端对象。
【查看邮箱的通信方式:邮箱设置菜单中,查看邮箱的接收服务器和发送服务器信息。】

如:腾讯企业邮箱
接收服务器:
imap.exmail.qq.com(使用SSL,端口号993)
发送服务器:
smtp.exmail.qq.com(使用SSL,端口号465)
  • smtplib.SMTP(host, port, local_hostname, timeout, source_address)
  • smtplib.SMTP_SSL(host, port, local_hostname, keyfile, certfile, timeout, source_address, context)
    创建SMTP对象。
    host:SMTP发送服务器主机
    port:SMTP服务器端哭口号
1.2. SMTP对象操作
  • login(user, password, *, initial_response_ok=True)
    SMTP对象登录
    user:授权登录的用户名
    password:授权登录的密码
  • sendmail(from_addr, to_addrs, msg, mail_options=[], rcpt_options=[])
    SMTP对象发送邮件
    from_addr:发件人地址,字符串类型。
    to_addr:收件人地址,包括收件人和抄送人。
    多个收件人时to_addr参数为列表,单个收件人时to_addr参数可以为列表或字符串。
    msg:要发送的信息
  • quite()
    终止SMTP会话

2. 发送邮件的实例

2.1. 添加邮件内容,包括收件人、抄送人、正文、附件
from email.mime.multipart import MIMEMultipart
from email.header import Header
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
import os
class EmailContent:
   
    def __init__(self, senderAdr, emailSubject, toReceivers, ccReceivers):
        # 邮件对象
        self.msg = MIMEMultipart()
        # 添加发件人头
        self.msg['From'] = Header("测试" + "<" + senderAdr + ">", 'utf-8')
        # 添加收件人
        if isinstance(toReceivers, str):
            self.msg["To"] = toReceivers
        elif isinstance(toReceivers, list):
            self.msg['To'] = ";".join(toReceivers)
        # 添加抄送人
        if isinstance(ccReceivers, str):
            self.msg["Cc"] = ccReceivers
        elif isinstance(ccReceivers, list):
            self.msg["Cc"] = ";".join(ccReceivers)
        # 添加邮件主题
            self.msg['Subject'] = Header(emailSubject, "utf-8")

    def addBody(self, bodyType):
        """
        添加不同的邮件正文的实例
        1. body为字符串:(如)"这是一个邮件正文内容"
        2. body为html格式的字符串:(如)"<div><p>第一段</p><p>&nbsp;第二段</p></div>"
        3. body正文中包含有图片:
        """
        if bodyType == "string":
            body = "这是一个邮件正文内容"
            mimeText = MIMEText(body, "plain", "utf-8")
            self.msg.attach(mimeText)
        elif bodyType == "html":
            body = "<div><p>第一段</p><p>&nbsp;第二段</p></div>"
            mimeText = MIMEText(body, "html", "utf-8")
            self.msg.attach(mimeText)
        elif "image" in bodyType:
            imageFile = "E://log//test.png"
            imageId = os.path.split(imageFile)[1]
            # 添加内容
            body = '''
                    <p>测试图片为:</p>
                    <p><img src="cid:{imageId}"></p>
                    '''.format(imageId=imageId)
            mimeText = MIMEText(body, "html", "utf-8")
            self.msg.attach(mimeText)
            # 读取图片,并设置图片id用于邮件正文引用
            with open(imageFile, "rb") as fp:
                mimeImage = MIMEImage(fp.read())
            mimeImage.add_header("Content-ID", imageId)
            self.msg.attach(mimeImage)

        def addAttachment(self, attachmentName):
        """
        添加附件
        :return:
        """
        file = "E://log//test.txt"
        # file = "E://log//test.zip"
        # file = "E://log//test.png"
        filePath, fileName = os.path.split(file)
        print("fileName =", fileName)
        enclosure = MIMEText(open(file, 'rb').read(), 'base64', 'utf-8')
        enclosure['Content-Type'] = 'application/octet-stream'
        if attachmentName == "英文":
            enclosure['Content-Disposition'] = 'attachment; filename="%s"' % fileName
        elif attachmentName == "中文":
            enclosure.add_header("Content-Disposition", "attachment", filename=("gbk", "", fileName))
        self.msg.attach(enclosure)

2.2. 发送邮件
import smtplib
def SendEmail():
    """发送邮件"""
    # SMTP的服务器信息
    smtpHost = "smtp.exmail.qq.com"
    sslPort = 465
    senderAdr = "xx@xx.cn"
    senderPwd = "XXXX"
    # 创建SMTP对象
    smtpServer = smtplib.SMTP_SSL(smtpHost, sslPort)
    # # 设置debug模块
    # smtpServer.set_debuglevel(True)
    # 登录
    smtpServer.login(senderAdr, senderPwd)
    # 添加邮件内容
    toReceivers = ["a@xx.cn", "b@xx.cn"]
    ccReceivers = ["d@xx.cn", "e@xx.cn"]
    toAddrs = toReceivers + ccReceivers
    emailSubject = "这是个自动发送的邮件"
    emailContent = EmailContent(senderAdr, emailSubject, toReceivers, ccReceivers)
    emailContent.addBody("html")
    emailContent.addAttachment("英文")
    message = emailContent.msg
    # 发送
    smtpServer.sendmail(senderAdr, toAddrs, message.as_string())
    # 终止SMTP会话
    smtpServer.quit()

SendEmail()

二、读取邮件

  • 收取邮件使用POP3协议;
  • 解析邮件:需要将收取的邮件转化为email.message.Message对象,再使用email模块解析内容。

1. 读取邮件的实例

1.1. 获取某封邮件的对象
import poplib
from email.parser import Parser
"""POP的服务器信息"""
popHost = "pop.exmail.qq.com"
userAdr = "xx@xx.cn"
userPwd = "xxxxx"

""" 创建POP3对象,添加用户名和密码"""
pop3Server = poplib.POP3(popHost)
pop3Server.user(userAdr)
pop3Server.pass_(userPwd)

"""获取邮件数量和占用空间"""
messageCount, mailboxSize = pop3Server.stat()

"""获取邮件请求返回状态码、每封邮件的字节大小(b'第几封邮件 此邮件字节大小')、"""
response, msgNumOctets, octets = pop3Server.list()

""" 获取任意一封邮件的邮件对象【第一封邮件的编号为1,而不是0】"""
msgIndex = random.randint(1,messageCount)
print(msgIndex)
# 获取第msgIndex封邮件的信息
response, msgLines, octets = pop3Server.retr(msgIndex)
# msgLines中为该邮件的每行数据,先将内容连接成字符串,再转化为email.message.Message对象
msgLinesToStr = b"\r\n".join(msgLines).decode("utf8", "ignore")
messageObject = Parser().parsestr(msgLinesToStr)
print(messageObject)

""" 终止POP3服务"""
pop3Server.quit()
1.2. 解析邮件对象
1.2.1. 获取邮件日期
msgDate = messageObject["date"]
print(msgDate)
1.2.2. 获取邮件发件人实名、邮箱地址

获取邮件实名时,名称一般是加密的,此时就需要对头文件进行解码才可获取它的实际内容

from email.header import decode_header
def decodeMsgHeader(header):
    """
    解码头文件
    :param header: 需解码的内容
    :return:
    """
    value, charset = decode_header(header)[0]
    if charset:
        value = value.decode(charset)
    return value
from email.utils import parseaddr
senderContent = messageObject["From"]
# parseaddr()函数返回的是一个元组(realname, emailAddress)
senderRealName, senderAdr = parseaddr(senderContent) 
# 将加密的名称进行解码 
senderRealName = decodeMsgHeader(senderRealName)   
print(senderRealName)
print(senderAdr)
1.2.3. 获取邮件主题

获取的邮件的主题也是加密的,此时就需要对头文件进行解码才可获取它的实际内容

msgHeader = messageObject["Subject"]
# 对头文件进行解码
msgHeader = decodeMsgHeader(msgHeader )
print(msgHeader)
1.2.4. 获取邮件正文

一封邮件的正文内容,可能是由几部分构成,每部分的格式不同。

"""获取邮件正文内容"""
msgBodyContents = []
if messageObject.is_multipart():  # 判断邮件是否由多个部分构成
    messageParts = messageObject.get_payload()  # 获取邮件附载部分
    for messagePart in messageParts:
        bodyContent = decodeBody(messagePart)
        if bodyContent:
            msgBodyContents.append(bodyContent)
else:
    bodyContent = decodeBody(messageObject)
    if bodyContent:
        messageBodyContents.append(bodyContent)
print(msgBodyContents)
def decodeBody(msgPart):
    """
    解码内容
   :param msgPart: 邮件某部分
    """
    contentType = msgPart.get_content_type()  # 判断邮件内容的类型,text/html
    textContent = ""
    if contentType == 'text/plain' or contentType == 'text/html':
        content = msgPart.get_payload(decode=True)
        charset = msgPart.get_charset()
        if charset is None:
            contentType = msgPart.get('Content-Type', '').lower()
            position = contentType.find('charset=')
            if position >= 0:
                charset = contentType[position + 8:].strip()
        if charset:
            textContent = content.decode(charset)
    return textContent
1.2.5. 获取邮件附件

邮件附件名为中文时,需借助头文件解码方式进行解码,否则会为乱码。

messageAttachments = []
if messageObject.is_multipart():  #   判断邮件是否由多个部分构成
    messageParts = messageObject.get_payload()  # 获取邮件附载部分
    for messagePart in messageParts:
        name = messagePart.get_param("name")  # 名字存在,则表示此部分为附件
        if name:
            fileName = decodeMsgHeader(name)   # 解码
            messageAttachments.append(fileName)
else:
    name = messageObject.get_param("name")
    if name:
       fileName = decodeMsgHeader(name)   # 解码
       messageAttachments.append(fileName)
print(messageAttachments)

2. 读取邮件时遇到的问题

2.1. 提示“poplib.error_proto: line too long”

File "XXX/EmailInfo.py", line 22, in getMessageObject
    return parser.Parser().parsestr(b"\n".join(self.popServer.retr(i)[1]).decode("utf8", "ignore"))
  File "/usr/local/lib/python3.6/poplib.py", line 248, in retr
    return self._longcmd('RETR %s' % which)
  File "/usr/local/lib/python3.6/poplib.py", line 183, in _longcmd
    return self._getlongresp()
  File "/usr/local/lib/python3.6/poplib.py", line 168, in _getlongresp
    line, o = self._getline()
  File "/usr/local/lib/python3.6/poplib.py", line 130, in _getline
    raise error_proto('line too long')
poplib.error_proto: line too long

POP3对行长度做了限制,默认为_MAXLINE = 2048,故若是邮件超过此长度就会提示“poplib.error_proto: line too long”。
解决方案:在读取邮件代码中重新定义最大行长度,即给poplib._MAXLINE设置新值。

import poplib
poplib._MAXLINE=20480
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,198评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,663评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,985评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,673评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,994评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,399评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,717评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,407评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,112评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,371评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,891评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,255评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,881评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,010评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,764评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,412评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,299评论 2 260