
简介一套基于朴素贝叶斯算法的垃圾邮件过滤系统完整源码面向计算机科学、信息安全、数据科学与大数据技术、人工智能等专业学生可直接用于毕业设计、课程设计、大作业或初期项目立项。项目以Python实现涵盖选择训练集训练、控制台实时查看每封邮件判断过程、精确度评估、屏蔽词自定义及邮件号测试等完整流程提供test-ham与test-spam目录结构并预留get_mail.py接口可接入个人邮箱进行联网过滤测试。资源包共407个文件以20个Python源码与txt操作说明、md文档为核心其余为训练集邮件样本压缩包仅995KB体量轻便、易部署。已有481人学习下载。对需要快速搭建邮件过滤原型或研究朴素贝叶斯文本分类的开发者这份资源提供了可直接运行、便于二次扩展的完整方案。1. 毕设答辩前能演示的朴素贝叶斯垃圾邮件过滤系统先从五分钟跑通训练开始做毕设最怕的不是算法难而是答辩演示时分类器当场翻车。这套基于 Python 实现朴素贝叶斯的垃圾邮件过滤系统是我见过少数“拿到就能演示”的完整工程带 GUI 操作界面选训练集、看精确度、查屏蔽词、输邮件号判别控制台还会把每封邮件的判断过程逐行打出来。它面向计算机、数据科学、人工智能等专业的学生可直接作为毕设、课程设计、大作业或初期项目立项演示。价值在于不是一个孤立的分类脚本而是把训练、评估、自定义词表、在线收件串成了一个闭环。下面从原理讲到踩坑按我拆项目时执行的顺序来写。2. 朴素贝叶斯为什么总被拿来过滤垃圾邮件三个必懂参数与特征边界2.1 从贝叶斯公式到“垃圾、正常”二分类邮件过滤本质上是一个二分类问题。预测一封新邮件时我们计算的是两个条件概率P(垃圾 | 邮件) P(邮件 | 垃圾) × P(垃圾) / P(邮件) P(正常 | 邮件) P(邮件 | 正常) × P(正常) / P(邮件)两个式子分母相同比较时可以直接丢掉。P(垃圾) 和 P(正常) 是训练集中垃圾邮件与正常邮件占比得出的先验概率。而 P(邮件 | 垃圾) 在朴素贝叶斯里被拆成所有特征词条件概率的乘积因为这里“朴素”指的就是词与词之间互相独立。到了代码层面第一个坑是浮点下溢。一封正常邮件至少几十个词每个词的概率往往在 0.001 量级几十个连乘后结果直接变成 0.0两类得分比不出来。成熟实现都会转成 log 加法先对每个概率取对数再累加最后比大小。你在这个项目的控制台里看到类似“0.0021 / -0.0008”的日志本质上就是在逐词累加对数得分。# nb_classifier.py —— 朴素贝叶斯训练与预测的核心 import math from collections import defaultdict # 垃圾邮件与正常邮件的词频表 spam_word defaultdict(int) ham_word defaultdict(int) spam_total 0 # 垃圾邮件总数用于计算先验 ham_total 0 alpha 1.0 # 拉普拉斯平滑参数默认 1.0 def train(email_words, label): global spam_total, ham_total if label 1: # 1 表示垃圾 spam_total 1 for w in email_words: spam_word[w] 1 else: # 0 表示正常 ham_total 1 for w in email_words: ham_word[w] 1 def predict(email_words): vocab set(spam_word) | set(ham_word) # 全部去重词表 denom_spam sum(spam_word.values()) len(vocab) denom_ham sum(ham_word.values()) len(vocab) # 用 log 累加替代连乘防止浮点下溢 log_spam math.log(spam_total / (spam_total ham_total)) log_ham math.log(ham_total / (spam_total ham_total)) for w in email_words: # 分子加 1 就是拉普拉斯平滑未登录词不再是 0 概率 log_spam math.log((spam_word[w] alpha) / denom_spam) log_ham math.log((ham_word[w] alpha) / denom_ham) return 1 if log_spam log_ham else 0这段代码的核心逻辑就是训练时按类别累加词频预测时把每个词的贡献累加进两个对数得分。这里有两个参数要留意。第一是alpha默认 1.0 对应 sklearn 中MultinomialNB(alpha1.0)如果改成 0.1未登录词的概率会变得非常小模型对陌生特征更敏感改成 2.0 则所有概率被拉向均值分类边界更钝。第二是vocab的大小分母里的len(vocab)必须用训练集整体去重后的词表不能用某一类单独的词表否则平滑后两类的分母不可比。spam_total / (spam_total ham_total)是先验概率数据不平衡时它会明显偏向样本多的一方。这也是后面踩坑章节里“全判成垃圾”的一个源头。2.2 拉普拉斯平滑一个参数决定新词是否“翻车”如果某封测试邮件里出现训练集没见过的词而这个词恰好是特征直接按朴素贝叶斯公式计算P(词|垃圾)和P(词|正常)都是 0整封邮件的概率会被乘成 0分类器直接失效界面上表现就是“怎么测都是同一类”。解决方法是拉普拉斯平滑也叫加一平滑。分子加alpha分母加词表大小 × alpha让每个词在每个类别下都有一个非零概率P(词 | 垃圾) (垃圾邮件中该词出现次数 alpha) / (垃圾邮件总词数 词表大小 × alpha)情况不平滑时的概率alpha1.0 平滑后某词训练期出现过 1 次1 / N(11) / (N V)某词从未出现01 / (N V)某词出现 100 次100 / N(1001) / (N V)V 是词表大小N 是当前类别邮件总词数。从这个表能看出平滑对高频词影响很小对低频词和未登录词的影响最明显。你在实践里如果发现某封邮件因为一个陌生词被错误分类不要怀疑算法先看alpha是不是被改成了接近 0 的数值。提示调试这类项目时优先检查平滑参数和停用词表而不要一上来重写分类逻辑。绝大多数分类异常都出在数据预处理而不是贝叶斯公式本身。2.3 中英文分词与停用词表的边界上一节代码的email_words是已经切好的词列表。这一层直接决定特征质量。常见做法是中文用 jieba 分词英文按正则把字母数字串切开再把大写折成小写。如果再讲究一点会把数字、URL、邮箱地址各归为一个特殊 token比如把https://...统一替换成__URL__避免每个链接都成为独立特征。提交课程设计时可以用默认的 jieba 分词但如果想提高区分度建议在分词后加一个维度把中文 2-gram 也带上。比如“点击领取”这个词组拆成“点击”和“领取”之后特征信息比整词组更稀疏这时候 2-gram 会保留“点击领取”的共现关系。停用词表要小心处理。以下是一份常见的中文停用词文件格式# stopwords_zh.txt —— 一行一个词UTF-8 无 BOM 的 了 和 是 在 我 你 也 就 都停用词表的边界在于删得太狠会把强特征词一起删掉。我拆过几个类似项目有人为追求“干净”把“点击”“领取”“现金”也放进了停用词表结果垃圾邮件识别率直线下降。这些词恰恰是垃圾邮件的高频信号。所以停用词表只删结构助词和虚词不要删实词。宁可让词表小一点也不要让强特征被误杀。3. 项目结构与数据集组织test-ham、test-ham3、test-spam 怎么摆才不翻车3.1 根目录下的文件与文件夹清单这个项目不是单文件脚本而是按“训练样本目录 GUI 主程序 在线收件模块”组织在一起的完整工程。核心文件和作用如下路径 / 文件作用使用时机get_mail.py在线收件模块想测试自己邮箱里的真实邮件时用到GUI 主程序界面入口训练、精确度、屏蔽词、单封测试都在这里操作test-ham正常邮件样本目录中等训练规模放类似 test-ham1 格式的纯文本邮件test-ham3正常邮件样本目录大量训练规模数量更多test-spam垃圾邮件样本目录与 test-ham 对应放垃圾样本拿到的压缩包解压后第一件事不是运行而是检查test-ham里是否真的有文件。项目操作说明里特别强调“在 test-ham 中放置类似 test-ham1 中的邮件格式的文件达到中等训练规模”言下之意是原始压缩包可能只带少量范例样本真正能跑到 80% 以上精确度需要你自己往这三个目录里补数据。3.2 邮件样本格式与命名规则test-ham1 这个文件就是单封邮件的文本样例。训练脚本扫描目录时通常按后缀.txt读取整个文件内容因此你的样本必须保持纯文本格式不要用 Word 文档改扩展名也不要在正文里塞 HTML 源码。命名上我建议统一成“数字编号.txt”比如ham_001.txt、spam_001.txt。拆项目时遇到过有同学把文件名改成“中奖通知邮件.txt”文件读写时中文名加空格在某些编码环境下直接报错程序把文件当作 0 字节跳过训练集等于白放。如果不想在命名上费心思就全部用 ASCII 文件名。编码是另一个隐蔽问题。邮件文本可能是 UTF-8也可能是 GBK如果训练脚本只用一种编码读取另一半文件会乱码。拿到项目后先在train函数入口把读到的文本打印前 100 字快速确认编码是否正常。3.3 训练规模与类别均衡操作说明里把样本规模分成两档test-ham 中等规模、test-ham3 大量规模。按我的经验中等规模大约每类 300 到 500 封大量规模建议每类 1500 封以上。低于这个量级精确度波动会很大尤其是垃圾邮件种类一多单个类别样本太少时特征稀疏的问题立刻暴露。类别均衡比总体数量更重要。垃圾邮件 2000 封、正常邮件 300 封训练出的分类器会有明显的偏置预测阶段大概率把正常邮件也判成垃圾。一个简单做法是复制少量样本把两类凑平但更好的做法是去真实邮箱里多收集被拦截邮件来补足垃圾样本。同时注意训练集和测试集要分开。这个项目里点击“精确度”时如果它读的目录刚好是训练目录结果会虚高到 95% 以上因为模型在用自己的训练数据回判。我一般会把原始邮件先按 8:2 切开用 8 成做训练2 成做精确度评估这样得到的数字才有参考意义。第 6 章会给一个自动分层脚本直接复用即可。4. 从训练到单封测试的完整操作流程五步走加 get_mail.py 邮箱配置4.1 选择训练集先训练再关闭选择窗口项目操作说明的第一步写得很明确一切开始之前先进行训练点击“选择训练集开始训练”。这里有一个容易忽略的动作顺序——选择好训练集后要关闭选择训练集的窗口再继续后续操作。为什么要把“关闭窗口”单独提出来因为这个项目的训练是在事件回调里触发的选择窗口回传目录路径后如果窗口还开着程序可能处于等待状态后续精确度、屏蔽词等按钮的事件不会正常响应。正确的顺序是启动 GUI点击“选择训练集开始训练”在文件对话框里选中test-ham或混合样本目录等待控制台输出训练完成的提示手动关闭选择训练集的窗口再进行下一步操作。训练完成后不要急着改目录里的文件。训练阶段读进内存的是文件快照之后你往目录里加文件当前模型不会感知必须重新走一遍训练流程。4.2 精确度按钮它在评估什么、为什么可能慢点击“精确度”按钮后程序会对测试目录里的邮件逐封预测并把每封邮件的判断过程打印到控制台这几行日志就是前面 2.1 节说的对数得分累加过程。因为要对每个文件做读入、分词、特征转换、概率累加再叠加 print 输出整体速度会比想象中慢。如果按下去十几分钟都没有响应先检查测试目录里是不是混入了附件、图片或超大文本文件。训练脚本如果对目录里所有文件统一按文本解析遇到二进制文件会把整段乱码读进来分词耗时暴增。先把测试目录清到只剩文本邮件再重新点按钮。精确度计算的是测试目录上的正确率不是训练集回判。如果结果高得不正常比如 99% 以上先怀疑测试目录选成了训练目录。这个数字也不是越高越好垃圾邮件场景里更该关注的是“正常邮件被误判为垃圾”的比例因为误杀一封正常邮件用户感知比漏掉一封垃圾邮件更强烈。4.3 屏蔽词内置两种词表与自定义格式点击“屏蔽词”按钮后可以在内置词表和自定义词表之间切换。内置的两组词表覆盖了两种典型垃圾场景一组偏营销推广包含“优惠、秒杀、扫码”这类词另一组偏欺诈链接包含“中奖、加QQ、验证码”这类词。自定义屏蔽词是文本文件格式一行一个词# custom_words.txt —— 自定义屏蔽词表UTF-8 无 BOM 中奖 加QQ 点击领取 贷款 刷单参数上有三个注意点。第一编码必须是无 BOM 的 UTF-8Windows 记事本默认存的 UTF-8 带 BOM程序按 UTF-8 读取时 BOM 字符会被留在第一个词前面匹配直接失败。第二一行只能放一个词不要用逗号分隔否则程序很可能按整行匹配。第三屏蔽词是“命中即加强垃圾判定”还是“命中即强制判垃圾”取决于源码实现我建议先用内置词表对比测试再决定是否自定义。4.4 输入邮件号测试单封与 get_mail.py 在线收件“输入想要测试的邮件号即可判断”这里的邮件号对应数据集中单封邮件的编号。测试前先确认该编号的文件确实存在否则程序会报“文件不存在”之类的错误。如果想把训练好的模型用到自己邮箱里的真实邮件上就需要 get_mail.py 这个模块。常见实现是 IMAP 协议收件配置项大致如下# get_mail.py —— 在线收件配置模板 IMAP_SERVER imap.qq.com # 邮箱服务商地址163/outlook 各不同 IMAP_PORT 993 # SSL 加密端口不要用 143 明文端口 MAIL_USER yournameqq.com # 改成你自己的邮箱地址 MAIL_AUTH your_auth_code # 授权码不是登录密码 USE_SSL True # 固定走 SSL 加密连接邮箱服务商一般都要求先在网页设置里开启 IMAP 服务再生成一个独立授权码登录密码本身通常不允许第三方客户端直接使用。配置好后程序会拉取收件箱里的邮件进入训练好的分类流程判断。这里要强调“需要联网不是一句空话”。IMAP 连接必须能访问到邮箱服务器校园网或公司内网经常有端口策略限制993 端口连不通时先换手机热点验证网络再排查代码配置。如果网络确实不可用就用本地编号文件测试不要硬等在线收件结果。5. 避坑清单训练顺序、授权码与样本均衡的五个常见翻车点5.1 现象一点击训练后界面没有任何反应现象点击“选择训练集开始训练”文件选择器弹出来又关掉但控制台没有任何输出界面像卡死了一样。原因最常见的是没有按操作说明关闭选择训练集的窗口。窗口未关闭状态下训练线程还在等待或持有目录句柄后续事件进不来。另一个原因是选择的目录为空或者目录里文件编码异常训练逻辑直接卡在读取阶段。解决严格按“选目录 → 等待训练完成 → 关闭选择窗口”的顺序操作。如果已经卡住关闭整个程序重开先选一个小目录验证流程再放大目录训练。5.2 现象二点击精确度后等了十几分钟没有输出现象精确度按钮点下去界面无响应控制台也不打印邮件判断过程持续很长时间。原因测试目录里混入了非文本文件或超大文件。程序把所有文件按文本解析遇到二进制内容后分词时间急剧增加。还有一个原因是精确度逻辑是单线程的遍历文件和打印日志串行执行样本量大了之后耗时成倍增长。解决把测试目录拆成小批量先放 200 封以内验证流程确认每封邮件都能在两秒内输出判断日志再逐步扩大样本量。不要一次性丢几千个文件进去。5.3 现象三get_mail.py 一直连接超时或认证失败现象运行在线收件模块报 timeout 或者 authentication failed邮箱邮件拉不下来。原因三个常见原因。第一邮箱的 IMAP 服务没有在网页设置里开启第二MAIL_AUTH填成了登录密码而不是授权码第三IMAP_PORT配成了 143 明文端口服务商拒绝明文登录。解决去邮箱设置页开启 IMAP 服务并生成授权码确认端口是 993USE_SSL保持 True。如果网络环境有端口限制先用手机热点确认能连上服务器再回到原网络排查。5.4 现象四测试结果明显偏向某一类现象所有邮件都判成垃圾或者所有邮件都判成正常精确度数字异常偏高或偏低。原因训练集里两类样本数量严重不均先验概率偏向样本多的一侧。另一个原因是“精确度”测试目录被误选成了训练目录模型在自己做过的题上考试分数自然虚高。解决统计test-ham和test-spam的文件数把两类样本调整到接近 5:5 再重新训练。同时确认精确度读的是独立的测试目录。想快速验证可以复制少数样本凑平类别再跑一次训练对比。5.5 现象五自定义屏蔽词没有生效现象在自定义词表里加了“刷单”“贷款”测试时这些词出现了但邮件还是被判成正常。原因词表文件编码带了 BOM第一行第一个词前面多了不可见字符或者一行放了多个词程序按整行匹配失败再或者自定义词表的文件名和程序内置的默认文件名不一致程序加载的还是内置词表。解决用 VS Code 或 Notepad 把词表另存为 UTF-8 无 BOM 格式确认一行一个词、无标点、无多余空格。然后在程序里打印加载的词表内容确认自定义词真的进了内存再重新测试。6. 进阶技巧精确度自查加一个自动分层脚本接手就能二次开发6.1 数据集自动分层脚本拿到这个项目后我最先做的事情不是改算法而是写一个脚本把原始邮件统一按 8:2 切开训练归训练测试归测试两类样本保持均衡。这个脚本直接决定后面的毕设结果有没有说服力。# split_mail.py —— 按比例切分邮件数据集保证类别均衡 import os import random import shutil def split_mail_dir(src_dir, train_out, test_out, ratio0.8, seed42): files [f for f in os.listdir(src_dir) if f.lower().endswith(.txt)] random.seed(seed) # 固定随机种子每次运行结果可复现 random.shuffle(files) split_at int(len(files) * ratio) for f in files[:split_at]: shutil.copy(os.path.join(src_dir, f), os.path.join(train_out, f)) for f in files[split_at:]: shutil.copy(os.path.join(src_dir, f), os.path.join(test_out, f)) # 对正常邮件目录和垃圾邮件目录分别执行 split_mail_dir(raw_ham, train_ham, test_ham, ratio0.8) split_mail_dir(raw_spam, train_spam, test_spam, ratio0.8)ratio0.8表示 80% 做训练、20% 做测试如果样本总量小可以改成 0.7。seed42是固定随机种子同一批数据在任何机器上运行切分结果都一样这在答辩演示时很重要——昨天 85%今天换个机器变成 78%评审会直接怀疑实验不可控。切分完先用 Excel 或文件管理器统计四个目录的文件数确认两类样本接近均衡再开始训练。从那以后我每次拿到分类器项目都强制先走一遍这套检查顺序先做小样本训练确认路径和编码再配授权码验证在线收件最后才动算法参数。这套朴素贝叶斯系统本身不复杂真正决定最终效果的是数据组织得整不整。你先用分层脚本把数据整理干净再点一次精确度会看到比在原始文件夹上乱点稳定得多的结果。希望帮到你。本文还有配套的精品资源点击获取