Cycoe@Home

利用 Python 完成自动化群发邮件

最近我们 Campus TMC 要过九周年生日,因此要给以前的老会员发邮件邀请函。本来打算一 个一个手动发,后来一看有将近 100 个。本着 Coding for everything 的准则,尝试用 Python 来解决。

Python 中已经实现了 stmp 的相关接口在 smtplib 模块中,邮件结构相关的接口在 email 模块中。

1. 生成通讯录

在批量发送邮件之前,一定要有一份结构化的通讯录,不管是用 list 也好,用 dict 存储也好。此处,我是使用 xlrd 模块从 Excel 表格中读取数据,并将其存储在 dict 中。对于邮件来说,收件人的名字可能会出现重名,但邮件地址应该是唯一的,因此选用邮件地址作为字典的键。

def read_contacts(contact_file):
    """ 讀取聯繫人
    """
    contacts = {}

    data = xlrd.open_workbook(contact_file)
    sheet = data.sheet_by_index(0)

    for row in range(sheet.nrows):
        email = sheet.cell(row, 6).value.strip()
        if email == '':
            continue

        if email.find('/'):
            emails = email.split('/')
            for email in emails:
                contacts[email] = {}
                contacts[email][ZH] = sheet.cell(row, 3).value.strip()
                contacts[email][EN] = sheet.cell(row, 1).value.strip()
        else:
            contacts[email] = {}
            contacts[email][ZH] = sheet.cell(row, 3).value.strip()
            contacts[email][EN] = sheet.cell(row, 1).value.strip()

    del sheet
    del data
    return contacts

生成的字典结构为 {key_address: {'zh': value_zh_name, 'en': value_en_name}, ...}。

2. 定义邮件内容

第一步是生成邮件对象,代码示例如下:

from email.header import Header
from email.mime.multipart import MIMEMultipart
from email.mime.image import MIMEImage
from email.utils import formataddr

# 定义邮件的主题与内容
SUBJECT = 'My best friend {:name}, I sincerely invite you to participate our anniversary activity'
# 邮件内容支持 HTML
CONTENT = """
My best friend {:name},
<p>I sincerely invite you to participate our anniversary activity.</p>
<p>Here is our post.</p>
<div>
<div align="center"><img src="cid:post"></div>
"""

# 发件人的信息
SENDER_NAME = 'Cycoe'
SENDER_ADDR = 'cycoe@163.com'

contacts = read_contacts(PATH_OF_CONTACTS)

for email in contacts.keys():
    name = contacts[email][EN]
    # 生成邮件对象
    message = MIMEMultipart()
    # 设置主题、发信人、收信人
    message['Subject'] = Header(SUBJECT.format(name))
    message['From'] = formataddr((SENDER_NAME, SENDER_ADDR))
    message['To'] = formataddr((name, email))
    # 内容有点特殊,对于 MIMEMultipart 类型的邮件,
    # 内容是作为 attachment 添加到 message 中
    content = MIMEText(CONTENT.format(name), _subtype='html', _charset='utf-8')
    message.attach(content)

    # 加载附件,POST_PATH 是附件在本机上的路径
    with open(POST_PATH, 'rb') as fp:
        # 此处的 filename 参数是附件在邮件中显示的名字
        post = MIMEImage(fp.read(), _subtype='png', filename='post.png')
        post.add_header('Content-Disposition', 'attachment', filename='post.png')
        # 该文件头非常重要,Content-ID 一定要对应上面内容中的 cid
        # 这样图片就会被引用到正文中
        post.add_header('Content-ID', '<post>')
        message.attach(post)

3. 发送邮件

Python 中使用 smtplib 来处理 smtp 协议,示例代码如下:

import smtplib

HOST = 'smtp.163.com'
PORT = 25
SENDER_ADDR = 'cycoe@163.com'
PASSWORD = '*********'


def send_mail(message, email):
    server = smtplib.SMTP()
    # 连接服务器
    try:
        server.connect(HOST, PORT)
    except smtplib.SMTPConnectError as e:
        print(e)
        return False

    # 登陆
    try:
        server.login(SENDER, PASSWORD)
    except smtplib.SMTPAuthenticationError as e:
        print(e)
        return False

    success = False
    # 发送邮件
    try:
        server.sendmail(SENDER_ADDR, [email], msg.as_string())
        print('郵件發送成功!')
        success = True
    except smtplib.SMTPException as e:
        print('郵件發送失敗!')
    finally:
        del message
        server.quit()

    return success

此处有一点需要注意,不要 connect 一次服务器循环发多封邮件,而是每次 connect 发送一封邮件就 quit 重新 connect。否则会被网易服务器当作垃圾邮件而连接错误。

Author: Cycoe (cycoejoo@163.com)
Date: <2020-03-30 Mon 13:22>
Generator: Emacs 29.1 (Org mode 9.6.6)
Built: <2024-01-27 Sat 21:20>