
简介这套AI智能联系人管理毕业设计资源基于Python与人工智能技术实现联系人添加、编辑、删除、智能检索与推荐等功能适合计算机相关专业毕业设计参考及Python AI应用入门学习者。压缩包共50个文件包含7个py源码、10个pyc编译文件、6个html页面、8个ui界面设计、2个doc开发文档说明、2个xlsx数据文件、2个dat与2个txt数据配置以及exe可执行程序和jpg/png图标素材等整体约141MB涵盖开发文档、源程序、可执行程序与界面模板。目前已有612人学习。通过源码可了解NLP与机器学习如何落地到联系人管理场景文档和UI文件帮助理解系统架构与交互设计可执行程序便于直接体验对学习Python编程、AI算法集成及软件工程实践具有切实参考价值。1. 你能拿到一份什么样的 AI 智能联系人管理程序从“能跑”到“敢答辩”答辩前夜还在用 CtrlF 在联系人表格里翻备注这种体验我不止一次见过。这份 AI 智能联系人管理程序 python 毕业设计源码正是把“联系人管理 轻量 AI 能力”打包成一个能直接运行的桌面程序本地录入、模糊搜索、自动标签、语音备注转写外加一份开发文档。它不依赖云端服务所有数据落在本地文件里演示时断网也不影响。适合两类人一是要交课程设计/毕业设计、需要完整可演示系统的在校同学二是想照着真实项目学 Python GUI 和文本处理的从业者。拿到手之后先读开发文档再跑起来你会发现它的代码量不大但链路完整。2. 技术选型与数据模型为什么是 Python SQLite Tkinter表结构怎么定2.1 技术栈取舍四个选择都是为了让答辩现场稳得住这个项目的第一原则是“答辩现场能讲清、能演示、能兜底”所以技术栈选得很保守不追新。整份资源的核心组合见下表模块选型为什么这样选GUI 层Tkinter ttkPython 自带现场不依赖外网装库ttk 提供 Treeview 等表格控件数据层SQLite3单文件保存全部数据换机器把 .db 文件拷走就行模糊匹配difflib标准库相似度算法可以直接讲给评委听中文分词jieba轻量、准确率高用来做自动分组的可解释性支撑语音备注speech_recognition 本地模型麦克风输入尽量离线识别防止演示现场网络波动程序入口main.py从入口到 UI 再到数据库目录职责清晰很多人会问为什么不用 Flask 或者 Django因为联系人管理是本地桌面场景不是 Web 服务浏览器套壳只会增加无意义的复杂度。为什么不用 MongoDBSQLite 一个文件搞定评委想把整个项目连数据拷走也很方便不需要安装数据库服务。这两个反对问题的答案本身就适合写进开发文档的“选型理由”一节。提示拿到源码先打开 requirements.txt 看依赖。核心第三方库就是 jieba 和 speech_recognitiondifflib 是标准库。版本不同导致的运行失败很少见但 jieba 版本不同会带来分词细节差异自动分组结果可能不完全一致这是正常现象不影响主流程。2.2 数据表设计联系人主表与标签扩展表的取舍开发文档里最值得先看的是数据库表结构因为表结构没定好功能都是空谈。这份资源默认带两张表建表语句如下CREATE TABLE IF NOT EXISTS contacts ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, company TEXT DEFAULT , phone TEXT DEFAULT , remark TEXT DEFAULT , tags TEXT DEFAULT , last_contact TEXT DEFAULT , contact_count INTEGER DEFAULT 0, created_at TEXT DEFAULT (datetime(now,localtime)) ); CREATE TABLE IF NOT EXISTS contact_tags ( contact_id INTEGER NOT NULL, tag TEXT NOT NULL, PRIMARY KEY (contact_id, tag) );contacts 表是主表核心字段有四个name 用于存姓名或别名remark 存备注原文tags 存算法生成的标签last_contact 存最近联系日期。这里有几个细节值得注意last_contact 用 TEXT 而不是 DATETIME存 ISO 格式字符串方便排序和比较contact_count 是整数用于统计联系频率这是后续做“高频联系人”分析的基础created_at 使用本地时间默认值避免记录时间比实际早 8 小时。tags 字段在主表中以逗号分隔字符串存储这是我常用的做法。毕设场景联系人是几百条量级不需要为多对多关系提前引入 join导出 CSV/JSON 时可以直接导出不用二次拼接。contact_tags 表是扩展表如果后续要做“按标签维度统计联系人数量”再把这张表用起来main.py 里的读取函数换一个实现即可界面代码不用动。这份源码把两张表都建好其实就是给你留了一条升级路径。建表逻辑写在 database.py 里一般在程序入口初始化时自动执行。我一般会要求建表语句带 IF NOT EXISTS这样重复启动不会报错也不会把已有数据清掉。如果你自己改造项目记住一个原则先设计表再写功能最后画界面。反过来做改一处业务逻辑就要连带改界面很容易翻车。2.3 目录结构与运行流程拿到源码后先跑通一次拿到压缩包后先别急着看代码按文件清单核对一遍结构路径作用main.py程序入口初始化数据库并创建主窗口database.py连接数据库、建表、增删改查对外暴露 get_all_contacts 等函数ai_core.py模糊搜索、自动分组、语音转写三个 AI 能力模块ui_main.py主窗口布局与事件绑定requirements.txt第三方依赖列表docs/开发文档.md需求分析、功能设计、数据库说明、答辩思路tests/smoke_test.py快速自检脚本先跑它再开图形界面运行流程很简单四步走python -m venv venv # Windows 环境激活虚拟环境 venv\Scripts\activate # macOS / Linux 环境激活虚拟环境 source venv/bin/activate pip install -r requirements.txt python main.py第一行 python -m venv venv 是创建一个独立的虚拟环境这是最值得养成的好习惯。很多同学图省事直接把依赖装到系统 Python 里换一台机器或者更新系统库之后程序突然就起不来了问题往往出在依赖冲突。venv 把项目的 jieba、speech_recognition 隔离在 venv 目录里不会污染全局环境答辩换机器也更容易复现。venv\Scripts\activate 或 source venv/bin/activate 是激活虚拟环境的命令Windows 和 Unix 系统路径不同我都列在注释里。激活成功后命令行提示符前面会多出 (venv) 前缀这时候 pip install 装的包才会进虚拟环境。最后 python main.py 启动程序。如果这一步报 No module named不要怀疑代码先执行 pip list 看 jieba 是否存在再确认你用的 Python 解释器路径是否指向 venv 里的 python。大多数外观上的“跑不通”问题都出在这里。3. 核心 AI 功能实现模糊搜索、自动分组、语音备注转写3.1 模糊检索不用 LIKE 而用 difflib阈值设成多少合适联系人管理的搜索场景有一个典型痛点用户只记得大概读音或者叫法是“张山”但库里存的是“张三”SQL 的 LIKE 只能做精确包含匹配完全搜不出结果。模糊搜索解决的就是这个“气息对、字不对”的问题。这份源码在 ai_core.py 里用 difflib 实现了一个可调阈值的搜索函数import difflib def search_contacts(keyword: str, threshold: float 0.45) - list: 按姓名/公司字段模糊搜索联系人返回按相似度排序的列表 if not keyword.strip(): return get_all_contacts() candidates get_all_contacts() results [] for c in candidates: name_score difflib.SequenceMatcher( None, keyword.lower(), c[name].lower() ).ratio() company_score difflib.SequenceMatcher( None, keyword.lower(), c[company].lower() ).ratio() best max(name_score, company_score) if best threshold: c[_score] round(best, 2) results.append(c) results.sort(keylambda x: x[_score], reverseTrue) return results逻辑很直白先从数据库全量取出联系人然后分别与 name 和 company 做 SequenceMatcher 相似度计算取两者中较高的分数高于阈值就进结果集最后按分数倒序返回。关键参数是 threshold默认 0.45。我调试这个函数的时候0.35 会混入大量无关联系人0.6 又会漏掉同音字结果0.45 左右是一个比较均衡的点。你可以让这个阈值由 GUI 上的滑条控制范围设 0.3 到 0.7现场演示时效果更直观。SequenceMatcher 是标准库 difflib 里的核心类基于最长公共子序列思想计算两个字符串的相似度比率完全本地运行数据不用出内存也不依赖外网。更重要的是评委问“AI 在哪里”的时候你能把原理讲清楚而不是丢出一句“调了第三方接口”。几百条联系人全量遍历的性能开销完全可以忽略不需要引入倒排索引这种过度设计。3.2 自动分组基于关键词命中与词频的可解释标签生成自动分组是这份资源里最有“AI 观感”的功能新增或编辑联系人时程序根据备注文本自动打上“客户”“同事”“家人”等标签。实现思路是可解释的规则加词频而不是深度学习黑匣子。源码里是一个字典加分词函数import jieba PRESET_TAGS { 客户: [客户, 合同, 报价, 下单], 同事: [同事, 部门, 开会, 项目], 家人: [家, 爸, 妈, 亲戚], 朋友: [同学, 朋友, 聚], 重要: [重要, 领导, 总监, 负责人], 待跟进: [跟进, 回访, 待办, 未回], } def auto_generate_tags(remark: str, top_k: int 2) - list: 从备注文本里提取 Top K 个标签按命中分数降序返回 if not remark: return [] words jieba.lcut(remark) scores {tag: 0 for tag in PRESET_TAGS} for tag, keys in PRESET_TAGS.items(): for key in keys: if key in remark: # 原文直接命中关键词 scores[tag] 1 if key in words: # 分词后命中关键词 scores[tag] 1 ranked sorted(scores.items(), keylambda x: x[1], reverseTrue) return [tag for tag, score in ranked if score 0][:top_k]auto_generate_tags 接收备注文本返回一个字符串列表。逻辑分三步先用 jieba.lcut 把备注切词再遍历预设标签的触发词表同时做原文包含判断和分词命中判断最后按分数排序取前两个。原文命中和分词命中同时发生时分数为 2这比单纯规则匹配更稳也保留了可解释性——你可以指出“待跟进”这个标签是因为备注里出现了“回访”这个词。PRESET_TAGS 是这张自动分组的灵魂完全可以按自己的联系人类型改。比如做销售的人可以加入“健身房”“装修”“宠物”这类业务词做人事的人可以加入“面试”“简历”“offer”。top_k 默认 2这个值刻意设得很保守标签太多噪音就大两个标签足够覆盖绝大多数场景又不会遮挡手动标签。jieba.lcut 和 jieba.cut 的区别容易被忽略lcut 直接返回 listcut 返回生成器。在几百条备注的场景里list 更直观也不需要额外转换所以这里用 lcut。分词粒度会影响命中结果但整体流程正确不会导致程序崩溃真正的坑在于标签为空如果备注里没有任何预设词函数返回空列表调用方一定要处理这个分支否则会把空标签写进数据库。我一般会在保存函数里加一个判断AI 标签为空时保留用户手动选择的标签。3.3 语音备注转写离线识别加降级输入语音备注是“AI”二字的另一重支撑也给演示增加了一点互动感。源码里的语音模块不依赖云端识别服务用的是 speech_recognition 库接本地模型。这个函数我一般放在 ai_core.py 末尾import speech_recognition as sr def speech_to_text(timeout: float 3.0, phrase_time_limit: float 8.0) - str: 从麦克风采集一段语音并转成文字失败时返回空字符串 recognizer sr.Recognizer() with sr.Microphone() as source: recognizer.adjust_for_ambient_noise(source, duration0.5) print(开始录音请说话) try: audio recognizer.listen( source, timeouttimeout, phrase_time_limitphrase_time_limit ) except sr.WaitTimeoutError: return try: text recognizer.recognize_sphinx(audio, languagezh-CN) return text.strip() except sr.UnknownValueError: return 这个函数有两条关键路径录音阶段和识别阶段。录音阶段用 timeout 控制等待用户开口的时间超过 3 秒不说话就直接返回空串phrase_time_limit 限制单句话最长 8 秒防止录音无限拉长。识别阶段尝试调用本地语音模型识别失败或找不到模型时返回空字符串由上层调用者提示“语音识别未启用已切换为手动输入”。这里有个经验要分享本地语音模型文件通常比较大常见的做法是把模型放在项目根目录的 models 文件夹下在代码里写一个路径检查模型不存在时直接跳过语音识别分支不让程序崩掉。演示现场最怕两种意外一是麦克风被系统静音二是模型路径被移动。我见过太多次“点完语音按钮程序卡 5 秒后什么都没发生”的情况其实都不是代码逻辑问题而是路径失效。语音转写结果的落地也有讲究识别出的文本会拼接到 remark 字段末尾然后重新触发 auto_generate_tags让新备注内容也能参与标签计算。这样“语音录入”和“自动分组”两条 AI 链路就打通了。降级策略在这里非常重要语音识别只是备注输入的一个分支不是唯一入口手动输入永远可用这种设计在答辩演示时能兜底。4. 界面与业务流程把搜索、分组、统计串成一套可操作的系统4.1 主界面布局搜索框、联系人列表、标签面板三段式界面是 ui_main.py 的核心职责这类型程序的布局我习惯分成三段顶部搜索框中间联系人列表右侧详情与标签面板。搜索框承接 3.1 的模糊搜索函数列表用 ttk.Treeview 展示多列数据右侧表单负责新增和编辑。刷新联系人列表是一个高频操作源码里对应一个公共函数def refresh_tree(tree: ttk.Treeview, contacts: list) - None: 清空并重填联系人列表避免双击后数据越攒越多 for row in tree.get_children(): tree.delete(row) for c in contacts: tree.insert( , end, iidstr(c[id]), values(c[name], c[company], c.get(phone, )) )refresh_tree 第一步是遍历并删除 Treeview 里现有的全部行第二步是重新插入新的联系人数据。这个“先清空再插入”的顺序不能反否则每次刷新都会在旧数据后面追加新行操作几次之后列表就会出现大量重复项。iid 参数传入联系人主键的字符串形式双击行时通过 iid 就能定位到对应联系人 id不用再根据行号反查数据。values 的元组顺序必须和创建 Treeview 时 columns 的定义顺序完全一致。我踩过的坑是 columns 定义成 (name, company, phone)insert 的时候写成了 (c[company], c[name], c[phone])显示层面没有报错但第一列和第二列内容对调了很影响演示观感。如果你改过列表展示字段记得把这两处同步改掉。4.2 保存联系人的动作链AI 先算标签再写库最后刷新界面一次“保存联系人”动作在这份源码里不是简单的 insert而是一个动作链先生成 AI 标签再更新联系频次最后写库并刷新界面。保存函数的骨架如下def save_contact(window, form_data: dict) - bool: 新增/编辑联系人返回值表示是否成功写入数据库 if not form_data.get(name): return False remark form_data.get(remark, ) tags auto_generate_tags(remark) # 1. AI 生成标签 # 2. AI 标签为空时不覆盖手动选择保留 form_data 里的旧值 if tags: form_data[tags] ,.join(tags) # 3. 每次保存视为一次联系频次加 1 form_data[contact_count] int(form_data.get(contact_count, 0)) 1 # 4. 写库并刷新界面 result upsert_contact(form_data) refresh_tree(window.tree, get_all_contacts()) return result is not None这个函数的顺序值得细看先校验姓名不为空然后调 auto_generate_tags再把生成的列表转成逗号分隔字符串。这里有一个重要判断——只有 tags 非空才覆盖 form_data 里的 tags如果 AI 没生成任何标签保留用户手动选择的值防止空标签把已有标签顶掉。contact_count 每次保存都加 1这是统计“最近常联系的人”的数据来源。你可以根据业务需要改成只有删除重录时才加 1或者保存时由用户勾选“本次联系”。我一般保持每次保存加 1 的简单规则毕设阶段够用。upsert_contact 是 database.py 里的适配函数内部根据 id 是否存在决定执行 INSERT 还是 UPDATE调用方不用关心数据库细节。动作链的设计核心是把“业务动作”和“界面刷新”分开。语音线程或后台任务只负责写数据库写完之后通过 window.tree 强制刷新一次界面这样既保证数据一致性又避免界面卡死。如果你想再加一个统计分析功能在这个链条里插入一个 update_stats() 调用就可以不需要重写现有关逻辑。4.3 数据导入导出CSV/JSON 的编码与去重细节毕业答辩最尴尬的时刻之一是换了一台演示机器发现联系人列表是空的。所以数据导入导出不是“锦上添花”功能而是现场恢复演示数据的后悔药。导出函数的实现如下import csv import json def export_contacts(path: str, fmt: str csv) - bool: 将全部联系人按 CSV 或 JSON 格式导出到 path contacts get_all_contacts() if fmt csv: with open(path, w, encodingutf-8-sig, newline) as f: writer csv.DictWriter(f, fieldnames[ id, name, company, phone, remark, tags, last_contact, contact_count ]) writer.writeheader() writer.writerows(contacts) elif fmt json: with open(path, w, encodingutf-8) as f: json.dump(contacts, f, ensure_asciiFalse, indent2) return TrueCSV 导出有三个细节容易踩坑。encoding 用 utf-8-sig 而不是 utf-8因为 Excel 打开无 BOM 的 UTF-8 文件会把中文识别成乱码newline 是为了避免 Windows 环境下每行数据之间出现多余空行fieldnames 顺序就是 CSV 表头顺序必须和 writerows 传入字典的键一致。JSON 导出的关键在于 ensure_asciiFalse否则中文字符会被转成 \uXXXX 形式打开文件完全没法看。导入方向比导出更容易踩坑。常见做法是导入时按手机号去重先读出 CSV 里所有行把数据库里已有手机号放进一个 set逐行检查是否已存在存在就跳过不存在才插入。这份源码如果默认没做去重我建议你在 import_contacts 函数的第一段就补上这个逻辑否则多次导入同一份文件会产生重复联系人演示现场很难清理。5. 避坑与排查源码跑不通的五个常见原因5.1 启动报 ModuleNotFoundError依赖装了但不是装在同一个 Python 里现象在项目目录执行 python main.py立刻提示 No module named jieba。原因最常见的是依赖没装或者装了但装进了另一个 Python 环境。很多 IDE 在启动项目时会自动选择系统的 Python 解释器你的 pip install -r requirements.txt 明明执行成功但装的路径可能和 IDE 用的不是同一个。解决先执行 pip list 看 jieba 是否存在再执行 where python 看当前解释器路径。正确做法是确认当前处在虚拟环境激活状态命令行前缀有 venv并且 IDE 的“解释器路径”手动指向 venv 目录而不是系统 Python。关键调试命令是python -m pip install -r requirements.txt用 python -m pip 可以确保安装目标和当前运行 main.py 的解释器一致。5.2 中文显示乱码或者界面出现方框现象代码运行成功但界面上中文按钮变成一排方框或者 CSV 打开后是乱码。原因源码文件保存时的编码不是 UTF-8Tkinter 默认字体对中文的兼容性在 Windows 上不稳定。解决所有 .py 文件统一用 UTF-8 保存并在文件头部写# -*- coding: utf-8 -*-。在 main.py 里创建窗口的位置加一行全局字体配置root.option_add(*Font, (SimHei, 11))把默认字体改成常见中文字体。CSV 导出时编码用 utf-8-sig这一点在第 4 章已经提过。这问题不是玄学是编码和字体两件事分开排查效率最高。5.3 保存时 sqlite3.OperationalError: database is locked现象程序运行一段时间后保存联系人或者导入数据时偶发报错提示 database is locked。原因语音线程或后台统计线程与主线程共用了同一个 SQLite 连接两个线程同时写入或者写操作执行后没有调用 commit。解决优先选择“短连接”模式每次增删改查函数内部新建连接、执行操作、提交并关闭连接。代码量增加很少但能根除大部分锁问题。如果必须保持长连接可以在创建连接时传 check_same_threadFalse并确保所有写操作不跨线程同时执行。排查时先看 database.py 里是否只有一个全局连接被到处引用这是最可疑的地方。5.4 PyInstaller 打包后找不到静态资源现象在开发环境运行正常用 PyInstaller 打包成 exe 后程序能打开但图标不显示语音模型文件也读不到。原因打包后程序工作目录偏移代码里写的相对路径如 static/icon.ico 已经不再指向解压目录。解决在 main.py 顶部加一个资源路径转换函数import sys, os def resource_path(relative_path: str) - str: 兼容开发模式与打包模式的资源路径查找 base getattr(sys, _MEIPASS, os.path.dirname(__file__)) return os.path.join(base, relative_path)getattr 判断的重点是如果程序运行在 PyInstaller 的临时解压环境里sys._MEIPASS 存在就用它作为基准目录否则用当前文件所在目录。项目里所有读文件的地方包括数据库路径、图标路径、模型路径统一调用 resource_path 包裹就再也不会出现打包后文件缺失的诡异问题。5.5 语音识别一直返回空字符串现象麦克风明明有声音程序也没有报错但语音转写结果始终为空。原因环境噪声校准后能量阈值调得过高导致语音被判为静音或者单句话时长没有达到识别要求或者本地中文模型没加载成功。解决先单独跑一段调试代码用 recognizer.energy_threshold 打印当前阈值看它在说话时是否明显高于环境噪声高点。把 phrase_time_limit 从默认 8 秒调大到 10 秒给识别器更多缓冲。模型路径问题好查在 ai_core.py 里打一行 print确认加载的是哪条路径。如果现场环境实在不适合语音输入直接走手动输入分支这也是当初设计降级策略的意义。6. 验证与进阶从“跑起来”到“讲得清”再往前迈一步6.1 拿到源码先跑一个链路自检脚本图形界面适合演示不适合快速验证逻辑。源码里带的 tests/smoke_test.py 是我非常看重的一个文件它的核心逻辑大概是这样def smoke_test(): created create_contact({ name: 张山, company: 测试公司, remark: 客户 合同 回访 }) assert created is not None found search_contacts(张山, 0.45) assert len(found) 1 tags auto_generate_tags(下午和客户谈合同之后要回访) assert 客户 in tags and 待跟进 in tags print(核心链路 OK)用断言检查三件事联系人能不能创建、模糊搜索能不能搜到、自动标签能不能命中关键词。这样在换机器演示前先跑一遍脚本链路通了再开图形界面能省下大量现场排错时间。6.2 三个低成本升级方向第一个方向是加拼音首字母字段让输入“zs”就能搜到“张三”。在 contacts 表加一列 pinyin录入时生成姓名首字母并存进去搜索函数里加一个字段命中分支即可。第二个方向是加联系频率统计图用 contact_count 字段执行一句SELECT name, contact_count FROM contacts ORDER BY contact_count DESC LIMIT 10把结果画成柱状图答辩展示的层次感立刻不同。第三个方向是加未联系提醒用 date() 函数计算 last_contact 距今超过 30 天的联系人在主界面列表标红。这三个都基于现有表结构补代码不需要推倒重来。有次演示前临时换了机器我图省事直接双击 main.py结果黑屏三分钟。后来才意识到数据文件、模型路径、解释器全都要检查一遍。从那以后我每次拿到这种带开发文档的源码都强制先跑一遍 smoke_test再确认数据目录可写最后才开图形界面这个顺序救过我很多次。希望帮到你。本文还有配套的精品资源点击获取