Python应用开发学习:让DeepSeek写个下载电子邮件的小工具

一、前言

一周前,我发了一篇《Python应用开发学习:做一个邮件发送工具,实现带附件及延时发送》的日志,记录了我借助DeepSeek做了一个发送电子邮件的小工具,方便我在公司内部网络进行ip限制的情况下,能够通过QQ邮箱的stmp服务发送邮件。但还没解决在公司内网下载QQ、163、新浪等邮箱里的邮件的问题。这次我又把问题抛给DeepSeek,让它帮我写代码,效率高了很多。虽然在测试时出现了一些问题,最终目标只实现了一部分,但总比啥都不能做要强。

二、功能描述

我最初的设想是在之前开发的收件小工具上进行扩展,希望能下载QQ邮箱、163邮箱、新浪邮箱的邮件,并将邮件内容及其附件保存到本地。在开发过程中,通过DeepSeek还添加了收件选项,能够选择(设置)开始时间,下载邮件数量,只下载未读邮件,将已下载的邮件标记为已读。并可以指定邮件保存路径。

三、借助DeepSeek开发软件

我主要借助DeepSeek编写逻辑部分的代码,之前已经通过它完成了发送邮件的逻辑代码了。这次继续提问要求它写个下载邮件及其附件的代码。

DeepSeek给出了一段200多行的代码,用到了imaplib库和email库。运行代码,根据提示依次输入邮箱地址、授权码、下载目录、最大下载数、开始日期,之后执行下载程序,由程序自动从邮箱下载邮件,并将邮件保存到指定目录下。在目录下每个邮件又建有一个邮件目录,邮件目录下有邮件信息文件、邮件正文文件、附件。

DeepSeek不仅给出了代码,还给出了一些说明,看到这些说明对代码会更容易理解。另外,要使用这段代码,需要获取到QQ邮箱、163邮箱、新浪邮箱的imap服务的授权码。

之后我又提出了希望添加只下载未读邮件的功能,DeepSeek给下载邮件的函数添加了参数,并修改了搜索条件。

这段代码,我用QQ邮箱测试后,发现下载了未读邮件后不能将其标注为已读(如下图)。

而DeepSeek给出的注意事项里告诉了我“ 如果需要将邮件标记为已读,需要额外添加 mail.store(email_id, '+FLAGS', '\\Seen') 命令 ”。我直接问它怎么添加。

再次对新生成的代码进行测试,能够将已下载的邮件标记为已读了。

至此,下载邮件的逻辑代码部分基本完成了。

四、功能实现

我将新得到的代码与之前得到的发送邮件的代码进行整合,方在一个email_module.py文件里。再通过wxFormBuilder对之前制作的GUI进行修改,已适应当前的软件需求。最后通过pycharm完成代码的整合,得到如下的小工具。

在测试过程中,出现了几个问题。

1、QQ邮箱不能选择起始日期,否则,无法下载到任何的邮件。

(虽然搜到了82封邮件,但下载数为0)

2、163邮箱无法正常下载到邮件。

返回的错误信息是:[b'SELECT Unsafe Login. Please contact kefu@188.com for help']

我将问题反馈给DeepSeek,它的回复大致意思是:

这通常是由于 163 邮箱的安全机制导致的,特别是当使用 IMAP 登录时。虽然给出了一些建议,但似乎,它也无法解决这个问题。

最后对新浪邮箱的测试比较正常,顺利下载到了邮件。

(在指定的下载路径下会看到多个子文件夹,每个文件夹就是一封邮件。)

(每个子文件夹下有三个文件分别是信息、正文、附件)

这个软件存在的问题似乎不是DeepSeek给出的代码的问题,而是QQ邮箱或163邮箱的设置问题。当前,DeepSeek也没有给出有效的方案。只能算是部分成功了,先这样吧。

五、代码展示

最后放上DeepSeek生成的代码供参考,此段代码运行后执行main函数,提示用户输入邮箱地址、授权码、下载路径、最大下载数量、开始日期等关键参数,程序会调用download_emails函数自动下载邮件,并保存到指定的路径下。

# -*- coding: UTF-8 -*-
import imaplib
import email
import os
import re
from email.header import decode_header
from datetime import datetime
import time

def get_email_service_config(email_address):
    """根据邮箱地址返回对应的IMAP服务器配置"""
    if '@qq.com' in email_address:
        return {
            'imap_server': 'imap.qq.com',
            'port': 993,
            'use_ssl': True
        }
    elif '@163.com' in email_address:
        return {
            'imap_server': 'imap.163.com',
            'port': 993,
            'use_ssl': True
        }
    elif '@sina.com' in email_address:
        return {
            'imap_server': 'imap.sina.com',
            'port': 993,
            'use_ssl': True
        }
    else:
        raise ValueError(f"不支持的邮箱类型: {email_address}")

def clean_filename(filename):
    """清理文件名,移除非法字符"""
    if not filename:
        return "unknown"
    
    # 尝试解码编码过的文件名
    if filename.startswith('=?') and filename.endswith('?='):
        decoded = decode_header(filename)
        if decoded and decoded[0][0]:
            filename = decoded[0][0]
            if isinstance(filename, bytes):
                filename = filename.decode(decoded[0][1] or 'utf-8', errors='ignore')
    
    # 移除特殊字符
    cleaned = re.sub(r'[\\/*?:"<>|]', '', filename)
    # 截断过长的文件名
    return cleaned[:100] if cleaned else "attachment"

def download_emails(email_address, password, output_dir, max_emails=10, since_date=None, only_unread=False, mark_as_read=False):
    """
    从邮箱下载邮件及其附件
    
    参数:
        email_address: 邮箱地址
        password: 邮箱授权码
        output_dir: 下载文件保存目录
        max_emails: 最多下载的邮件数量
        since_date: 只下载此日期之后的邮件 (格式: "YYYY-MM-DD")
        only_unread: 是否只下载未读邮件
        mark_as_read: 是否将下载的邮件标记为已读
    """
    # 验证参数
    if not email_address or not password:
        print("错误: 邮箱地址和授权码不能为空")
        return
    
    if not os.path.exists(output_dir):
        os.makedirs(output_dir)
    
    # 获取邮箱配置
    try:
        config = get_email_service_config(email_address)
    except ValueError as e:
        print(e)
        return
    
    # 连接邮箱服务器
    try:
        if config['use_ssl']:
            mail = imaplib.IMAP4_SSL(config['imap_server'], config['port'])
        else:
            mail = imaplib.IMAP4(config['imap_server'], config['port'])
        
        mail.login(email_address, password)
        print(f"成功登录到 {email_address}")
    except Exception as e:
        print(f"登录失败: {e}")
        return
    
    # 选择收件箱 - 修复选择状态问题
    try:
        status, data = mail.select('inbox')
        if status != 'OK':
            print(f"选择收件箱失败: {data}")
            mail.logout()
            return
        print(f"收件箱选择成功,共有 {data[0].decode()} 封邮件")
    except Exception as e:
        print(f"选择收件箱失败: {e}")
        mail.logout()
        return
    
    # 构建搜索条件
    search_criteria = 'ALL'
    criteria_parts = []
    
    # 添加日期筛选
    if since_date:
        try:
            # 转换日期格式为 IMAP 格式 (dd-MMM-yyyy)
            dt = datetime.strptime(since_date, "%Y-%m-%d")
            imap_date = dt.strftime("%d-%b-%Y")
            criteria_parts.append(f'SINCE "{imap_date}"')
        except ValueError:
            print(f"警告: 无效的日期格式 {since_date}, 忽略日期筛选")
    
    # 添加未读邮件筛选
    if only_unread:
        criteria_parts.append('UNSEEN')
    
    # 组合搜索条件
    if criteria_parts:
        search_criteria = f'({" ".join(criteria_parts)})'
    
    print(f"搜索条件: {search_criteria}")
    
    # 搜索邮件 - 添加状态检查
    try:
        status, messages = mail.search(None, search_criteria)
        if status != 'OK':
            print(f"搜索邮件失败: {messages}")
            mail.logout()
            return
        
        # 获取邮件ID列表
        email_ids = messages[0].split()
        total_emails = len(email_ids)
        print(f"找到 {total_emails} 封符合条件的邮件")
        
        # 限制下载数量
        if max_emails > 0 and total_emails > max_emails:
            email_ids = email_ids[-max_emails:]  # 下载最新的N封邮件
            print(f"将下载最新的 {max_emails} 封邮件")
    except Exception as e:
        print(f"邮件搜索出错: {e}")
        mail.logout()
        return
    
    # 下载邮件
    downloaded_count = 0
    for i, email_id in enumerate(reversed(email_ids), 1):  # 从最新邮件开始
        try:
            # 获取邮件内容 - 添加状态检查
            status, msg_data = mail.fetch(email_id, '(RFC822)')
            if status != 'OK':
                print(f"获取邮件 {email_id} 失败: {msg_data}")
                continue
            
            # 解析邮件
            msg = email.message_from_bytes(msg_data[0][1])
            
            # 创建邮件保存目录
            email_dir = os.path.join(output_dir, f"email_{email_id.decode()}")
            if not os.path.exists(email_dir):
                os.makedirs(email_dir)
            
            # 保存邮件基本信息
            subject, encoding = decode_header(msg["Subject"])[0] if msg["Subject"] else ("无主题", None)
            if isinstance(subject, bytes):
                subject = subject.decode(encoding or 'utf-8', errors='ignore')
            
            from_email = msg.get("From", "未知发件人")
            date = msg.get("Date", "未知日期")
            
            info_file = os.path.join(email_dir, "email_info.txt")
            with open(info_file, 'w', encoding='utf-8') as f:
                f.write(f"主题: {subject}\n")
                f.write(f"发件人: {from_email}\n")
                f.write(f"日期: {date}\n")
                f.write(f"收件人: {msg.get('To', '')}\n")
                f.write(f"邮件ID: {email_id.decode()}\n")
                f.write(f"是否未读: {'是' if only_unread else '未知'}\n")
                f.write(f"是否标记为已读: {'是' if mark_as_read else '否'}\n\n")
            
            # 保存邮件正文
            body_file = os.path.join(email_dir, "email_body.txt")
            body_content = ""
            
            # 解析邮件内容
            for part in msg.walk():
                content_type = part.get_content_type()
                content_disposition = str(part.get("Content-Disposition"))
                
                # 保存附件
                if "attachment" in content_disposition:
                    filename = part.get_filename()
                    if filename:
                        filename = clean_filename(filename)
                        attachment_path = os.path.join(email_dir, filename)
                        with open(attachment_path, "wb") as f:
                            f.write(part.get_payload(decode=True))
                        print(f"  - 附件已保存: {filename}")
                
                # 提取文本内容
                elif content_type in ["text/plain", "text/html"]:
                    try:
                        payload = part.get_payload(decode=True)
                        charset = part.get_content_charset() or 'utf-8'
                        text = payload.decode(charset, errors='replace')
                        
                        if content_type == "text/plain":
                            body_content += text + "\n"
                        elif content_type == "text/html":
                            # 保存HTML内容到单独文件
                            html_path = os.path.join(email_dir, "email_body.html")
                            with open(html_path, "w", encoding='utf-8') as f:
                                f.write(text)
                    except Exception as e:
                        print(f"解析邮件内容出错: {e}")
            
            # 保存纯文本正文
            if body_content:
                with open(body_file, "w", encoding='utf-8') as f:
                    f.write(body_content)
            
            # 标记邮件为已读
            if mark_as_read:
                try:
                    result = mail.store(email_id, '+FLAGS', '\\Seen')
                    if result[0] == 'OK':
                        print(f"  - 邮件已标记为已读")
                    else:
                        print(f"  - 标记为已读失败: {result[1]}")
                except Exception as e:
                    print(f"  - 标记为已读失败: {e}")
            
            downloaded_count += 1
            print(f"({i}/{len(email_ids)}) 邮件已保存: {subject}")
            
        except Exception as e:
            print(f"处理邮件 {email_id} 时出错: {e}")
    
    mail.logout()
    print(f"\n下载完成! 共保存了 {downloaded_count} 封邮件到目录: {output_dir}")

def main():
    """主函数,提供用户交互界面"""
    print("\n===== 邮箱邮件下载工具 =====")
    
    # 用户输入
    email_address = input("请输入邮箱地址: ").strip()
    password = input("请输入邮箱授权码: ").strip()
    
    # 设置输出目录
    default_dir = f"emails_{int(time.time())}"
    output_dir = input(f"请输入保存目录 (默认为 {default_dir}): ").strip()
    if not output_dir:
        output_dir = default_dir
    
    # 设置下载选项
    max_emails = 10
    try:
        max_input = input("请输入最多下载邮件数 (默认为10): ").strip()
        if max_input:
            max_emails = max(1, min(100, int(max_input)))
    except:
        print("输入无效,使用默认值10")
    
    since_date = None
    date_input = input("请输入起始日期 (YYYY-MM-DD, 默认为所有邮件): ").strip()
    if date_input:
        try:
            datetime.strptime(date_input, "%Y-%m-%d")
            since_date = date_input
        except ValueError:
            print("日期格式无效,将下载所有邮件")
    
    # 添加只下载未读邮件选项
    only_unread = False
    unread_input = input("是否只下载未读邮件? (y/n, 默认为n): ").strip().lower()
    if unread_input == 'y' or unread_input == 'yes':
        only_unread = True
        print("将只下载未读邮件")
    
    # 添加标记为已读选项
    mark_as_read = False
    read_input = input("是否将下载的邮件标记为已读? (y/n, 默认为n): ").strip().lower()
    if read_input == 'y' or read_input == 'yes':
        mark_as_read = True
        print("下载的邮件将被标记为已读")
    
    # 开始下载
    print("\n开始下载邮件...")
    download_emails(
        email_address=email_address,
        password=password,
        output_dir=output_dir,
        max_emails=max_emails,
        since_date=since_date,
        only_unread=only_unread,
        mark_as_read=mark_as_read
    )

if __name__ == "__main__":
    main()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

武陵悭臾

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值